Skip to content

Commit

Permalink
NEP25 Expression parentheses
Browse files Browse the repository at this point in the history
  • Loading branch information
ghewgill committed Mar 5, 2024
1 parent 92a48aa commit 2057bc4
Show file tree
Hide file tree
Showing 32 changed files with 468 additions and 439 deletions.
20 changes: 12 additions & 8 deletions contrib/grammar/neon.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -157,29 +157,33 @@ WhileStatement = 'WHILE', ExpressionOrValid, ['LABEL', Identifier], 'DO', {State
ExpressionOrValid = Expression | ('VALID', (Identifier | (Expression, 'AS', Identifier)), {',', (Identifier | (Expression, 'AS', Identifier))});
BracketedExpression = ConditionalExpression | TryExpression | DisjunctionExpression;
BracketedExpression = ConditionalExpression | TryExpression | LogicalExpression;
ConditionalExpression = ('IF', Expression, 'THEN', Expression, 'ELSE', Expression);
TryExpression = ('TRY', Expression, {'TRAP', Identifier, ['.', Identifier], ['AS', Identifier], (('DO', {Statement}) | 'GIVES', Expression)});
Expression = DisjunctionExpression;
Expression = LogicalExpression;
DisjunctionExpression = ConjunctionExpression, {'OR', ConjunctionExpression};
LogicalExpression = MembershipExpression, [ DisjunctionExpressionChain | ConjunctionExpressionChain ];
ConjunctionExpression = MembershipExpression, {'AND', MembershipExpression};
DisjunctionExpressionChain = 'OR', MembershipExpression, {'OR', MembershipExpression};
ConjunctionExpressionChain = 'AND', MembershipExpression, {'AND', MembershipExpression};
MembershipExpression = TypeTestExpression, [('IN' | 'NOT', 'IN'), TypeTestExpression];
TypeTestExpression = ComparisonExpression, ['ISA', Type];
ComparisonExpression = AdditionExpression, {('=' | '<>' | '<' | '>' | '<=' | '>='), AdditionExpression};
ComparisonExpression = ArithmeticExpression, {('=' | '<>' | '<' | '>' | '<=' | '>='), ArithmeticExpression};
ArithmeticExpression = CompoundExpression, [ AdditionExpressionChain | ConcatExpressionChain | MultiplicationExpressionChain | (('-' | '/' | 'INTDIV' | 'MOD' | '^'), CompoundExpression) ];
AdditionExpression = MultiplicationExpression, {('+' | '-' | '&'), MultiplicationExpression};
AdditionExpressionChain = '+', CompoundExpression, {'+', CompoundExpression}, ['-', CompoundExpression];
MultiplicationExpression = ExponentiationExpression, {('*' | '/' | 'INTDIV' | 'MOD'), ExponentiationExpression};
ConcatExpressionChain = '&', CompoundExpression, {'&', CompoundExpression};
ExponentiationExpression = CompoundExpression, {'^', CompoundExpression};
MultiplicationExpressionChain = '*', CompoundExpression, {'*', CompoundExpression}, ['/', CompoundExpression];
Atom =
('(', BracketedExpression, ')') |
Expand Down
20 changes: 15 additions & 5 deletions docs/modules/reference/pages/expressions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,26 @@ The operator precedence is as follows, highest to lowest:
| Operator | Description

| `( )` | subexpression
| `^` | exponentiation
| `*` `/` `MOD` `INTDIV` | multiplication, division, modulo
| `+` `-` `&` | addition, subtraction, concatenation
| `^` `*` `/` `MOD` `INTDIV` `+` `-` `&` | arithmetic (see below)
| `=` `<>` `<` `>` `<=` `>=` | comparison
| `IN` `NOT IN` | membership
| `AND` | conjunction
| `OR` | disjunction
| `AND` `OR` | logical (see below)
| `IF` | conditional
|===

Operator precedence only matters for expressions with three or more operands.
For arithmetic and logical expressions, there is a simple rule:

* Parentheses are required in an expression, if adding parentheses around any two operands would change the meaning of the expression.

In practice, this means that all arithmetic and logical expressions (with three or more operands) must be fully parenthesised except in the following cases:

* Sums: `a + b + c` with an optional single subtraction at the end
* Products: `a * b * c` with an optional single division at the end
* Concatenation: `a & b & c`
* Conjunction: `a AND b AND c`
* Disjunction: `a OR b OR c`

== Array Subscripts

Array subscripts are normally integers greater than or equal to zero:
Expand Down
2 changes: 1 addition & 1 deletion lib/base.neon
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ EXPORT FUNCTION from(base: Number, digits: String): NumberResult
CHECK d ISA string.FindResult.index ELSE
RETURN NumberResult.error("invalid digit: \(c) at index \(i)")
END CHECK
r := r * base + d.index
r := (r * base) + d.index
END FOREACH
RETURN NumberResult.value(r)
END FUNCTION
Expand Down
4 changes: 2 additions & 2 deletions lib/bigint.neon
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ FUNCTION sub(x, y: BigInt): BigInt
VAR i: Number := 0
VAR borrow: Number := 0
WHILE i < x.digits.size() DO
r.digits[i] := (IF i < x.digits.size() THEN x.digits[i] ELSE 0) - (IF i < y.digits.size() THEN y.digits[i] ELSE 0) - borrow
r.digits[i] := ((IF i < x.digits.size() THEN x.digits[i] ELSE 0) - (IF i < y.digits.size() THEN y.digits[i] ELSE 0)) - borrow
IF r.digits[i] < 0 THEN
r.digits[i] := r.digits[i] + Base
borrow := 1
Expand Down Expand Up @@ -216,7 +216,7 @@ FUNCTION mul(x, y: BigInt): BigInt
VAR p: BigInt := Zero
VAR carry: Number := 0
FOR j := 0 TO x.digits.size()-1 DO
LET z: Number := (IF j < x.digits.size() THEN x.digits[j] ELSE 0) * y.digits[i] + carry
LET z: Number := ((IF j < x.digits.size() THEN x.digits[j] ELSE 0) * y.digits[i]) + carry
p.digits[i+j] := z MOD Base
carry := math.floor(z / Base)
END FOR
Expand Down
26 changes: 13 additions & 13 deletions lib/complex.neon
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ FUNCTION acos(a: Complex): Complex
LET s2: Complex := make(1 + a.re, a.im).sqrt()
LET r1: Number := 2 * math.atan2(s1.re, s2.re)
VAR i1: Number := (s2.re*s1.im) - (s2.im*s1.re)
i1 := sign(i1) * math.log(math.abs(i1) + math.sqrt(i1*i1 + 1))
i1 := sign(i1) * math.log(math.abs(i1) + math.sqrt((i1*i1) + 1))
RETURN make(r1, i1)
END FUNCTION

Expand All @@ -115,7 +115,7 @@ FUNCTION acosh(a: Complex): Complex
LET s1: Complex := make(a.re - 1, a.im).sqrt()
LET s2: Complex := make(a.re + 1, a.im).sqrt()
VAR r1: Number := (s1.re*s2.re) + (s1.im*s2.im)
r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt(r1*r1 + 1))
r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt((r1*r1) + 1))
LET i1: Number := 2 * math.atan2(s1.im, s2.re)
RETURN make(r1, i1)
END FUNCTION
Expand All @@ -128,7 +128,7 @@ END FUNCTION
* http://mathworld.wolfram.com/AbsoluteValue.html
*/
FUNCTION abs(a: Complex): Number
RETURN math.sqrt(a.re^2 + a.im^2)
RETURN math.sqrt((a.re^2) + (a.im^2))
END FUNCTION

