diff --git a/src/02_syntax/expression.zig b/src/02_syntax/expression.zig index 1a1b71d..552de27 100644 --- a/src/02_syntax/expression.zig +++ b/src/02_syntax/expression.zig @@ -10,7 +10,7 @@ pub const Expression = union(enum) { /// Attempts to parse an expression from a token stream. /// /// Receives a pointer to the memory for initialization - pub fn init(target: *Expression, tokens: *const std.ArrayList(Token), pos: usize) error{Unmatched}!void { + pub fn init(tokens: *const std.ArrayList(Token), pos: usize) error{Unmatched}!Expression { std.debug.assert(pos < tokens.items.len); const t = tokens.items[pos]; @@ -18,7 +18,7 @@ pub const Expression = union(enum) { return error.Unmatched; } - target.* = .{ + return .{ .number = &t, }; } @@ -29,8 +29,7 @@ test "should parse expression" { const tokens = try lexic.tokenize(input, std.testing.allocator); defer tokens.deinit(); - var expr: Expression = undefined; - try expr.init(&tokens, 0); + const expr = try Expression.init(&tokens, 0); try std.testing.expectEqualDeep("322", expr.number.value); try std.testing.expectEqualDeep(TokenType.Int, expr.number.token_type); } @@ -40,8 +39,7 @@ test "should fail on non expression" { const tokens = try lexic.tokenize(input, std.testing.allocator); defer tokens.deinit(); - var expr: Expression = undefined; - expr.init(&tokens, 0) catch |err| { + const expr = Expression.init(&tokens, 0) catch |err| { try std.testing.expectEqual(ParseError.Unmatched, err); return; }; diff --git a/src/02_syntax/root.zig b/src/02_syntax/root.zig index 7696b57..b0da001 100644 --- a/src/02_syntax/root.zig +++ b/src/02_syntax/root.zig @@ -3,21 +3,71 @@ const lexic = @import("lexic"); const expression = @import("./expression.zig"); const variable = @import("./variable.zig"); const types = @import("./types.zig"); +const statement = @import("./statement.zig"); const Token = lexic.Token; const TokenType = lexic.TokenType; const ParseError = types.ParseError; +const TokenStream = types.TokenStream; -const Statement = union(enum) { - VariableBinding: u8, +pub const Module = struct { + statements: std.ArrayList(*statement.Statement), + alloc: std.mem.Allocator, - fn parse(tokens: *const std.ArrayList(Token), pos: usize) ParseError!@This() { - _ = tokens; - _ = pos; - return ParseError.Error; + pub fn init(target: *@This(), tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void { + var arrl = std.ArrayList(*statement.Statement).init(allocator); + errdefer arrl.deinit(); + + const input_len = tokens.items.len; + var current_pos = pos; + + // parse many statements + while (current_pos < input_len) { + std.debug.print("running on pos {d} \n", .{current_pos}); + + // FIXME: if a statement was added to the array list, + // and then one of these fails, + // will all previous statements leak memory? + var stmt = try allocator.create(statement.Statement); + errdefer allocator.destroy(stmt); + const next_pos = try stmt.init(tokens, current_pos, allocator); + current_pos = next_pos; + + arrl.append(stmt) catch { + return ParseError.Error; + }; + } + + target.* = .{ + // FIXME: is this copying the whole arraylist? should use a pointer? + .statements = arrl, + .alloc = allocator, + }; + } + + pub fn deinit(self: @This()) void { + // FIXME: should deinit all elements inside the arraylist no? otherwise + // they will leak no? + for (self.statements.items) |stmt| { + stmt.deinit(); + self.alloc.destroy(stmt); + } + self.statements.deinit(); } }; test { std.testing.refAllDecls(@This()); } + +test "should parse a single statement" { + const input = "var my_variable = 322"; + const tokens = try lexic.tokenize(input, std.testing.allocator); + defer tokens.deinit(); + + var module: Module = undefined; + _ = try module.init(&tokens, 0, std.testing.allocator); + + std.debug.print("len: {d} \n", .{module.statements.items.len}); + defer module.deinit(); +} diff --git a/src/02_syntax/statement.zig b/src/02_syntax/statement.zig index a2cea08..22a42fb 100644 --- a/src/02_syntax/statement.zig +++ b/src/02_syntax/statement.zig @@ -11,14 +11,16 @@ const ParseError = types.ParseError; pub const Statement = union(enum) { VariableBinding: *variable.VariableBinding, - fn init(target: *Statement, tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void { + /// Parses a Statement and return the position of the next token + pub fn init(target: *Statement, tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!usize { // try to parse a variable definition var vardef: variable.VariableBinding = undefined; var parse_failed = false; - vardef.init(tokens, pos, allocator) catch |err| switch (err) { - error.Unmatched => { + const vardef_end = vardef.init(tokens, pos, allocator) catch |err| switch (err) { + error.Unmatched => blk: { parse_failed = true; + break :blk 0; }, else => { return err; @@ -29,14 +31,14 @@ pub const Statement = union(enum) { target.* = .{ .VariableBinding = &vardef, }; - return; + return vardef_end; } // fail return ParseError.Unmatched; } - fn deinit(self: @This()) void { + pub fn deinit(self: @This()) void { switch (self) { .VariableBinding => |v| v.deinit(), } @@ -49,7 +51,7 @@ test "should parse a variable declaration statement" { defer tokens.deinit(); var statement: Statement = undefined; - try statement.init(&tokens, 0, std.testing.allocator); + _ = try statement.init(&tokens, 0, std.testing.allocator); defer statement.deinit(); switch (statement) { @@ -65,7 +67,7 @@ test "should fail on other constructs" { defer tokens.deinit(); var statement: Statement = undefined; - statement.init(&tokens, 0, std.testing.allocator) catch |e| switch (e) { + _ = statement.init(&tokens, 0, std.testing.allocator) catch |e| switch (e) { error.Unmatched => { return; }, diff --git a/src/02_syntax/variable.zig b/src/02_syntax/variable.zig index e4d9cbf..722d758 100644 --- a/src/02_syntax/variable.zig +++ b/src/02_syntax/variable.zig @@ -11,11 +11,11 @@ pub const VariableBinding = struct { is_mutable: bool, datatype: ?*lexic.Token, identifier: *lexic.Token, - expression: *expression.Expression, + expression: *const expression.Expression, alloc: std.mem.Allocator, - /// Parses a variable binding - pub fn init(target: *VariableBinding, tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void { + /// Parses a variable binding and returns the position of the next token + pub fn init(target: *VariableBinding, tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!usize { std.debug.assert(pos < tokens.items.len); // try to parse a var keyword @@ -42,9 +42,8 @@ pub const VariableBinding = struct { // parse expression if (pos + 3 >= tokens.items.len) return ParseError.Error; - var exp = try allocator.create(expression.Expression); - errdefer allocator.destroy(exp); - exp.init(tokens, pos + 3) catch { + + const exp = expression.Expression.init(tokens, pos + 3) catch { return ParseError.Error; }; @@ -53,13 +52,15 @@ pub const VariableBinding = struct { .is_mutable = true, .datatype = null, .identifier = identifier, - .expression = exp, + .expression = &exp, .alloc = allocator, }; + // TODO: when expression parses more than one token this will break. + return pos + 4; } pub fn deinit(self: *VariableBinding) void { - self.alloc.destroy(self.expression); + _ = self; } }; @@ -69,7 +70,7 @@ test "should parse a minimal var" { defer tokens.deinit(); var binding: VariableBinding = undefined; - try binding.init(&tokens, 0, std.testing.allocator); + _ = try binding.init(&tokens, 0, std.testing.allocator); defer binding.deinit(); try std.testing.expectEqual(true, binding.is_mutable); @@ -81,7 +82,7 @@ test "should fail is it doesnt start with var" { defer tokens.deinit(); var binding: VariableBinding = undefined; - binding.init(&tokens, 0, std.testing.allocator) catch |err| { + _ = binding.init(&tokens, 0, std.testing.allocator) catch |err| { try std.testing.expectEqual(ParseError.Unmatched, err); return; }; @@ -95,7 +96,7 @@ test "should fail if the idenfier is missing" { defer tokens.deinit(); var binding: VariableBinding = undefined; - binding.init(&tokens, 0, std.testing.allocator) catch |err| { + _ = binding.init(&tokens, 0, std.testing.allocator) catch |err| { try std.testing.expectEqual(ParseError.Error, err); return; }; @@ -109,7 +110,7 @@ test "should fail if there is not an identifier after var" { defer tokens.deinit(); var binding: VariableBinding = undefined; - binding.init(&tokens, 0, std.testing.allocator) catch |err| { + _ = binding.init(&tokens, 0, std.testing.allocator) catch |err| { try std.testing.expectEqual(ParseError.Error, err); return; }; @@ -123,7 +124,7 @@ test "should fail if the equal sign is missing" { defer tokens.deinit(); var binding: VariableBinding = undefined; - binding.init(&tokens, 0, std.testing.allocator) catch |err| { + _ = binding.init(&tokens, 0, std.testing.allocator) catch |err| { try std.testing.expectEqual(ParseError.Error, err); return; }; @@ -137,7 +138,7 @@ test "should fail if the equal sign is not found" { defer tokens.deinit(); var binding: VariableBinding = undefined; - binding.init(&tokens, 0, std.testing.allocator) catch |err| { + _ = binding.init(&tokens, 0, std.testing.allocator) catch |err| { try std.testing.expectEqual(ParseError.Error, err); return; }; @@ -151,7 +152,7 @@ test "should fail if the expression parsing fails" { defer tokens.deinit(); var binding: VariableBinding = undefined; - binding.init(&tokens, 0, std.testing.allocator) catch |err| { + _ = binding.init(&tokens, 0, std.testing.allocator) catch |err| { try std.testing.expectEqual(ParseError.Error, err); return; };