allow chained comparisons i<x<j (desugared into: i<x and x<j)

This commit is contained in:
Irmen de Jong 2023-12-28 01:18:59 +01:00
parent 2b8f613a00
commit dfce292294
6 changed files with 47 additions and 21 deletions

View File

@ -171,6 +171,30 @@ internal class StatementReorderer(
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// desugar chained comparisons: i < x < j ---> i<x and x<j
// only if i<x or x<j was not written in parentheses! (i<x) < y, i < (x<y) -> leave untouched
if(expr.operator in ComparisonOperators) {
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && !leftBinExpr.insideParentheses && leftBinExpr.operator in ComparisonOperators) {
if(!leftBinExpr.right.isSimple) {
errors.warn("possible multiple evaluation of subexpression in chained comparison, consider using a temporary variable", leftBinExpr.right.position)
}
val right = BinaryExpression(leftBinExpr.right.copy(), expr.operator, expr.right, leftBinExpr.right.position)
val desugar = BinaryExpression(leftBinExpr, "and", right, expr.position)
return listOf(IAstModification.ReplaceNode(expr, desugar, parent))
}
else if(rightBinExpr!=null && !rightBinExpr.insideParentheses && rightBinExpr.operator in ComparisonOperators) {
if(!rightBinExpr.left.isSimple) {
errors.warn("possible multiple evaluation of subexpression in chained comparison, consider using a temporary variable", rightBinExpr.left.position)
}
val left = BinaryExpression(expr.left, expr.operator, rightBinExpr.left.copy(), rightBinExpr.left.position)
val desugar = BinaryExpression(left, "and", rightBinExpr, expr.position)
return listOf(IAstModification.ReplaceNode(expr, desugar, parent))
}
}
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)

View File

@ -423,7 +423,7 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode {
}
}
private fun ExpressionContext.toAst() : Expression {
private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expression {
val litval = literalvalue()
if(litval!=null) {
@ -468,7 +468,7 @@ private fun ExpressionContext.toAst() : Expression {
return scoped_identifier().toAst()
if(bop!=null)
return BinaryExpression(left.toAst(), bop.text.trim(), right.toAst(), toPosition())
return BinaryExpression(left.toAst(), bop.text.trim(), right.toAst(), toPosition(), insideParentheses=insideParentheses)
if(prefix!=null)
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
@ -483,7 +483,7 @@ private fun ExpressionContext.toAst() : Expression {
}
if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst() // expression within ( )
return expression(0).toAst(insideParentheses=true) // expression within ( )
if(typecast()!=null)
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())

View File

@ -148,7 +148,13 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
}
}
class BinaryExpression(var left: Expression, var operator: String, var right: Expression, override val position: Position) : Expression() {
class BinaryExpression(
var left: Expression,
var operator: String,
var right: Expression,
override val position: Position,
val insideParentheses: Boolean = false // used in very few places to check priorities
) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {

View File

@ -579,9 +579,10 @@ postfix increment and decrement: ``++`` ``--``
Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
Because these operations are so common, we have these short forms.
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
comparison: ``==`` ``!=`` ``<`` ``>`` ``<=`` ``>=``
Equality, Inequality, Less-than, Greater-than, Less-or-Equal-than, Greater-or-Equal-than comparisons.
The result is a 'boolean' value 'true' or 'false' (which in reality is just a byte value of 1 or 0).
The result is a boolean value 'true' or 'false' (1 or 0).
Note that you can chain comparisons like so: ``i < x < j``, which is the same as ``i<x and x<j``.
logical: ``not`` ``and`` ``or`` ``xor``
These operators are the usual logical operations that are part of a logical expression to reason

View File

@ -83,4 +83,3 @@ Other language/syntax features to think about
- add (rom/ram)bank support to romsub. A call will then automatically switch banks, use callfar and something else when in banked ram.
challenges: how to not make this too X16 specific? How does the compiler know what bank to switch (ram/rom)?
How to make it performant when we want to (i.e. NOT have it use callfar/auto bank switching) ?
- chained comparisons `10<x<20` , `x==y==z` (desugars to `10<x and x<20`, `x==y and y==z`) BUT this changes the semantics of what it is right now ! (x==(y==z) --> x==true)

View File

@ -3,19 +3,15 @@
main {
sub start() {
cx16.r0++
str[] names = ["irmen", "de", "jong"]
uword zz = names[1]
txt.print(names[1])
}
sub derp() {
cx16.r0++
}
asmsub hurrah() {
%ir {{
nop
}}
ubyte x = 10
ubyte y = 2
txt.print_ub(5<x and x<=20)
txt.nl()
txt.print_ub(5<x and x<=9)
txt.nl()
txt.print_ub(5<x<=9)
txt.nl()
txt.print_ub(5<(x-y)<=9<y)
txt.nl()
}
}