/* Function: add
Expand Down Expand Up @@ -164,7 +164,7 @@ FUNCTION asin(a: Complex): Complex
LET s1: Complex := make(1 + a.re, a.im).sqrt()
LET s2: Complex := make(1 - a.re, -a.im).sqrt()
VAR r1: Number := (s1.re*s2.im) - (s2.re*s1.im)
r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt(r1*r1 + 1))
r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt((r1*r1) + 1))
LET i1: Number := math.atan2(a.re, (s1.re*s2.re) - (s1.im*s2.im))
RETURN make(i1, -r1)
END FUNCTION
Expand All @@ -180,7 +180,7 @@ FUNCTION asinh(a: Complex): Complex
LET s1: Complex := make(1 + a.im, -a.re).sqrt()
LET s2: Complex := make(1 - a.im, a.re).sqrt()
VAR r1: Number := (s1.re * s2.im) - (s2.re * s1.im)
r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt(r1 * r1 + 1))
r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt((r1 * r1) + 1))
LET i1: Number := math.atan2(a.im, (s1.re * s2.re) - (s1.im * s2.im))
RETURN make(r1, i1)
END FUNCTION
Expand Down Expand Up @@ -250,8 +250,8 @@ END FUNCTION
* http://mathworld.wolfram.com/ComplexDivision.html
*/
FUNCTION div(a, b: Complex): Complex
LET d: Number := b.re^2 + b.im^2
RETURN make((a.re*b.re + a.im*b.im) / d, (a.im*b.re - a.re*b.im) / d)
LET d: Number := (b.re^2) + (b.im^2)
RETURN make(((a.re*b.re) + (a.im*b.im)) / d, ((a.im*b.re) - (a.re*b.im)) / d)
END FUNCTION

