hare

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

commit 13c16e6a44e8d94308cb10880f73f0627270aec3
parent 829c2a08476a94f9784dd04cae9e796642f1d082
Author: Alexey Yerin <yyp@disroot.org>
Date:   Sat, 19 Aug 2023 17:33:03 +0300

hare::unparse: Wrap expressions in parentheses where needed

Prevents unparse from generating invalid code.

Signed-off-by: Alexey Yerin <yyp@disroot.org>

Diffstat:
Mhare/parse/+test/expr_test.ha | 33+++++++++++++++++++++++++++++++++
Mhare/unparse/expr.ha | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 211 insertions(+), 8 deletions(-)

diff --git a/hare/parse/+test/expr_test.ha b/hare/parse/+test/expr_test.ha @@ -305,3 +305,36 @@ "export fn main() void = yield :foo;\n\n" "export fn main() void = yield :foo, void;\n"); }; + +@test fn parenthesis() void = { + roundtrip( + "export fn main() void = -((2 + 2) * 2);\n\n" + "export fn main() void = &(1: uint);\n\n" + "export fn main() void = **x;\n\n" + "export fn main() void = *alloc(2);\n\n" + "export fn main() void = &(&x);\n\n" + "export fn main() void = &*&*&x;\n\n" + "export fn main() void = x: int: uint: u8;\n\n" + "export fn main() void = &array[idx];\n\n" + "export fn main() void = &array[idx..];\n\n" + "export fn main() void = (array: *[*]u8)[idx];\n\n" + "export fn main() void = (s: *object).field;\n\n" + "export fn main() void = (s: *object).0;\n\n" + "export fn main() void = &offset(header.x);\n\n" + "export fn main() void = *((a + b): uintptr);\n\n" + "export fn main() void = *value();\n\n" + "export fn main() void = (ptr: function)();\n\n" + "export fn main() void = func()?.field;\n\n" + "export fn main() void = (x: thing)?;\n\n" + "export fn main() void = (x: thing)!;\n\n" + "export fn main() void = (hey: *[*]u8)[1..2];\n\n" + "export fn main() void = (hey: *[*]u8)[0];\n\n" + "export fn main() void = &{\n" + "\t" "yield 12;\n" + "};\n\n" + "export fn main() void = ({\n" + "\t" "yield err;\n" + "})!;\n\n" + "export fn main() void = &(if (true) 1 else 2);\n\n" + "export fn main() void = (a + b): uintptr: size;\n"); +}; diff --git a/hare/unparse/expr.ha b/hare/unparse/expr.ha @@ -23,17 +23,41 @@ export fn expr( case let id: ast::access_identifier => return ident(out, id); case let ix: ast::access_index => - let z = expr(out, indent, *ix.object)?; + let z = 0z; + const needs_parens = !is_postfix(ix.object); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *ix.object)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprintf(out, "[")?; z += expr(out, indent, *ix.index)?; z += fmt::fprintf(out, "]")?; return z; case let fi: ast::access_field => - let z = expr(out, indent, *fi.object)?; + let z = 0z; + const needs_parens = !is_postfix(fi.object); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *fi.object)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprintf(out, ".{}", fi.field)?; return z; case let tp: ast::access_tuple => - let z = expr(out, indent, *tp.object)?; + let z = 0z; + const needs_parens = !is_postfix(tp.object); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *tp.object)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprintf(out, ".")?; z += expr(out, indent, *tp.value)?; return z; @@ -240,7 +264,15 @@ export fn expr( }; return z; case let e: ast::call_expr => - let z = expr(out, indent, *e.lvalue)?; + let z = 0z; + const needs_parens = !is_postfix(e.lvalue); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *e.lvalue)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprintf(out, "(")?; for (let i = 0z; i < len(e.args); i += 1) { z += expr(out, indent, *e.args[i])?; @@ -254,7 +286,15 @@ export fn expr( z += fmt::fprintf(out, ")")?; return z; case let e: ast::cast_expr => - let z = expr(out, indent, *e.value)?; + let z = 0z; + const needs_parens = !is_cast(e.value); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *e.value)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; const op = switch (e.kind) { case ast::cast_kind::CAST => yield ": "; @@ -283,7 +323,15 @@ export fn expr( z += fmt::fprint(out, ")")?; return z; case let e: ast::error_assert_expr => - let z = expr(out, indent, *e)?; + let z = 0z; + const needs_parens = !is_postfix(e); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *e)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprint(out, "!")?; return z; case let e: ast::for_expr => @@ -358,7 +406,15 @@ export fn expr( z += fmt::fprint(out, ")")?; return z; case let e: ast::propagate_expr => - let z = expr(out, indent, *e)?; + let z = 0z; + const needs_parens = !is_postfix(e); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *e)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprint(out, "?")?; return z; case let e: ast::return_expr => @@ -371,7 +427,15 @@ export fn expr( }; return z; case let e: ast::slice_expr => - let z = expr(out, indent, *e.object)?; + let z = 0z; + const needs_parens = !is_postfix(e.object); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; + z += expr(out, indent, *e.object)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; z += fmt::fprint(out, "[")?; match (e.start) { case null => void; @@ -401,7 +465,20 @@ export fn expr( case ast::unarithm_op::MINUS => yield "-"; })?; + const needs_parens = match (e.operand.expr) { + case let inner: ast::unarithm_expr => + yield e.op == ast::unarithm_op::ADDR + && inner.op == e.op; + case => + yield !is_unary(e.operand); + }; + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; z += expr(out, indent, *e.operand)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; return z; case let e: ast::variadic_expr => match (e) { @@ -479,7 +556,14 @@ fn binexprval( }; case => void; }; + const needs_parens = !is_cast(&e) && !(e.expr is ast::binarithm_expr); + if (needs_parens) { + z += fmt::fprint(out, "(")?; + }; z += expr(out, indent, e)?; + if (needs_parens) { + z += fmt::fprint(out, ")")?; + }; return z; }; @@ -745,3 +829,89 @@ fn case_exprs( return z; }; + +fn is_plain(e: *ast::expr) bool = { + match (e.expr) { + case ast::constant_expr => + return true; + case ast::access_identifier => + return true; + case => + return false; + }; +}; + +fn is_postfix(e: *ast::expr) bool = { + if (is_plain(e)) { + return true; + }; + + match (e.expr) { + case ast::call_expr => + return true; + case ast::access_expr => + return true; + case ast::slice_expr => + return true; + case ast::error_assert_expr => + return true; + case ast::propagate_expr => + return true; + case => + return false; + }; +}; + +fn is_builtin(e: *ast::expr) bool = { + if (is_postfix(e)) { + return true; + }; + + match (e.expr) { + case ast::alloc_expr => + return true; + case ast::assert_expr => + return true; + case ast::variadic_expr => + return true; + // measurement-expression + case ast::len_expr => + return true; + case ast::align_expr => + return true; + case ast::size_expr => + return true; + case ast::offset_expr => + return true; + // slice-mutation-expression + case ast::append_expr => + return true; + case ast::insert_expr => + return true; + case => + return false; + }; +}; + +fn is_unary(e: *ast::expr) bool = { + if (is_builtin(e)) { + return true; + }; + + match (e.expr) { + case ast::compound_expr => + return true; + case ast::match_expr => + return true; + case ast::switch_expr => + return true; + case ast::unarithm_expr => + return true; + case => + return false; + }; +}; + +fn is_cast(e: *ast::expr) bool = { + return is_unary(e) || (e.expr is ast::cast_expr); +};