fix: memory issues in syntax root

This commit is contained in:
Fernando Araoz 2024-12-15 07:17:13 -05:00
parent 9a31d1aca3
commit 6edb533f3d
4 changed files with 85 additions and 34 deletions

View File

@ -10,7 +10,7 @@ pub const Expression = union(enum) {
/// Attempts to parse an expression from a token stream. /// Attempts to parse an expression from a token stream.
/// ///
/// Receives a pointer to the memory for initialization /// 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); std.debug.assert(pos < tokens.items.len);
const t = tokens.items[pos]; const t = tokens.items[pos];
@ -18,7 +18,7 @@ pub const Expression = union(enum) {
return error.Unmatched; return error.Unmatched;
} }
target.* = .{ return .{
.number = &t, .number = &t,
}; };
} }
@ -29,8 +29,7 @@ test "should parse expression" {
const tokens = try lexic.tokenize(input, std.testing.allocator); const tokens = try lexic.tokenize(input, std.testing.allocator);
defer tokens.deinit(); defer tokens.deinit();
var expr: Expression = undefined; const expr = try Expression.init(&tokens, 0);
try expr.init(&tokens, 0);
try std.testing.expectEqualDeep("322", expr.number.value); try std.testing.expectEqualDeep("322", expr.number.value);
try std.testing.expectEqualDeep(TokenType.Int, expr.number.token_type); 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); const tokens = try lexic.tokenize(input, std.testing.allocator);
defer tokens.deinit(); defer tokens.deinit();
var expr: Expression = undefined; const expr = Expression.init(&tokens, 0) catch |err| {
expr.init(&tokens, 0) catch |err| {
try std.testing.expectEqual(ParseError.Unmatched, err); try std.testing.expectEqual(ParseError.Unmatched, err);
return; return;
}; };

View File