/* Function: exp
Expand Down Expand Up @@ -287,7 +287,7 @@ END FUNCTION
* http://mathworld.wolfram.com/MultiplicativeInverse.html
*/
FUNCTION inv(a: Complex): Complex
LET d: Number := a.re*a.re + a.im*a.im
LET d: Number := (a.re*a.re) + (a.im*a.im)
RETURN make(a.re/d, -a.im/d)
END FUNCTION

Expand All @@ -299,7 +299,7 @@ END FUNCTION
* http://mathworld.wolfram.com/Logarithm.html
*/
FUNCTION log(a: Complex): Complex
RETURN make(math.log(a.re*a.re + a.im*a.im)/2, a.arg())
RETURN make(math.log((a.re*a.re) + (a.im*a.im))/2, a.arg())
END FUNCTION

/* Function: log10
Expand All @@ -322,7 +322,7 @@ END FUNCTION
* http://mathworld.wolfram.com/ComplexMultiplication.html
*/
FUNCTION mul(a, b: Complex): Complex
RETURN make(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re)
RETURN make((a.re * b.re) - (a.im * b.im), (a.re * b.im) + (a.im * b.re))
END FUNCTION

/* Function: pow
Expand All @@ -335,8 +335,8 @@ END FUNCTION
FUNCTION pow(a, b: Complex): Complex
LET p: Number := a.arg()
LET m: Number := a.abs()
LET r: Number := m^b.re * math.exp(-b.im * p)
LET t: Number := b.re * p + b.im * math.log(m)
LET r: Number := (m^b.re) * math.exp(-b.im * p)
LET t: Number := (b.re * p) + (b.im * math.log(m))
RETURN make(r * math.cos(t), r * math.sin(t))
END FUNCTION

Expand Down Expand Up @@ -390,7 +390,7 @@ END FUNCTION
* Complex square (a^2).
*/
FUNCTION square(a: Complex): Complex
RETURN make(a.re*a.re - a.im*a.im, 2*a.re*a.im)
RETURN make((a.re*a.re) - (a.im*a.im), 2*a.re*a.im)
END FUNCTION

