As I understand the question, the intent is that whenever an expression statement ends in a '}', the terminating ';' can be omitted, even with statements like
{ let x = 2*if cond { foobar(); 42; } else { 666; }
foo();
}
As can be seen, using ';' as a terminator in an expression language "clashes"; it might be worth considering whether the value yielded by a block should be followed by ';' as in my example. (I noticed that it was omitted from the examples in the question, which would be a syntax error with the provided grammar.)
Perhaps it is better to use ';' as a separator, and let it be optional, except in cases where two adjacent expressions would be parsed as one expression. (Using line breaks as separation hints might also be a good idea.) This way:
{ let x = 2*if cond {42} else {666}
foo()
}
would not need any (assuming that a function call cannot follow at that point), but
{ let x = 2*if cond {42} else {666}
*p = foo()
}
would be parsed as a multiplication by p unless the prefix '*' was separated from the preceding expression by ';'.
Designing this in a formal grammar would be easier than making a grammar that distinguishes expressions that end in '}' from ones that don't. However, in a hand-written recursive descent parser it would not be too hard to hack the expression parser function to provide information on whether its last symbol was a '}'. Just add a bool curlycue flag to the CST nodes for expressions, and then inspect it in the stmt parse function:
CST stmt() {
ExprCST ex = expr();
if(!ex->curlycue) expect(';');
return new StmtCST(ex);
}
All parse functions for subexpressions just need to check for curlycue and propagate it up in the parse tree, for example:
ExprCST mult_expr() {
ExprCST left_operand = cast_expr();
while(sym() == '*' || sym() == '/' || sym() == '%') {
int op = sym(); nextsym();
ExprCST right_operand = cast_expr();
left_operand = new BinaryExprCST(op, left_operand, right_operand);
left_operand->curlycue = right_operand->curlycue;
}
return left_operand;
}
Alternatively, a special expect_semi parser function could be used
(assuming sym() gives the current token as an int, nextsym() advances to the next token, you'll also need a prevsym() that un-gets the current token):
void expect_semi() {
prevsym(); // un-gets the last token of a preceding expression.
if(sym() == '}') { nextsym(); } // skip it, but do nothing further
else { nextsym(); //skip whatever else it may have been
if(sym() == ';') { nextsym(); } // but require a semicolon now.
else { syntax_error("semicolon expected"); }
}
}
Note however, that this will still not solve the issue with separating two expressions that could be parsed as one, when the second begins with an unary operator that is also an infix operator. I'd not recommend doing this. The only reason to have it seems to be a desire to mimic the absence of ';' after compound-statements in C, which I actually always have thought was a bit strange. In Algol (and descendants like Pascal), ';' is a separator and is not omitted after the end keyword of blocks and compound-statements.
let x = if cond then foo else bar in print "foo"; 2
(;
is an operator that discards the value on the left) $\endgroup$};
is that bad. I’m also saying that with the background that C++ requires that for every struct and class definition. $\endgroup$}
. $\endgroup$