hare

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

commit 5bbbaac50cd70115f8c5cda9ef257fdff5c14ba5
parent d69fc2db25622b16c1f873a647b5d3ccfc1ba0e2
Author: Sebastian <sebastian@sebsite.pw>
Date:   Thu, 17 Feb 2022 20:18:44 -0500

unparse: add parantheses when needed to show precedence

Parentheses are only added when strictly required -- that is -- when an
operand to a binary arithmetic expression has a higher precedence than
the expression itself.

Fixes: https://todo.sr.ht/~sircmpwn/hare/383
Signed-off-by: Sebastian <sebastian@sebsite.pw>

Diffstat:
Mhare/parse/+test/expr.ha | 7+------
Mhare/unparse/expr.ha | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
2 files changed, 87 insertions(+), 45 deletions(-)

diff --git a/hare/parse/+test/expr.ha b/hare/parse/+test/expr.ha @@ -85,13 +85,8 @@ }; @test fn constant() void = { - // XXX: nested-expression should probably be tested here, but it - // presents a problem for round tripping because it does not end up in - // the AST. We would need unparse to be able to tell that a given AST - // does not match the precedence requirements, and thus infer that - // parenthesis are needed to unparse it correctly. roundtrip("export fn main() void = { - 2 + -4 + void + true + \"hello\" + '?'; + 2 + (-4 + void) * true / (\"hello\" << '?'); [1, 2, 3, 4]; [1, 2, 3, 4...]; (1, 2, 3); diff --git a/hare/unparse/expr.ha b/hare/unparse/expr.ha @@ -1,14 +1,11 @@ use io; use fmt; use hare::ast; +use hare::ast::{binarithm_op}; use hare::lex::{ltok}; use hare::lex; // Unparses an [[ast::expr]]. -// -// A known limitation of the current implementation is that precedence between -// binary operators (e.g. +) is not accounted for, so such expressions may -// produce a different AST if parsed again. export fn expr( out: io::handle, indent: size, @@ -104,33 +101,33 @@ export fn expr( const op = match (e.op) { case void => yield "="; - case let op: ast::binarithm_op => + case let op: binarithm_op => yield switch (op) { - case ast::binarithm_op::BAND => + case binarithm_op::BAND => yield "&="; - case ast::binarithm_op::LAND => + case binarithm_op::LAND => yield "&&="; - case ast::binarithm_op::BOR => + case binarithm_op::BOR => yield "|="; - case ast::binarithm_op::LOR => + case binarithm_op::LOR => yield "||="; - case ast::binarithm_op::DIV => + case binarithm_op::DIV => yield "/="; - case ast::binarithm_op::LSHIFT => + case binarithm_op::LSHIFT => yield "<<="; - case ast::binarithm_op::MINUS => + case binarithm_op::MINUS => yield "-="; - case ast::binarithm_op::MODULO => + case binarithm_op::MODULO => yield "%="; - case ast::binarithm_op::PLUS => + case binarithm_op::PLUS => yield "+="; - case ast::binarithm_op::RSHIFT => + case binarithm_op::RSHIFT => yield ">>="; - case ast::binarithm_op::TIMES => + case binarithm_op::TIMES => yield "*="; - case ast::binarithm_op::BXOR => + case binarithm_op::BXOR => yield "^="; - case ast::binarithm_op::LXOR => + case binarithm_op::LXOR => yield "^^="; }; }; @@ -138,48 +135,49 @@ export fn expr( z += expr(out, indent, *e.value)?; return z; case let e: ast::binarithm_expr => - let z = expr(out, indent, *e.lvalue)?; + const prec = binprecedence(e.op); + let z = binexprval(out, indent, *e.lvalue, prec)?; z += fmt::fprintf(out, " {} ", switch (e.op) { - case ast::binarithm_op::BAND => + case binarithm_op::BAND => yield "&"; - case ast::binarithm_op::BOR => + case binarithm_op::BOR => yield "|"; - case ast::binarithm_op::DIV => + case binarithm_op::DIV => yield "/"; - case ast::binarithm_op::GT => + case binarithm_op::GT => yield ">"; - case ast::binarithm_op::GTEQ => + case binarithm_op::GTEQ => yield ">="; - case ast::binarithm_op::LAND => + case binarithm_op::LAND => yield "&&"; - case ast::binarithm_op::LEQUAL => + case binarithm_op::LEQUAL => yield "=="; - case ast::binarithm_op::LESS => + case binarithm_op::LESS => yield "<"; - case ast::binarithm_op::LESSEQ => + case binarithm_op::LESSEQ => yield "<="; - case ast::binarithm_op::LOR => + case binarithm_op::LOR => yield "||"; - case ast::binarithm_op::LSHIFT => + case binarithm_op::LSHIFT => yield "<<"; - case ast::binarithm_op::LXOR => + case binarithm_op::LXOR => yield "^^"; - case ast::binarithm_op::MINUS => + case binarithm_op::MINUS => yield "-"; - case ast::binarithm_op::MODULO => + case binarithm_op::MODULO => yield "%"; - case ast::binarithm_op::NEQUAL => + case binarithm_op::NEQUAL => yield "!="; - case ast::binarithm_op::PLUS => + case binarithm_op::PLUS => yield "+"; - case ast::binarithm_op::RSHIFT => + case binarithm_op::RSHIFT => yield ">>"; - case ast::binarithm_op::TIMES => + case binarithm_op::TIMES => yield "*"; - case ast::binarithm_op::BXOR => + case binarithm_op::BXOR => yield "^"; })?; - z += expr(out, indent, *e.rvalue)?; + z += binexprval(out, indent, *e.rvalue, prec)?; return z; case let e: ast::binding_expr => let z = fmt::fprintf(out, "{}{}", @@ -388,6 +386,55 @@ export fn expr( }; }; +fn binprecedence(op: binarithm_op) uint = { + switch (op) { + case binarithm_op::DIV, binarithm_op::MODULO, binarithm_op::TIMES => + return 10; + case binarithm_op::MINUS, binarithm_op::PLUS => + return 9; + case binarithm_op::LSHIFT, binarithm_op::RSHIFT => + return 8; + case binarithm_op::BAND => + return 7; + case binarithm_op::BXOR => + return 6; + case binarithm_op::BOR => + return 5; + case binarithm_op::GT, binarithm_op::GTEQ, + binarithm_op::LESS, binarithm_op::LESSEQ => + return 4; + case binarithm_op::LEQUAL, binarithm_op::NEQUAL => + return 3; + case binarithm_op::LAND => + return 2; + case binarithm_op::LXOR => + return 1; + case binarithm_op::LOR => + return 0; + }; +}; + +fn binexprval( + out: io::handle, + indent: size, + e: ast::expr, + prec: uint, +) (size | io::error) = { + let z = 0z; + match (e.expr) { + case let b: ast::binarithm_expr => + if (binprecedence(b.op) < prec) { + z += fmt::fprint(out, "(")?; + z += expr(out, indent, e)?; + z += fmt::fprint(out, ")")?; + return z; + }; + case => void; + }; + z += expr(out, indent, e)?; + return z; +}; + fn stmt( out: io::handle, indent: size,