/* Function: tan
Expand Down
6 changes: 3 additions & 3 deletions lib/datetime.neon
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ FUNCTION makeFromInstant(inst: Number): DateTime
year WITH 1900 + tm.tm_year,
month WITH 1 + tm.tm_mon,
day WITH tm.tm_mday,
weekday WITH 1 + (tm.tm_wday + 6) MOD 7,
weekday WITH 1 + ((tm.tm_wday + 6) MOD 7),
hour WITH tm.tm_hour,
minute WITH tm.tm_min,
second WITH tm.tm_sec,
Expand Down Expand Up @@ -256,10 +256,10 @@ END FUNCTION
FUNCTION DateTime.plusPeriod(self: DateTime, period: Period): DateTime
VAR dt: DateTime := self
dt.year := dt.year + period.years
LET m: Number := dt.month - 1 + period.months
LET m: Number := (dt.month - 1) + period.months
dt.year := dt.year + math.floor(m / 12)
dt.month := 1 + (m MOD 12)
RETURN makeFromParts(dt).plusDuration(86400*period.days + 3600*period.hours + 60*period.minutes + period.seconds)
RETURN makeFromParts(dt).plusDuration((86400*period.days) + (3600*period.hours) + (60*period.minutes) + period.seconds)
END FUNCTION

/* Function: DateTime.toString
Expand Down
6 changes: 3 additions & 3 deletions lib/json.neon
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@ FUNCTION decodePart(json: String, INOUT index: Number): JsonResult
VAR char: Number := 0
FOR i := 1 TO 4 DO
IF "0" <= json[index+i] <= "9" THEN
char := char * 0x10 + (string.toCodePoint(json[index+i]) - string.toCodePoint("0"))
char := (char * 0x10) + (string.toCodePoint(json[index+i]) - string.toCodePoint("0"))
ELSIF "a" <= json[index+i] <= "f" THEN
char := char * 0x10 + (string.toCodePoint(json[index+i]) - string.toCodePoint("a"))
char := (char * 0x10) + (string.toCodePoint(json[index+i]) - string.toCodePoint("a"))
ELSIF "A" <= json[index+i] <= "F" THEN
char := char * 0x10 + (string.toCodePoint(json[index+i]) - string.toCodePoint("A"))
char := (char * 0x10) + (string.toCodePoint(json[index+i]) - string.toCodePoint("A"))
ELSE
RETURN JsonResult.error(DecodeError(message WITH "invalid hex character", index WITH index+i))
END IF
Expand Down
2 changes: 1 addition & 1 deletion lib/regex.neon
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ FUNCTION ExprGroup.compile(self: VALID POINTER TO ExprGroup, INOUT regex: Regex)
IF self->capture THEN
regex.append(Opcode.save(2*self->index))
self->expr->compile(INOUT regex)
regex.append(Opcode.save(2*self->index+1))
regex.append(Opcode.save((2*self->index)+1))
ELSE
self->expr->compile(INOUT regex)
END IF
Expand Down
6 changes: 3 additions & 3 deletions lib/string.neon
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ FUNCTION format(s: String, fmt: String): String
ELSIF align IN [">", "="] THEN
r := repeat(fill, space) & r
ELSIF align = "^" THEN
r := repeat(fill, space INTDIV 2) & r & repeat(fill, space - space INTDIV 2)
r := repeat(fill, space INTDIV 2) & r & repeat(fill, space - (space INTDIV 2))
END IF
END IF
END IF
Expand Down Expand Up @@ -220,7 +220,7 @@ FUNCTION padLeft(s: String, pad: String, width: Number): String
CHECK n > 0 ELSE
RETURN s
END CHECK
RETURN repeat(pad, n INTDIV pad.length()) & pad[0 TO n MOD pad.length() - 1] & s
RETURN repeat(pad, n INTDIV pad.length()) & pad[0 TO (n MOD pad.length()) - 1] & s
END FUNCTION

/*
Expand All @@ -234,7 +234,7 @@ FUNCTION padRight(s: String, pad: String, width: Number): String
CHECK n > 0 ELSE
RETURN s
END CHECK
RETURN s & pad[LAST - (n MOD pad.length() - 1) TO LAST] & repeat(pad, n INTDIV pad.length())
RETURN s & pad[LAST - ((n MOD pad.length()) - 1) TO LAST] & repeat(pad, n INTDIV pad.length())
END FUNCTION

/* Function: quoted
Expand Down
6 changes: 3 additions & 3 deletions neon/lexer.neon
Original file line number Diff line number Diff line change
Expand Up @@ -474,12 +474,12 @@ FUNCTION tokenize_fragment(source_path: String, INOUT line: Number, column_start
EXIT LOOP
END IF
IF d <> "_" THEN
LET x: Number := (IF "0" <= d <= "9" THEN string.toCodePoint(d) - string.toCodePoint("0") ELSE (IF "a" <= d <= "z" THEN string.toCodePoint(d) - string.toCodePoint("a") + 10 ELSE -1))
LET x: Number := (IF "0" <= d <= "9" THEN string.toCodePoint(d) - string.toCodePoint("0") ELSE (IF "a" <= d <= "z" THEN (string.toCodePoint(d) - string.toCodePoint("a")) + 10 ELSE -1))
IF x < 0 OR x >= base THEN
error(1005, t, "invalid digit for given base")
END IF
-- TODO decimal
value := value * base + x
value := (value * base) + x
END IF
INC i
END LOOP
Expand Down Expand Up @@ -586,7 +586,7 @@ FUNCTION tokenize_fragment(source_path: String, INOUT line: Number, column_start
IF NOT hex_char(d) THEN
error(1013, t, "invalid hex character")
END IF
x := x * 16 + (IF "0" <= d <= "9" THEN string.toCodePoint(d) - string.toCodePoint("0") ELSE (IF "a" <= d <= "z" THEN string.toCodePoint(d) - string.toCodePoint("a") + 10 ELSE -1))
x := (x * 16) + (IF "0" <= d <= "9" THEN string.toCodePoint(d) - string.toCodePoint("0") ELSE (IF "a" <= d <= "z" THEN (string.toCodePoint(d) - string.toCodePoint("a")) + 10 ELSE -1))
END FOR
c := string.fromCodePoint(x)
i := i + len
Expand Down
Loading

0 comments on commit 2057bc4

Please sign in to comment.