Compare commits

...

2 Commits

Author SHA1 Message Date
73268f59b5 implement a very rough translator from s-expressions to AST
All checks were successful
Run unit tests / zig build test (push) Successful in 47s
2026-01-07 01:03:52 +01:00
8f5a3e00b8 clean up renaming 2026-01-07 00:32:53 +01:00
2 changed files with 128 additions and 12 deletions

View File

@@ -41,9 +41,92 @@ pub const SExpr = union(enum) {
pub fn format(self: @This(), w: *std.Io.Writer) !void { pub fn format(self: @This(), w: *std.Io.Writer) !void {
try self.format_(w, 0); try self.format_(w, 0);
} }
pub fn parse_stmt(self: @This(), gpa: std.mem.Allocator) !Stmt {
switch (self) {
.call => |ss| {
if (ss.len == 0) {
return error.EmptyCall;
}
const name = ss[0].identifier;
if (std.mem.eql(u8, name, "func")) {
if (ss.len != 4 or ss[1] != .identifier or ss[2] != .list or ss[3] != .block) return error.InvalidFunc;
const func_name = ss[1].identifier;
var args = std.ArrayList([]const u8).empty;
for (ss[2].list) |arg| {
if (arg != .identifier) return error.InvalidFuncArg;
try args.append(gpa, arg.identifier);
}
var body = std.ArrayList(Stmt).empty;
for (ss[3].block) |sexpr| {
try body.append(gpa, try sexpr.parse_stmt(gpa));
}
return .{ .func_decl = .{
.name = func_name,
.args = try args.toOwnedSlice(gpa),
.body = try body.toOwnedSlice(gpa),
} };
} else if (std.mem.eql(u8, name, "let")) {
if (ss.len != 3 or ss[1] != .identifier) return error.InvalidLet;
const var_name = ss[1].identifier;
return .{ .var_let = .{
.name = var_name,
.value = try ss[2].parse_expr(gpa),
} };
} else if (std.mem.eql(u8, name, "set")) {
if (ss.len != 3 or ss[1] != .identifier) return error.InvalidLet;
const var_name = ss[1].identifier;
return .{ .var_set = .{
.name = var_name,
.value = try ss[2].parse_expr(gpa),
} };
} else if (std.mem.eql(u8, name, "while")) {
if (ss.len != 3 or ss[2] != .block) return error.InvalidWhile;
const cond = try ss[1].parse_expr(gpa);
var body = std.ArrayList(Stmt).empty;
for (ss[2].block) |sexpr| {
try body.append(gpa, try sexpr.parse_stmt(gpa));
}
return .{ .while_loop = .{
.cond = cond,
.body = try body.toOwnedSlice(gpa),
} };
} else {
return .{ .expr = try self.parse_expr(gpa) };
}
},
else => return .{ .expr = try self.parse_expr(gpa) },
}
}
pub fn parse_expr(self: @This(), gpa: std.mem.Allocator) !Expr {
switch (self) {
.integer => |int| return .{ .integer = int },
.identifier => |id| {
if (std.mem.eql(u8, id, "nil")) {
return .nil;
}
return .{ .variable = id };
},
.string => |str| return .{ .string = str },
.call => |ss| {
if (ss.len < 1 or ss[0] != .identifier) return error.InvalidCall;
var args = std.ArrayList(Expr).empty;
for (ss[1..]) |sexpr| {
try args.append(gpa, try sexpr.parse_expr(gpa));
}
return .{ .func_call = .{
.name = ss[0].identifier,
.args = try args.toOwnedSlice(gpa),
} };
},
.block => return error.BlockAsExpr,
.list => return error.ListsNotImplemented,
}
}
}; };
fn parse_expr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SExpr, usize } { fn parse_sexpr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SExpr, usize } {
if (tokens.len == 0) { if (tokens.len == 0) {
return error.UnexpectedEOF; return error.UnexpectedEOF;
} }
@@ -56,8 +139,8 @@ fn parse_expr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SEx
var call = std.ArrayList(SExpr).empty; var call = std.ArrayList(SExpr).empty;
var tokens_rest = tokens[1..]; var tokens_rest = tokens[1..];
while (tokens_rest.len > 0 and tokens_rest[0] != .rparen) { while (tokens_rest.len > 0 and tokens_rest[0] != .rparen) {
const expr, const consumed = try parse_expr(gpa, tokens_rest); const sexpr, const consumed = try parse_sexpr(gpa, tokens_rest);
try call.append(gpa, expr); try call.append(gpa, sexpr);
tokens_rest = tokens_rest[consumed..]; tokens_rest = tokens_rest[consumed..];
} }
if (tokens_rest.len == 0) { if (tokens_rest.len == 0) {
@@ -70,8 +153,8 @@ fn parse_expr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SEx
var block = std.ArrayList(SExpr).empty; var block = std.ArrayList(SExpr).empty;
var tokens_rest = tokens[1..]; var tokens_rest = tokens[1..];
while (tokens_rest.len > 0 and tokens_rest[0] != .rbrace) { while (tokens_rest.len > 0 and tokens_rest[0] != .rbrace) {
const expr, const consumed = try parse_expr(gpa, tokens_rest); const sexpr, const consumed = try parse_sexpr(gpa, tokens_rest);
try block.append(gpa, expr); try block.append(gpa, sexpr);
tokens_rest = tokens_rest[consumed..]; tokens_rest = tokens_rest[consumed..];
} }
if (tokens_rest.len == 0) { if (tokens_rest.len == 0) {
@@ -84,8 +167,8 @@ fn parse_expr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SEx
var list = std.ArrayList(SExpr).empty; var list = std.ArrayList(SExpr).empty;
var tokens_rest = tokens[1..]; var tokens_rest = tokens[1..];
while (tokens_rest.len > 0 and tokens_rest[0] != .rbracket) { while (tokens_rest.len > 0 and tokens_rest[0] != .rbracket) {
const expr, const consumed = try parse_expr(gpa, tokens_rest); const sexpr, const consumed = try parse_sexpr(gpa, tokens_rest);
try list.append(gpa, expr); try list.append(gpa, sexpr);
tokens_rest = tokens_rest[consumed..]; tokens_rest = tokens_rest[consumed..];
} }
if (tokens_rest.len == 0) { if (tokens_rest.len == 0) {
@@ -105,10 +188,42 @@ pub fn parse_sexprs(gpa: std.mem.Allocator, tokens: []const lexer.Token) ![]cons
var idx: usize = 0; var idx: usize = 0;
while (idx < tokens.len) { while (idx < tokens.len) {
const expr, const consumed = try parse_expr(gpa, tokens[idx..]); const sexpr, const consumed = try parse_sexpr(gpa, tokens[idx..]);
try result.append(gpa, expr); try result.append(gpa, sexpr);
idx += consumed; idx += consumed;
} }
return result.toOwnedSlice(gpa); return result.toOwnedSlice(gpa);
} }
pub const Stmt = union(enum) {
func_decl: struct {
name: []const u8,
args: []const []const u8,
body: []const Stmt,
},
var_let: struct {
name: []const u8,
value: Expr,
},
var_set: struct {
name: []const u8,
value: Expr,
},
while_loop: struct {
cond: Expr,
body: []const Stmt,
},
expr: Expr,
};
pub const Expr = union(enum) {
nil,
variable: []const u8,
integer: u64,
string: []const u8,
func_call: struct {
name: []const u8,
args: []const Expr,
},
};

View File

@@ -41,10 +41,11 @@ fn entry(init: struct {
const tokens = try lib.lexer.lex(init.arena.allocator(), src); const tokens = try lib.lexer.lex(init.arena.allocator(), src);
const exprs = try lib.parser.parse_sexprs(init.arena.allocator(), tokens); const sexprs = try lib.parser.parse_sexprs(init.arena.allocator(), tokens);
for (exprs) |expr| { for (sexprs) |sexpr| {
try init.stdout.print("{f}", .{expr}); const stmt = try sexpr.parse_stmt(init.arena.allocator());
try init.stdout.print("{any}\n", .{stmt});
try init.stdout.flush(); try init.stdout.flush();
} }
} }