hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 2300f0693021d3541fa9a5f81ab9d9af57212006
parent 01c9d4e72991bbd391a29a146fc30f469141deb3
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 18 Apr 2021 09:28:17 -0400

hare::parse: implement switch expressions

Diffstat:
Mhare/ast/expr.ha | 2+-
Mhare/parse/+test/expr.ha | 13+++++++++++++
Mhare/parse/expr.ha | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mhare/unparse/expr.ha | 37++++++++++++++++++++++++++++++++++++-
4 files changed, 107 insertions(+), 9 deletions(-)

diff --git a/hare/ast/expr.ha b/hare/ast/expr.ha @@ -402,7 +402,7 @@ export fn expr_free(e: (expr | nullable *expr)) void = match (e) { for (let i = 0z; i < len(s.cases); i += 1) { let opts = s.cases[i].options; for (let j = 0z; j < len(opts); j += 1) { - expr_free(opts[i]); + expr_free(opts[j]); }; free(opts); expr_free(s.cases[i].value); diff --git a/hare/parse/+test/expr.ha b/hare/parse/+test/expr.ha @@ -114,6 +114,19 @@ "export fn main() void = x[..321];\n"); }; +@test fn switch_expr() void = { + roundtrip("export fn main() void = { + switch (x) { + 1234, 4321 => y, + 1337 => { + z; + }, + * => q, + }; +}; +"); +}; + @test fn unarithm() void = { roundtrip("export fn main() void = +void;\n" "export fn main() void = -void;\n" diff --git a/hare/parse/expr.ha b/hare/parse/expr.ha @@ -14,7 +14,7 @@ export fn expression(lexer: *lex::lexer) (ast::expr | error) = { return switch (tok.0) { ltok::LBRACE => expression_list(lexer), ltok::MATCH => abort(), // TODO - ltok::SWITCH => abort(), // TODO + ltok::SWITCH => switch_expr(lexer), ltok::IF => if_expr(lexer), ltok::LABEL, ltok::FOR => for_expr(lexer), ltok::BREAK, ltok::CONTINUE, ltok::RETURN => control(lexer), @@ -238,8 +238,8 @@ fn expression_list(lexer: *lex::lexer) (ast::expr | error) = { }; fn for_expr(lexer: *lex::lexer) (ast::expr | error) = { - let tok = want(lexer, ltok::FOR, ltok::LABEL)?; - let label: ast::label = switch (tok.0) { + const tok = want(lexer, ltok::FOR, ltok::LABEL)?; + const label: ast::label = switch (tok.0) { ltok::LABEL => { want(lexer, ltok::FOR)?; tok.1 as str; @@ -249,19 +249,19 @@ fn for_expr(lexer: *lex::lexer) (ast::expr | error) = { want(lexer, ltok::LPAREN)?; - let bindings: nullable *ast::expr = match (peek( + const bindings: nullable *ast::expr = match (peek( lexer, ltok::LET, ltok::CONST)?) { void => null, lex::token => { - let bindings = alloc(binding(lexer, false)?); + const bindings = alloc(binding(lexer, false)?); want(lexer, ltok::SEMICOLON)?; bindings; }, }; - let cond = alloc(expression(lexer)?); + const cond = alloc(expression(lexer)?); - let afterthought: nullable *ast::expr = match (peek( + const afterthought: nullable *ast::expr = match (peek( lexer, ltok::SEMICOLON)) { void => null, lex::token => { @@ -472,6 +472,56 @@ fn postfix_dot(lexer: *lex::lexer, lvalue: ast::expr) (ast::expr | error) = { }; }; +fn switch_expr(lexer: *lex::lexer) (ast::expr | error) = { + const start = want(lexer, ltok::SWITCH)?; + + want(lexer, ltok::LPAREN)?; + const value = expression(lexer)?; + want(lexer, ltok::RPAREN)?; + + want(lexer, ltok::LBRACE)?; + + let cases: []ast::switch_case = []; + for (true) { + if (try(lexer, ltok::RBRACE)? is lex::token) break; + + let opts: []*ast::expr = []; + if (try(lexer, ltok::TIMES)? is lex::token) { + want(lexer, ltok::CASE)?; + } else { + for (true) { + if (try(lexer, ltok::CASE) is lex::token) break; + + append(opts, alloc(expression(lexer)?)); + + if (!(try(lexer, ltok::COMMA) is lex::token)) { + want(lexer, ltok::CASE)?; + break; + }; + }; + synassert(mkloc(lexer), len(opts) != 0, + "Expected a list of options")?; + }; + + append(cases, ast::switch_case { + options = opts, + value = alloc(expression(lexer)?), + }); + + if (!(try(lexer, ltok::COMMA) is lex::token)) { + want(lexer, ltok::RBRACE)?; + break; + }; + }; + + synassert(start.2, len(cases) != 0, "Expected a list of switch cases")?; + + return ast::switch_expr { + value = alloc(value), + cases = cases, + }; +}; + fn unarithm(lexer: *lex::lexer) (ast::expr | error) = { const tok = match (try(lexer, ltok::PLUS, ltok::MINUS, ltok::BNOT, diff --git a/hare/unparse/expr.ha b/hare/unparse/expr.ha @@ -184,7 +184,7 @@ export fn expr( z += fmt::fprint(out, "]")?; z; }, - e: ast::switch_expr => abort(), + e: ast::switch_expr => switch_expr(out, indent, e)?, e: ast::unarithm_expr => { let z = fmt::fprintf(out, "{}", switch (e.op) { ast::unarithm_op::ADDR => "&", @@ -265,3 +265,38 @@ fn for_expr( z += fmt::fprintf(out, ") ")?; return z + expr(out, indent, *e.body)?; }; + +fn switch_expr( + out: *io::stream, + indent: size, + e: ast::switch_expr, +) (size | io::error) = { + let z = fmt::fprint(out, "switch (")?; + z += expr(out, indent, *e.value)?; + z += fmt::fprint(out, ") {")?; + indent += 1; + + for (let i = 0z; i < len(e.cases); i += 1) { + z += newline(out, indent)?; + const case = e.cases[i]; + if (len(case.options) == 0) { + z += fmt::fprint(out, "* => ")?; + } else { + for (let j = 0z; j < len(case.options); j += 1) { + const opt = case.options[j]; + z += expr(out, indent, *opt)?; + if (j + 1 < len(case.options)) { + z += fmt::fprint(out, ", ")?; + }; + }; + z += fmt::fprint(out, " => ")?; + }; + z += expr(out, indent, *case.value)?; + z += fmt::fprint(out, ",")?; + }; + + indent -= 1; + z += newline(out, indent)?; + z += fmt::fprint(out, "}")?; + return z; +};