Skip to content

Commit e3c6b76

Browse files
A lambda is written with an arrow as well as a call (#1151)
`a => a + 3` was a parse error and is now the same entity as `lambda(a, a + 3)`. Several parameters are the curried form #495's plan specifies: `a b => a + b` is `a => b => a + b`, which is `lambda(a, b, a + b)`. The nodes were never missing. `Lambda`, `Application`, beta reduction and currying are all in the library already, and #1137's ODE solver is built on them; what that plan describes and the library did not have is the syntax. This is the half of it that costs nothing -- `=` followed by `>` was not a token and not a parse, so no reading of any valid input changes. The other half (`f a b`, `sin x`, `sin (x)`) changes what juxtaposition means, which is the decision #286 is about, and is not free. Written by sharing the left side rather than as `names+ '=>' body | expression`. Both alternatives of that begin with a name and stay viable through a second one, juxtaposition being multiplication, so `a b => a + b` was decided as a product before the arrow was reached and came back "mismatched input '=>'". Sharing leaves one decision, taken on the token after the left side, and the parameters are read back out of the product it parsed as. That also settles the parameter that cannot arrive as a name token: `i` lexes as the imaginary unit, so `i => i + 1` had to be read the way `lambda(i, i + 1)` is read -- through `Binding` -- and the two now agree. #976. A parameter that is not a name is refused, as the plan says: `a 3 => 3`, `2 => 3` and `x + 1 => 2` raise. They raised before too, under a different name -- `UnhandledParseException` where they now raise `InvalidArgumentParseException` -- which is recorded in BREAKING-CHANGES. The arrow is read, not printed. A lambda still prints as `lambda(x, x + 1)`, which is what keeps the round trip the printed form promises. The grammar was regenerated the way ImproveParser.md sets out. The unmodified grammar was regenerated first and its diff confirmed empty, so what is committed here is the rule and not a toolchain version. Syntax.md updated in three places. Full suite 9386 passed, 0 failed. Part of #495. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent e3329b8 commit e3c6b76

11 files changed

Lines changed: 1777 additions & 1492 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,46 @@ read first.
106106
| | `Transformation.Factorization.Name` | `… then polynomial-factorization` | `… then polynomial-factorization then numeric-content` |
107107
| **Silent** | `"arccotan(-1)".ToEntity().Simplify()`, and every negative argument the inverse-trigonometric table knows | `3/4 * pi` — the textbook range, and **not equal to `arccotan(-1)`**, whose value is `-pi/4` | `-1/4 * pi` |
108108
| | `"e ^ ln(x)".ToEntity().Simplify()`, and every exponential of a natural logarithm | `e ^ ln(x)` — left as written | `x` |
109+
| | `"a => a + 3".ToEntity()`, and every lambda written with an arrow | `UnhandledParseException` | `lambda(a, a + 3)` |
110+
111+
### A lambda is written with an arrow as well as a call
112+
113+
`a => a + 3` was a parse error and is now the same entity as `lambda(a, a + 3)`. Several
114+
parameters are the curried form the plan in
115+
[#495](https://github.com/asc-community/AngouriMath/issues/495) specifies — `a b => a + b` is
116+
`a => b => a + b`, which is `lambda(a, b, a + b)`.
117+
118+
```
119+
"a => a + 3".ToEntity() was UnhandledParseException
120+
is lambda(a, a + 3)
121+
"a b => a + b".ToEntity() is lambda(a, lambda(b, a + b))
122+
"apply(apply(a b => a + b, 1), 2)".Simplify() is 3
123+
```
124+
125+
**Nothing that parsed before parses differently.** `=` followed by `>` was not a token and not a
126+
parse, so no reading of any valid input has changed; `>=`, `->`, `=` and the rest are untouched,
127+
and there are tests pinning them. The `Lambda` node, beta reduction and currying were all already
128+
there — this is the syntax for them.
129+
130+
**The arrow is read, not printed.** A lambda still prints as `lambda(x, x + 1)`, which is what
131+
keeps the round trip the printed form promises: several spellings may be read, exactly one is
132+
printed.
133+
134+
**Every parameter must be a name**, which is what the plan says. `a 3 => 3` is refused, and so
135+
are `2 => 3` and `x + 1 => 2`. Those raised `UnhandledParseException` before and now raise
136+
`InvalidArgumentParseException` — still invalid, differently named. Code catching the parse
137+
exception by type around input like that will not catch this one.
138+
139+
An index called `i` is the name rather than the imaginary unit, matching `lambda(i, i + 1)`,
140+
which it gets by reading its parameters through the same `Binding` the call form uses
141+
([#976](https://github.com/asc-community/AngouriMath/issues/976)).
142+
143+
**Not in this change:** the rest of that plan's syntax — `f a b` for `apply(apply(f, a), b)`,
144+
`sin x` without brackets, and `sin (x)` with a space. Each of those changes what juxtaposition
145+
means, which is the decision [#286](https://github.com/asc-community/AngouriMath/issues/286) is
146+
about, and none of them is free the way the arrow is.
147+
148+
[#495](https://github.com/asc-community/AngouriMath/issues/495).
109149

110150
### The exponential of a natural logarithm folds
111151

‎Sources/.editorconfig‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,8 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
171171
[Tests/UnitTests/Algebra/SolveTest/ParametricSystemTest.cs]
172172
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
173173

174+
[Tests/UnitTests/Common/LambdaArrowSyntaxTest.cs]
175+
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
176+
174177
[Tests/UnitTests/Calculus/ApplicationDerivativeTest.cs]
175178
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

‎Sources/AngouriMath/Core/Antlr/AngouriMath.g‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,49 @@ Nodes end
243243

244244
*/
245245

246+
/* `a => a + 3`, and `a b => a + b` for `a => b => a + b`. Lowest priority of anything, and
247+
right-associative through the recursion on `expression`, so the body runs to the end of what
248+
is being parsed: `a => a + 3` is `a => (a + 3)` and never `(a => a) + 3`.
249+
250+
The parameters are VARIABLE tokens rather than expressions, which is what makes `a 3 => 3`
251+
invalid as the plan says it should be: `3` cannot match, the alternative fails, and what is
252+
left is not a parse.
253+
254+
The body is built exactly as `lambda(...)` builds it, through Binding, so that the two spell
255+
the same thing -- including the case a lambda's Variable-typed parameter cannot state
256+
directly, an index called `i`. https://github.com/asc-community/AngouriMath/issues/976 */
246257
expression returns[Entity value]
247258
: s = provided_expression { $value = $s.value; }
259+
('=>' b = expression
260+
{
261+
/* The parameters are read back out of an ordinary expression rather than matched as
262+
a list of names, and that is about what the parser can predict rather than about
263+
taste. Written as `names+ '=>' body | expression`, both alternatives begin with a
264+
name and stay viable through a second one -- juxtaposition being multiplication --
265+
so `a b => a + b` was decided as a product before the arrow was ever reached, and
266+
came back "mismatched input '=>'". Sharing the left side leaves one decision, taken
267+
on the token after it.
268+
269+
So `a b c` arrives here as the product it parsed as, and its factors in order are
270+
the parameters. Anything that is not a name fails here instead of failing to
271+
parse: `a 3 => 3`, which the plan calls invalid, raises rather than being read as
272+
a lambda over `a` and `3`.
273+
274+
Through Binding, exactly as `lambda(...)` builds it, so the two spell the same
275+
thing -- including an index called `i`, which lexes as the imaginary unit and can
276+
therefore never arrive as a Variable token at all.
277+
https://github.com/asc-community/AngouriMath/issues/976 */
278+
Entity lambdaBody = $b.value;
279+
var parameters = ($value is Mulf ? Mulf.LinearChildren($value) : new[] { $value }).ToList();
280+
foreach (var x in ((IEnumerable<Entity>)parameters).Reverse())
281+
{
282+
var bound = AngouriMath.Core.Binding.Of(x);
283+
if (bound.Name is not Variable v) throw new InvalidArgumentParseException($"Lambda is expected to have valid parameters, {x} encountered instead");
284+
lambdaBody = bound.In(lambdaBody).LambdaOver(v);
285+
}
286+
$value = lambdaBody;
287+
}
288+
)?
248289
;
249290
250291

‎Sources/AngouriMath/Core/Antlr/AngouriMath.interp‎

Lines changed: 3 additions & 1 deletion
Large diffs are not rendered by default.

‎Sources/AngouriMath/Core/Antlr/AngouriMath.tokens‎

Lines changed: 127 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,15 @@ T__141=142
143143
T__142=143
144144
T__143=144
145145
T__144=145
146-
NEWLINE=146
147-
NUMBER=147
148-
SPECIALSET=148
149-
BOOLEAN=149
150-
NAN=150
151-
VARIABLE=151
152-
COMMENT=152
153-
WS=153
146+
T__145=146
147+
NEWLINE=147
148+
NUMBER=148
149+
SPECIALSET=149
150+
BOOLEAN=150
151+
NAN=151
152+
VARIABLE=152
153+
COMMENT=153
154+
WS=154
154155
'!'=1
155156
'^'=2
156157
'-'=3
@@ -180,120 +181,121 @@ WS=153
180181
'implies'=27
181182
'->'=28
182183
'provided'=29
183-
','=30
184-
';'=31
185-
':'=32
186-
'+oo'=33
187-
'-oo'=34
188-
'(|'=35
189-
'|)'=36
190-
'['=37
191-
']T'=38
192-
']'=39
193-
'('=40
194-
')'=41
195-
'{'=42
196-
'}'=43
197-
'log('=44
198-
'log10('=45
199-
'log2('=46
200-
'pow('=47
201-
'sqrt('=48
202-
'cbrt('=49
203-
'sqr('=50
204-
'ln('=51
205-
'exp('=52
206-
'sin('=53
207-
'cos('=54
208-
'tan('=55
209-
'cotan('=56
210-
'cot('=57
211-
'sec('=58
212-
'cosec('=59
213-
'csc('=60
214-
'arcsin('=61
215-
'arccos('=62
216-
'arctan('=63
217-
'arccotan('=64
218-
'arcsec('=65
219-
'arccosec('=66
220-
'arccsc('=67
221-
'acsc('=68
222-
'asin('=69
223-
'acos('=70
224-
'atan('=71
225-
'acotan('=72
226-
'asec('=73
227-
'acosec('=74
228-
'acot('=75
229-
'arccot('=76
230-
'sinh('=77
231-
'sh('=78
232-
'cosh('=79
233-
'ch('=80
234-
'tanh('=81
235-
'th('=82
236-
'cotanh('=83
237-
'coth('=84
238-
'cth('=85
239-
'sech('=86
240-
'sch('=87
241-
'cosech('=88
242-
'csch('=89
243-
'asinh('=90
244-
'arsinh('=91
245-
'arsh('=92
246-
'arcsinh('=93
247-
'acosh('=94
248-
'arcosh('=95
249-
'arch('=96
250-
'arccosh('=97
251-
'atanh('=98
252-
'artanh('=99
253-
'arth('=100
254-
'arctanh('=101
255-
'acoth('=102
256-
'arcoth('=103
257-
'acotanh('=104
258-
'arcotanh('=105
259-
'arcth('=106
260-
'arccotanh('=107
261-
'asech('=108
262-
'arsech('=109
263-
'arsch('=110
264-
'arcsech('=111
265-
'acosech('=112
266-
'arcosech('=113
267-
'arcsch('=114
268-
'arccosech('=115
269-
'acsch('=116
270-
'factorial('=117
271-
'gamma('=118
272-
'derivative('=119
273-
'integral('=120
274-
'limit('=121
275-
'limitleft('=122
276-
'limitright('=123
277-
'sum('=124
278-
'product('=125
279-
'signum('=126
280-
'sgn('=127
281-
'sign('=128
282-
'abs('=129
283-
'phi('=130
284-
'floor('=131
285-
'ceil('=132
286-
'ceiling('=133
287-
'round('=134
288-
'min('=135
289-
'max('=136
290-
'gcd('=137
291-
'trunc('=138
292-
'lcm('=139
293-
'erf('=140
294-
'conjugate('=141
295-
'domain('=142
296-
'piecewise('=143
297-
'apply('=144
298-
'lambda('=145
299-
'NaN'=150
184+
'=>'=30
185+
','=31
186+
';'=32
187+
':'=33
188+
'+oo'=34
189+
'-oo'=35
190+
'(|'=36
191+
'|)'=37
192+
'['=38
193+
']T'=39
194+
']'=40
195+
'('=41
196+
')'=42
197+
'{'=43
198+
'}'=44
199+
'log('=45
200+
'log10('=46
201+
'log2('=47
202+
'pow('=48
203+
'sqrt('=49
204+
'cbrt('=50
205+
'sqr('=51
206+
'ln('=52
207+
'exp('=53
208+
'sin('=54
209+
'cos('=55
210+
'tan('=56
211+
'cotan('=57
212+
'cot('=58
213+
'sec('=59
214+
'cosec('=60
215+
'csc('=61
216+
'arcsin('=62
217+
'arccos('=63
218+
'arctan('=64
219+
'arccotan('=65
220+
'arcsec('=66
221+
'arccosec('=67
222+
'arccsc('=68
223+
'acsc('=69
224+
'asin('=70
225+
'acos('=71
226+
'atan('=72
227+
'acotan('=73
228+
'asec('=74
229+
'acosec('=75
230+
'acot('=76
231+
'arccot('=77
232+
'sinh('=78
233+
'sh('=79
234+
'cosh('=80
235+
'ch('=81
236+
'tanh('=82
237+
'th('=83
238+
'cotanh('=84
239+
'coth('=85
240+
'cth('=86
241+
'sech('=87
242+
'sch('=88
243+
'cosech('=89
244+
'csch('=90
245+
'asinh('=91
246+
'arsinh('=92
247+
'arsh('=93
248+
'arcsinh('=94
249+
'acosh('=95
250+
'arcosh('=96
251+
'arch('=97
252+
'arccosh('=98
253+
'atanh('=99
254+
'artanh('=100
255+
'arth('=101
256+
'arctanh('=102
257+
'acoth('=103
258+
'arcoth('=104
259+
'acotanh('=105
260+
'arcotanh('=106
261+
'arcth('=107
262+
'arccotanh('=108
263+
'asech('=109
264+
'arsech('=110
265+
'arsch('=111
266+
'arcsech('=112
267+
'acosech('=113
268+
'arcosech('=114
269+
'arcsch('=115
270+
'arccosech('=116
271+
'acsch('=117
272+
'factorial('=118
273+
'gamma('=119
274+
'derivative('=120
275+
'integral('=121
276+
'limit('=122
277+
'limitleft('=123
278+
'limitright('=124
279+
'sum('=125
280+
'product('=126
281+
'signum('=127
282+
'sgn('=128
283+
'sign('=129
284+
'abs('=130
285+
'phi('=131
286+
'floor('=132
287+
'ceil('=133
288+
'ceiling('=134
289+
'round('=135
290+
'min('=136
291+
'max('=137
292+
'gcd('=138
293+
'trunc('=139
294+
'lcm('=140
295+
'erf('=141
296+
'conjugate('=142
297+
'domain('=143
298+
'piecewise('=144
299+
'apply('=145
300+
'lambda('=146
301+
'NaN'=151

0 commit comments

Comments
 (0)