Skip to content

Commit 47e6350

Browse files
committed
Implement @if keyword expression
1 parent 78275f9 commit 47e6350

File tree

6 files changed

+317
-80
lines changed

6 files changed

+317
-80
lines changed

src/end-to-end.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,13 @@ testCases(endToEnd, code => code)('end-to-end tests', [
264264
}`,
265265
either.makeRight({ true: 'true', false: 'false' }),
266266
],
267+
[
268+
`{@runtime context =>
269+
{@if :boolean.not(:boolean.is(:context))
270+
"it works!"
271+
{@panic}
272+
}
273+
}`,
274+
either.makeRight('it works!'),
275+
],
267276
])
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { elaborationSuite, success } from '../test-utilities.test.js'
2+
3+
elaborationSuite('@if', [
4+
[{ 0: '@if', condition: 'false', then: 'no', else: 'yes' }, success('yes')],
5+
[{ 0: '@if', condition: 'true', then: 'yes', else: 'no' }, success('yes')],
6+
[
7+
{
8+
0: '@if',
9+
condition: 'true',
10+
then: 'it works!',
11+
else: { 0: '@panic' },
12+
},
13+
success('it works!'),
14+
],
15+
[
16+
{
17+
a: 'it works!',
18+
b: {
19+
0: '@if',
20+
condition: 'true',
21+
then: { 0: '@lookup', key: 'a' },
22+
else: { 0: '@panic' },
23+
},
24+
},
25+
success({ a: 'it works!', b: 'it works!' }),
26+
],
27+
[
28+
{
29+
0: '@if',
30+
condition: 'false',
31+
then: { 0: '@panic' },
32+
else: 'it works!',
33+
},
34+
success('it works!'),
35+
],
36+
[
37+
{
38+
0: '@if',
39+
condition: {
40+
0: '@apply',
41+
function: {
42+
0: '@index',
43+
object: { 0: '@lookup', key: 'boolean' },
44+
query: { 0: 'not' },
45+
},
46+
argument: 'false',
47+
},
48+
then: 'it works!',
49+
else: { 0: '@panic' },
50+
},
51+
success('it works!'),
52+
],
53+
[
54+
{
55+
0: '@if',
56+
1: {
57+
0: '@apply',
58+
function: {
59+
0: '@index',
60+
object: { 0: '@lookup', key: 'boolean' },
61+
query: { 0: 'not' },
62+
},
63+
argument: 'false',
64+
},
65+
2: 'it works!',
66+
3: { 0: '@panic' },
67+
},
68+
success('it works!'),
69+
],
70+
[
71+
{
72+
a: {
73+
0: '@if',
74+
condition: { 0: '@lookup', key: 'b' },
75+
then: 'it works!',
76+
else: { 0: '@panic' },
77+
},
78+
b: 'true',
79+
},
80+
success({ a: 'it works!', b: 'true' }),
81+
],
82+
])
Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,91 @@
11
import either, { type Either } from '@matt.kantor/either'
22
import type { ElaborationError } from '../../../errors.js'
3+
import type { Molecule } from '../../../parsing.js'
34
import {
5+
asSemanticGraph,
6+
containsAnyUnelaboratedNodes,
7+
elaborateWithContext,
8+
makeIfExpression,
9+
readIfExpression,
10+
serialize,
411
type Expression,
512
type ExpressionContext,
613
type KeywordHandler,
714
type SemanticGraph,
815
} from '../../../semantics.js'
916

1017
export const ifKeywordHandler: KeywordHandler = (
11-
_expression: Expression,
12-
_context: ExpressionContext,
18+
expression: Expression,
19+
context: ExpressionContext,
1320
): Either<ElaborationError, SemanticGraph> =>
14-
either.makeLeft({ kind: 'bug', message: 'not implemented' })
21+
either.flatMap(readIfExpression(expression), ifExpression => {
22+
const expressionKeys = {
23+
// Note: this must be kept in alignment with `readIfExpression`.
24+
condition: 'condition' in expression ? 'condition' : '1',
25+
then: 'then' in expression ? 'then' : '2',
26+
else: 'else' in expression ? 'else' : '3',
27+
}
28+
29+
const elaboratedCondition = evaluateSubexpression(
30+
expressionKeys.condition,
31+
context,
32+
ifExpression.condition,
33+
)
34+
35+
return either.flatMap(elaboratedCondition, elaboratedCondition => {
36+
if (elaboratedCondition === 'true') {
37+
return either.map(
38+
evaluateSubexpression(
39+
expressionKeys.then,
40+
context,
41+
ifExpression.then,
42+
),
43+
asSemanticGraph,
44+
)
45+
} else if (elaboratedCondition === 'false') {
46+
return either.map(
47+
evaluateSubexpression(
48+
expressionKeys.else,
49+
context,
50+
ifExpression.else,
51+
),
52+
asSemanticGraph,
53+
)
54+
} else {
55+
return either.flatMap(
56+
serialize(elaboratedCondition),
57+
elaboratedCondition => {
58+
if (containsAnyUnelaboratedNodes(elaboratedCondition)) {
59+
// Return an unelaborated `@if` expression.
60+
return either.makeRight(
61+
makeIfExpression({
62+
...ifExpression,
63+
condition: elaboratedCondition,
64+
}),
65+
)
66+
} else {
67+
return either.makeLeft({
68+
kind: 'invalidExpression',
69+
message: 'condition was not boolean',
70+
})
71+
}
72+
},
73+
)
74+
}
75+
})
76+
})
77+
78+
const evaluateSubexpression = (
79+
key: string,
80+
context: ExpressionContext,
81+
subexpression: SemanticGraph | Molecule,
82+
) =>
83+
either.flatMap(
84+
serialize(asSemanticGraph(subexpression)),
85+
serializedSubexpression =>
86+
elaborateWithContext(serializedSubexpression, {
87+
keywordHandlers: context.keywordHandlers,
88+
program: context.program,
89+
location: [...context.location, key],
90+
}),
91+
)

src/language/semantics.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export {
2727
readFunctionExpression,
2828
type FunctionExpression,
2929
} from './semantics/expressions/function-expression.js'
30+
export {
31+
makeIfExpression,
32+
readIfExpression,
33+
type IfExpression,
34+
} from './semantics/expressions/if-expression.js'
3035
export {
3136
makeIndexExpression,
3237
readIndexExpression,

0 commit comments

Comments
 (0)