Skip to content

Commit

Permalink
Add 'pub' keyword for public declarations. Closes #34
Browse files Browse the repository at this point in the history
  • Loading branch information
Luukdegram committed Nov 4, 2020
1 parent 239963a commit c0e4ad0
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 23 deletions.
2 changes: 1 addition & 1 deletion editors/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

As the package itself is unpublished, it is required to manually install the extension.
For convenience, the package file is currently included within the repo.
- Download [luf-0.1.1.xsix](luf-0.1.1.vsix)
- Download [luf-0.1.2.xsix](luf-0.1.2.vsix)
- Open vscode
- Go to extensions (Ctrl+Shift+x)
- Click the 3 dots (`...`) on the top and click on 'Install from VSIX'.
Expand Down
Binary file removed editors/vscode/luf-0.1.1.vsix
Binary file not shown.
Binary file added editors/vscode/luf-0.1.2.vsix
Binary file not shown.
4 changes: 4 additions & 0 deletions editors/vscode/luf.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@
"match": "\\b(while|for)\\b",
"name": "keyword.control.repeat.luf"
},
{
"name": "keyword.storage.luf",
"match": "\\b(pub)\\b"
},
{
"name": "keyword.structure.luf",
"match": "\\b(enum|import)\\b"
Expand Down
2 changes: 1 addition & 1 deletion editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "luf",
"displayName": "Luf",
"description": "Syntax highlighting for the Luf programming language",
"version": "0.1.1",
"version": "0.1.2",
"publisher": "luukdegram",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion examples/to_import.luf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ const sum = fn(x:int, y:int) int {
return x + y
}

const add = fn(x: int) int {
pub const add = fn(x: int) int {
return sum(20, x)
}
1 change: 1 addition & 0 deletions src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ pub const Node = union(NodeType) {
value: Node,
type_def: ?Node,
mutable: bool = false,
is_pub: bool = false,
};

/// Identifier node -> x
Expand Down
70 changes: 58 additions & 12 deletions src/compiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ pub const Compiler = struct {
name: []const u8,
/// Not mutable by default
mutable: bool = false,
/// Not public by default
is_pub: bool = false,
/// Index of symbol table
index: u16 = 0,
/// The scope the symbol belongs to
Expand Down Expand Up @@ -164,7 +166,15 @@ pub const Compiler = struct {

/// Defines a new symbol and saves it in the symbol table
/// Returns an error if Symbol already exists
fn define(self: *Scope, name: []const u8, mutable: bool, node: ast.Node, forward_declared: bool, index: ?u16) !?*Symbol {
fn define(
self: *Scope,
name: []const u8,
mutable: bool,
node: ast.Node,
forward_declared: bool,
is_pub: bool,
index: ?u16,
) !?*Symbol {
if (self.symbols.contains(name)) return null;

const new_index = index orelse @intCast(u16, self.symbols.items().len);
Expand All @@ -178,6 +188,7 @@ pub const Compiler = struct {
.node = node,
.forward_declared = forward_declared,
.ident = undefined,
.is_pub = is_pub,
};

try self.symbols.putNoClobber(self.allocator, name, symbol);
Expand Down Expand Up @@ -256,9 +267,16 @@ pub const Compiler = struct {
}

/// Defines a new symbol in the current scope
fn defineSymbol(self: *Compiler, name: []const u8, mutable: bool, node: ast.Node, forward_declared: bool) !?*Symbol {
fn defineSymbol(
self: *Compiler,
name: []const u8,
mutable: bool,
node: ast.Node,
forward_declared: bool,
is_pub: bool,
) !?*Symbol {
const index: ?u16 = if (self.scope.id == .global or self.scope.id == .module) self.gc else null;
if (try self.scope.define(name, mutable, node, forward_declared, index)) |symbol| {
if (try self.scope.define(name, mutable, node, forward_declared, is_pub, index)) |symbol| {
if (index != null) self.gc += 1;
return symbol;
} else return null;
Expand Down Expand Up @@ -342,8 +360,18 @@ pub const Compiler = struct {
continue;
} else if (decl.value != .func_lit) continue;

const symbol = (try self.defineSymbol(decl.name.identifier.value, decl.mutable, node, true)) orelse
return self.fail("'{}' has already been declared", node.tokenPos(), .{decl.name.identifier.value});
const symbol = (try self.defineSymbol(
decl.name.identifier.value,
decl.mutable,
node,
true,
decl.is_pub,
)) orelse return self.fail(
"'{}' has already been declared",
node.tokenPos(),
.{decl.name.identifier.value},
);

symbol.ident = try self.ir.emitIdent(node.tokenPos(), symbol.name, .global, symbol.index);
}
for (self.scope.symbols.items()) |entry| {
Expand All @@ -355,7 +383,7 @@ pub const Compiler = struct {
symbol.name,
symbol.index,
.global,
false,
symbol.is_pub,
symbol.mutable,
value,
);
Expand Down Expand Up @@ -394,6 +422,7 @@ pub const Compiler = struct {

for (tree.nodes) |n| {
if (n != .declaration or n.declaration.value != .func_lit) continue;
if (!n.declaration.is_pub) continue; // only public declarations

const decl = n.declaration;
const name = decl.name.identifier.value;
Expand All @@ -410,6 +439,7 @@ pub const Compiler = struct {
.forward_declared = true,
.index = self.gc,
.ident = undefined,
.is_pub = decl.is_pub,
};

try module.symbols.putNoClobber(self.allocator, name, symbol);
Expand Down Expand Up @@ -568,7 +598,7 @@ pub const Compiler = struct {
.{decl.name.identifier.value},
);
} else blk: {
const s = (try self.defineSymbol(decl.name.identifier.value, decl.mutable, node, false)).?;
const s = (try self.defineSymbol(decl.name.identifier.value, decl.mutable, node, false, decl.is_pub)).?;
s.ident = try self.ir.emitIdent(
node.tokenPos(),
s.name,
Expand All @@ -593,7 +623,7 @@ pub const Compiler = struct {
.global, .module => .global,
else => .local,
},
false,
symbol.is_pub,
symbol.mutable,
decl_value,
);
Expand Down Expand Up @@ -668,7 +698,7 @@ pub const Compiler = struct {

for (function.params) |param| {
// function arguments are not mutable by default
const symbol = (try self.defineSymbol(param.func_arg.value, false, param, false)) orelse
const symbol = (try self.defineSymbol(param.func_arg.value, false, param, false, false)) orelse
return self.fail(
"Identifier '{}' has already been declared",
function.token.start,
Expand Down Expand Up @@ -834,16 +864,31 @@ pub const Compiler = struct {
const index_node = &ast.Node.ForLoop.index_node;
index_node.token = i.identifier.token;

const symbol = (try self.defineSymbol(i.identifier.value, false, ast.Node{ .int_lit = index_node }, false)) orelse
return self.fail("Identifier '{}' has already been declared", loop.token.start, .{i.identifier.value});
const symbol = (try self.defineSymbol(
i.identifier.value,
false, // not mutable
ast.Node{ .int_lit = index_node },
false, // not forward declared
false, // not public
)) orelse return self.fail(
"Identifier '{}' has already been declared",
loop.token.start,
.{i.identifier.value},
);

// generate an identifier to attach to the symbol
symbol.ident = try self.ir.emitIdent(i.tokenPos(), symbol.name, .local, symbol.index);
break :blk symbol.ident;
} else null;

// parser already parses it as an identifier, no need to check here again
const capture = (try self.defineSymbol(loop.capture.identifier.value, false, loop.iter, false)) orelse return self.fail(
const capture = (try self.defineSymbol(
loop.capture.identifier.value,
false, // not mutable
loop.iter,
false, // not forward declared
false, // not public
)) orelse return self.fail(
"Capture identifier '{}' has already been declared",
loop.token.start,
.{loop.capture.identifier.value},
Expand Down Expand Up @@ -939,6 +984,7 @@ pub const Compiler = struct {
symbol.mutable,
symbol.node,
true,
symbol.is_pub,
symbol.index,
);
new_symbol.?.ident = try self.ir.emitIdent(symbol.node.tokenPos(), symbol.name, .global, symbol.index);
Expand Down
53 changes: 45 additions & 8 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fn findPrecedence(token_type: Token.TokenType) Precedence {
}

/// Parses source code into an AST tree
pub fn parse(allocator: *Allocator, source: []const u8, _errors: *Errors) Parser.Error!Tree {
pub fn parse(allocator: *Allocator, source: []const u8, err: *Errors) Parser.Error!Tree {
var lexer = Lexer.init(source);

var arena = std.heap.ArenaAllocator.init(allocator);
Expand All @@ -78,7 +78,8 @@ pub fn parse(allocator: *Allocator, source: []const u8, _errors: *Errors) Parser
.allocator = &arena.allocator,
.lexer = &lexer,
.source = source,
.err = _errors,
.err = err,
.depth = 0,
};

var nodes = ArrayList(Node).init(parser.allocator);
Expand All @@ -98,12 +99,19 @@ pub fn parse(allocator: *Allocator, source: []const u8, _errors: *Errors) Parser
/// Parser retrieves tokens from our Lexer and turns them into
/// nodes to create an AST.
pub const Parser = struct {
/// Current token that has been parsed
current_token: Token,
/// The next token that will be set as current when calling .next() or .expectPeek()
peek_token: Token,
allocator: *Allocator,
/// Lexer that tokenized all tokens and is used to retrieve the next token
lexer: *Lexer,
/// Original source code. Memory is not owned by the parser.
source: []const u8,
/// List of errors that can be filled with Parser errors
err: *Errors,
/// Current scope depth. Increased and decreased by blocks
depth: usize,

pub const Error = error{
ParserError,
Expand All @@ -129,7 +137,7 @@ pub const Parser = struct {
fn parseStatement(self: *Parser) Error!Node {
return switch (self.current_token.token_type) {
.comment => self.parseComment(),
.constant, .mutable => self.parseDeclaration(),
.constant, .mutable, .@"pub" => self.parseDeclaration(),
.@"return" => self.parseReturn(),
.@"switch" => self.parseSwitchStatement(),
.while_loop => self.parseWhile(),
Expand All @@ -142,13 +150,27 @@ pub const Parser = struct {

/// Parses a declaration
fn parseDeclaration(self: *Parser) Error!Node {
const is_pub = if (self.currentIsType(.@"pub")) blk: {
if (self.depth > 0) {
return self.fail(
"Public declaration not allowed outside global scope",
self.current_token.start,
.{},
);
}

self.next();
break :blk true;
} else false;

const decl = try self.allocator.create(Node.Declaration);
decl.* = .{
.token = self.current_token,
.name = undefined,
.value = undefined,
.type_def = null,
.mutable = self.current_token.token_type == .mutable,
.mutable = self.currentIsType(.mutable),
.is_pub = is_pub,
};

try self.expectPeek(.identifier);
Expand Down Expand Up @@ -379,6 +401,9 @@ pub const Parser = struct {
/// Indirectly asserts a closing bracket is found
/// Parse statements using this function do not have to assert for it.
fn parseBlockStatement(self: *Parser) !Node {
self.depth += 1;
defer self.depth -= 1;

const block = try self.allocator.create(Node.BlockStatement);
block.* = .{
.token = self.current_token,
Expand Down Expand Up @@ -868,12 +893,13 @@ pub const Parser = struct {
}
};

test "Parse Delcaration" {
test "Parse Declaration" {
var allocator = testing.allocator;
const test_cases = .{
.{ .input = "const x = 5", .id = "x", .expected = 5, .mutable = false },
.{ .input = "mut y = 50", .id = "y", .expected = 50, .mutable = true },
.{ .input = "mut x = 2 const y = 5", .id = "y", .expected = 5, .mutable = false },
.{ .input = "const x = 5", .id = "x", .expected = 5, .mutable = false, .is_pub = false },
.{ .input = "mut y = 50", .id = "y", .expected = 50, .mutable = true, .is_pub = false },
.{ .input = "mut x = 2 const y = 5", .id = "y", .expected = 5, .mutable = false, .is_pub = false },
.{ .input = "pub const x = 2 pub const y = 5", .id = "y", .expected = 5, .mutable = false, .is_pub = true },
};

inline for (test_cases) |case| {
Expand All @@ -885,9 +911,20 @@ test "Parse Delcaration" {
testing.expectEqualSlices(u8, case.id, node.name.identifier.value);
testing.expect(case.expected == node.value.int_lit.value);
testing.expect(case.mutable == node.mutable);
testing.expect(case.is_pub == node.is_pub);
}
}

test "Parse public declaration outside global scope" {
var allocator = testing.allocator;
const input = "if(1<2){ pub const x = 2 }";

var errors = Errors.init(allocator);
defer errors.deinit();
testing.expectError(Parser.Error.ParserError, parse(allocator, input, &errors));
testing.expectEqual(@as(usize, 1), errors.list.items.len);
}

test "Parse Return statment" {
const test_cases = .{
.{ .input = "return 5", .expected = 5 },
Expand Down
4 changes: 4 additions & 0 deletions src/token.zig
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub const Token = struct {
@"break",
@"enum",
@"switch",
@"pub",
// types
bool_type,
int_type,
Expand Down Expand Up @@ -109,6 +110,7 @@ pub const Token = struct {
.{ "int", .int_type },
.{ "string", .string_type },
.{ "void", .void_type },
.{ "pub", .@"pub" },
});

/// Returns the string value of the token
Expand Down Expand Up @@ -179,6 +181,7 @@ pub const Token = struct {
.@"continue" => "continue",
.@"enum" => "enum",
.@"switch" => "switch",
.@"pub" => "pub",
// types
.bool_type => "bool",
.string_type => "string",
Expand Down Expand Up @@ -219,6 +222,7 @@ test "Keywords" {
"int",
"string",
"void",
"pub",
};

for (keywords) |keyword| {
Expand Down

0 comments on commit c0e4ad0

Please sign in to comment.