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:
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;
+};