@ -3,21 +3,71 @@ const lexic = @import("lexic");
const expression = @import("./expression.zig"); const expression = @import("./expression.zig");
const variable = @import("./variable.zig"); const variable = @import("./variable.zig");
const types = @import("./types.zig"); const types = @import("./types.zig");
const statement = @import("./statement.zig");
const Token = lexic.Token; const Token = lexic.Token;
const TokenType = lexic.TokenType; const TokenType = lexic.TokenType;
const ParseError = types.ParseError; const ParseError = types.ParseError;
const TokenStream = types.TokenStream;
const Statement = union(enum) { pub const Module = struct {
VariableBinding: u8, statements: std.ArrayList(*statement.Statement),
alloc: std.mem.Allocator,
fn parse(tokens: *const std.ArrayList(Token), pos: usize) ParseError!@This() { pub fn init(target: *@This(), tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void {
_ = tokens; var arrl = std.ArrayList(*statement.Statement).init(allocator);
_ = pos; errdefer arrl.deinit();
return ParseError.Error;
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 { test {
std.testing.refAllDecls(@This()); 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();
}

View File

@ -11,14 +11,16 @@ const ParseError = types.ParseError;
pub const Statement = union(enum) { pub const Statement = union(enum) {
VariableBinding: *variable.VariableBinding, 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 // try to parse a variable definition
var vardef: variable.VariableBinding = undefined; var vardef: variable.VariableBinding = undefined;
var parse_failed = false; var parse_failed = false;
vardef.init(tokens, pos, allocator) catch |err| switch (err) { const vardef_end = vardef.init(tokens, pos, allocator) catch |err| switch (err) {
error.Unmatched => { error.Unmatched => blk: {
parse_failed = true; parse_failed = true;
break :blk 0;
}, },
else => { else => {
return err; return err;
@ -29,14 +31,14 @@ pub const Statement = union(enum) {
target.* = .{ target.* = .{
.VariableBinding = &vardef, .VariableBinding = &vardef,
}; };
return; return vardef_end;
} }
// fail // fail
return ParseError.Unmatched; return ParseError.Unmatched;
} }
fn deinit(self: @This()) void { pub fn deinit(self: @This()) void {
switch (self) { switch (self) {
.VariableBinding => |v| v.deinit(), .VariableBinding => |v| v.deinit(),
} }
@ -49,7 +51,7 @@ test "should parse a variable declaration statement" {
defer tokens.deinit(); defer tokens.deinit();
var statement: Statement = undefined; var statement: Statement = undefined;
try statement.init(&tokens, 0, std.testing.allocator); _ = try statement.init(&tokens, 0, std.testing.allocator);
defer statement.deinit(); defer statement.deinit();
switch (statement) { switch (statement) {
@ -65,7 +67,7 @@ test "should fail on other constructs" {
defer tokens.deinit(); defer tokens.deinit();
var statement: Statement = undefined; 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 => { error.Unmatched => {
return; return;
}, },

View File

@ -11,11 +11,11 @@ pub const VariableBinding = struct {
is_mutable: bool, is_mutable: bool,
datatype: ?*lexic.Token, datatype: ?*lexic.Token,
identifier: *lexic.Token, identifier: *lexic.Token,
expression: *expression.Expression, expression: *const expression.Expression,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
/// Parses a variable binding /// 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!void { pub fn init(target: *VariableBinding, tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!usize {
std.debug.assert(pos < tokens.items.len); std.debug.assert(pos < tokens.items.len);
// try to parse a var keyword // try to parse a var keyword
@ -42,9 +42,8 @@ pub const VariableBinding = struct {
// parse expression // parse expression
if (pos + 3 >= tokens.items.len) return ParseError.Error; if (pos + 3 >= tokens.items.len) return ParseError.Error;
var exp = try allocator.create(expression.Expression);
errdefer allocator.destroy(exp); const exp = expression.Expression.init(tokens, pos + 3) catch {
exp.init(tokens, pos + 3) catch {
return ParseError.Error; return ParseError.Error;
}; };
@ -53,13 +52,15 @@ pub const VariableBinding = struct {
.is_mutable = true, .is_mutable = true,
.datatype = null, .datatype = null,
.identifier = identifier, .identifier = identifier,
.expression = exp, .expression = &exp,
.alloc = allocator, .alloc = allocator,
}; };
// TODO: when expression parses more than one token this will break.
return pos + 4;
} }
pub fn deinit(self: *VariableBinding) void { 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(); defer tokens.deinit();
var binding: VariableBinding = undefined; var binding: VariableBinding = undefined;
try binding.init(&tokens, 0, std.testing.allocator); _ = try binding.init(&tokens, 0, std.testing.allocator);
defer binding.deinit(); defer binding.deinit();
try std.testing.expectEqual(true, binding.is_mutable); try std.testing.expectEqual(true, binding.is_mutable);
@ -81,7 +82,7 @@ test "should fail is it doesnt start with var" {
defer tokens.deinit(); defer tokens.deinit();
var binding: VariableBinding = undefined; 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); try std.testing.expectEqual(ParseError.Unmatched, err);
return; return;
}; };
@ -95,7 +96,7 @@ test "should fail if the idenfier is missing" {
defer tokens.deinit(); defer tokens.deinit();
var binding: VariableBinding = undefined; 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); try std.testing.expectEqual(ParseError.Error, err);
return; return;
}; };
@ -109,7 +110,7 @@ test "should fail if there is not an identifier after var" {
defer tokens.deinit(); defer tokens.deinit();
var binding: VariableBinding = undefined; 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); try std.testing.expectEqual(ParseError.Error, err);
return; return;
}; };
@ -123,7 +124,7 @@ test "should fail if the equal sign is missing" {
defer tokens.deinit(); defer tokens.deinit();
var binding: VariableBinding = undefined; 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); try std.testing.expectEqual(ParseError.Error, err);
return; return;
}; };
@ -137,7 +138,7 @@ test "should fail if the equal sign is not found" {
defer tokens.deinit(); defer tokens.deinit();
var binding: VariableBinding = undefined; 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); try std.testing.expectEqual(ParseError.Error, err);
return; return;
}; };
@ -151,7 +152,7 @@ test "should fail if the expression parsing fails" {
defer tokens.deinit(); defer tokens.deinit();
var binding: VariableBinding = undefined; 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); try std.testing.expectEqual(ParseError.Error, err);
return; return;
}; };