Compare commits

..

2 Commits

Author SHA1 Message Date
41d36ce8e1 fix markdown lint (not important)
All checks were successful
Run unit tests / zig build test (push) Successful in 55s
2026-01-07 10:39:20 +01:00
083f5c4016 the name is officially a backronym now lol
All checks were successful
Run unit tests / zig build test (push) Successful in 48s
2026-01-07 10:33:15 +01:00
4 changed files with 23 additions and 131 deletions

3
.markdownlint.json Normal file
View File

@@ -0,0 +1,3 @@
{
"MD033": false
}

View File

@@ -1,15 +1,20 @@
# neon
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![test workflow](https://git.svecs.cz/svecs/neon/actions/workflows/test.yml/badge.svg?branch=main)](https://git.svecs.cz/svecs/neon/actions/?workflow=test.yml)
<small>(stands for Not Everything Oughtta've a Name)</small>
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![test workflow](https://git.svecs.cz/svecs/neon/actions/workflows/test.yml/badge.svg?branch=main)](https://git.svecs.cz/svecs/neon/actions/?workflow=test.yml)
> a simple programming language (hopefully one day)
## features
> [!WARNING]
> this project is still in very early development, so features are limited and subject to change
> this project is still in very early development, so features are limited and
> subject to change
>
> for now the language uses an s-expression syntax (even though it's not a lisp) to simplify parsing. this syntax WILL change in the future!
> for now the language uses an s-expression syntax (even though it's not a lisp)
> to simplify parsing. this syntax WILL change in the future!
- currently none lol

View File

@@ -41,92 +41,9 @@ pub const SExpr = union(enum) {
pub fn format(self: @This(), w: *std.Io.Writer) !void {
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_sexpr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SExpr, usize } {
fn parse_expr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SExpr, usize } {
if (tokens.len == 0) {
return error.UnexpectedEOF;
}
@@ -139,8 +56,8 @@ fn parse_sexpr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SE
var call = std.ArrayList(SExpr).empty;
var tokens_rest = tokens[1..];
while (tokens_rest.len > 0 and tokens_rest[0] != .rparen) {
const sexpr, const consumed = try parse_sexpr(gpa, tokens_rest);
try call.append(gpa, sexpr);
const expr, const consumed = try parse_expr(gpa, tokens_rest);
try call.append(gpa, expr);
tokens_rest = tokens_rest[consumed..];
}
if (tokens_rest.len == 0) {
@@ -153,8 +70,8 @@ fn parse_sexpr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SE
var block = std.ArrayList(SExpr).empty;
var tokens_rest = tokens[1..];
while (tokens_rest.len > 0 and tokens_rest[0] != .rbrace) {
const sexpr, const consumed = try parse_sexpr(gpa, tokens_rest);
try block.append(gpa, sexpr);
const expr, const consumed = try parse_expr(gpa, tokens_rest);
try block.append(gpa, expr);
tokens_rest = tokens_rest[consumed..];
}
if (tokens_rest.len == 0) {
@@ -167,8 +84,8 @@ fn parse_sexpr(gpa: std.mem.Allocator, tokens: []const lexer.Token) !struct { SE
var list = std.ArrayList(SExpr).empty;
var tokens_rest = tokens[1..];
while (tokens_rest.len > 0 and tokens_rest[0] != .rbracket) {
const sexpr, const consumed = try parse_sexpr(gpa, tokens_rest);
try list.append(gpa, sexpr);
const expr, const consumed = try parse_expr(gpa, tokens_rest);
try list.append(gpa, expr);
tokens_rest = tokens_rest[consumed..];
}
if (tokens_rest.len == 0) {
@@ -188,42 +105,10 @@ pub fn parse_sexprs(gpa: std.mem.Allocator, tokens: []const lexer.Token) ![]cons
var idx: usize = 0;
while (idx < tokens.len) {
const sexpr, const consumed = try parse_sexpr(gpa, tokens[idx..]);
try result.append(gpa, sexpr);
const expr, const consumed = try parse_expr(gpa, tokens[idx..]);
try result.append(gpa, expr);
idx += consumed;
}
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,11 +41,10 @@ fn entry(init: struct {
const tokens = try lib.lexer.lex(init.arena.allocator(), src);
const sexprs = try lib.parser.parse_sexprs(init.arena.allocator(), tokens);
const exprs = try lib.parser.parse_sexprs(init.arena.allocator(), tokens);
for (sexprs) |sexpr| {
const stmt = try sexpr.parse_stmt(init.arena.allocator());
try init.stdout.print("{any}\n", .{stmt});
for (exprs) |expr| {
try init.stdout.print("{f}", .{expr});
try init.stdout.flush();
}
}