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,
```