zpy

A lisp like language written in hare
Log | Files | Refs

commit 9a29a949ccbb897b060cafbb8c3f07de744d3731
parent de964f635d782f9c6ad51eb14c87d1f4e54e3f76
Author: thing1 <thing1@seacrossedlovers.xyz>
Date:   Tue,  5 May 2026 17:07:43 +0100

init parser

Diffstat:
Mzpy/lex/+test.ha | 54+++++++++---------------------------------------------
Azpy/parse/+test.ha | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Azpy/parse/ast.ha | 10++++++++++
Azpy/parse/help.ha | 9+++++++++
Azpy/parse/parse.ha | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 142 insertions(+), 45 deletions(-)

diff --git a/zpy/lex/+test.ha b/zpy/lex/+test.ha @@ -18,55 +18,35 @@ fn check(lex: *lexer, expect: [](types | invalid)) void = { @test fn NoArgExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("(foo)")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("(foo)"))); check(lex, [types::OBRACE, types::NAME, types::CBRACE, types::EOF]); }; @test fn HasArgsExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("(foo 1 2 3)")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("(foo 1 2 3)"))); check(lex, [types::OBRACE, types::NAME, types::NUM, types::NUM, types::NUM, types::CBRACE, types::EOF]); }; @test fn NestedArgsExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("(foo (bar 1 2) 3)")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("(foo (bar 1 2) 3)"))); check(lex, [types::OBRACE, types::NAME, types::OBRACE, types::NAME, types::NUM, types::NUM, types::CBRACE, types::NUM, types::CBRACE, types::EOF]); }; @test fn NameNumExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("(foo bar 1)")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("(foo bar 1)"))); check(lex, [types::OBRACE, types::NAME, types::NAME, types::NUM, types::CBRACE, types::EOF]); }; @test fn NumNameExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("(foo 1 bar)")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("(foo 1 bar)"))); check(lex, [types::OBRACE, types::NAME, types::NUM, types::NAME, types::CBRACE, types::EOF]); }; @@ -76,44 +56,28 @@ fn NumNameExpr() void = { @test fn EmptyExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8(""))); check(lex, [invalid]); }; @test fn SingleOBraceExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("(")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("("))); check(lex, [types::OBRACE, invalid]); }; @test fn SingleCBraceExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8(")")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8(")"))); check(lex, [invalid]); }; @test fn SingleNameExpr() void = { - let lex = &lexer{ - in = &memio::fixed(strings::toutf8("foo")), - nexts = [types::OBRACE], - items = items - }; + let lex = &new(&memio::fixed(strings::toutf8("foo"))); check(lex, [invalid]); }; diff --git a/zpy/parse/+test.ha b/zpy/parse/+test.ha @@ -0,0 +1,51 @@ +use zpy::lex; +use memio; +use strings; + +@test +fn BinaryExpr() void = { + let lex = &lex::new(&memio::fixed(strings::toutf8("(foo 1 2)"))); + + assert(((try(lex, [&parseSexpr]) as node) as sexpr).func == "foo"); + assert(len(((try(lex, [&parseSexpr]) as node) as sexpr).args) == 2); + assert((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[0]) as lit) == 1); + assert((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[1]) as lit) == 2); +}; + +@test +fn NoArgExpr() void = { + let lex = &lex::new(&memio::fixed(strings::toutf8("(foo)"))); + + assert(((try(lex, [&parseSexpr]) as node) as sexpr).func == "foo"); + assert(len(((try(lex, [&parseSexpr]) as node) as sexpr).args) == 0); +}; + +@test +fn ExprWithName() void = { + let lex = &lex::new(&memio::fixed(strings::toutf8("(foo bar)"))); + + assert(((try(lex, [&parseSexpr]) as node) as sexpr).func == "foo"); + assert(len(((try(lex, [&parseSexpr]) as node) as sexpr).args) == 1); + assert((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[0]) as name) == "bar"); +}; + +@test +fn ExprWithNameAndNum() void = { + let lex = &lex::new(&memio::fixed(strings::toutf8("(foo bar 1)"))); + + assert(((try(lex, [&parseSexpr]) as node) as sexpr).func == "foo"); + assert(len(((try(lex, [&parseSexpr]) as node) as sexpr).args) == 2); + assert((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[0]) as name) == "bar"); + assert((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[1]) as lit) == 1); +}; + +@test +fn NestedExpr() void = { + let lex = &lex::new(&memio::fixed(strings::toutf8("(foo (bar 1))"))); + + assert(((try(lex, [&parseSexpr]) as node) as sexpr).func == "foo"); + assert(len(((try(lex, [&parseSexpr]) as node) as sexpr).args) == 1); + assert((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[0]) as sexpr).func == "bar"); + assert(len((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[0]) as sexpr).args) == 1); + assert((*((*(((try(lex, [&parseSexpr]) as node) as sexpr).args[0]) as sexpr).args[0]) as lit) == 1); +}; diff --git a/zpy/parse/ast.ha b/zpy/parse/ast.ha @@ -0,0 +1,10 @@ +export type node = (sexpr | lit | name); + +export type sexpr = struct { + func: str, + args: []*node +}; + +export type lit = u64; + +export type name = str; diff --git a/zpy/parse/help.ha b/zpy/parse/help.ha @@ -0,0 +1,9 @@ +use zpy::lex; + +fn want(l: *lex::lexer, want: []lex::types) (lex::token | lex::error | error) = { + let n = lex::next(l)?; + for (let ty .. want) { + if (n.ty == ty) return n; + }; + abort(); +}; diff --git a/zpy/parse/parse.ha b/zpy/parse/parse.ha @@ -0,0 +1,63 @@ +use zpy::lex; +use io; +use strconv; + +export type error = !int; + +export type parserfn = fn(_: *lex::lexer) (node | error); + +export fn try(l: *lex::lexer, fs: []*parserfn) (node | error) = { + let errors: []error = []; + + for (let f .. fs) { + let save = io::tell(l.in)!; + + return match (f(l)) { + case let n: node => return n; + case let e: error => + append(errors, e)!; + io::seek(l.in, save, io::whence::SET)!; + continue; + }; + }; + + abort(); +}; + +export fn parseSexpr(l: *lex::lexer) (node | error) = { + match (want(l, [lex::types::OBRACE])) { + case lex::token => yield; + case => abort(); + }; + + let name = try(l, [&parseName])?; + + let args: []*node = []; + + for (true) { + match (try(l, [&parseName, &parseLit, &parseSexpr])) { + case let n: node => append(args, &n)!; + case let e: error => + match (want(l, [lex::types::CBRACE])) { + case lex::token => return sexpr{func = name: str, args = args}; + case => return e; + }; + }; + }; +}; + +export fn parseName(l: *lex::lexer) (node | error) = { + match (want(l, [lex::types::NAME])) { + case let t: lex::token => return t.data; + case => abort(); + }; +}; + +export fn parseLit(l: *lex::lexer) (node | error) = { + match (want(l, [lex::types::NUM])) { + case let t: lex::token => return strconv::stou64(t.data)!; + case => abort(); + }; +}; + +