Skip to content

Commit 19e09a2

Browse files
committed
Revamp @lookup implementation
Hopefully the new version is easier to follow.
1 parent 1209f6f commit 19e09a2

File tree

1 file changed

+93
-36
lines changed

1 file changed

+93
-36
lines changed

src/language/compiling/semantics/keyword-handlers/lookup-handler.ts

Lines changed: 93 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
readLookupExpression,
1414
type Expression,
1515
type ExpressionContext,
16+
type KeyPath,
1617
type KeywordHandler,
1718
type SemanticGraph,
1819
} from '../../../semantics.js'
@@ -42,6 +43,9 @@ export const lookupKeywordHandler: KeywordHandler = (
4243
}
4344
})
4445

46+
/**
47+
* Recursively search upwards in lexical scope for the given `key`.
48+
*/
4549
const lookup = ({
4650
context,
4751
key,
@@ -59,47 +63,100 @@ const lookup = ({
5963
})
6064
: either.makeRight(option.makeSome(asSemanticGraph(valueFromPrelude)))
6165
} else {
66+
// Given the following program:
67+
// ```
68+
// {
69+
// a1: …
70+
// a2: {
71+
// b1: …
72+
// b2: … // we are here
73+
// }
74+
// }
75+
// ```
76+
// If `context.location` is `['a2', 'b2']`, the current scope (containing `b1`) is at `['a2']`,
77+
// and the parent scope (containing `a1`) is at `[]`.
6278
const pathToCurrentScope = context.location.slice(0, -1)
79+
const pathToParentScope = pathToCurrentScope.slice(0, -1)
6380

64-
// TODO: This is sketchy, or at least confusingly-written. Improve test coverage to weed out
65-
// potential bugginess, and consider refactoring to make it easier to follow.
66-
const pathToPossibleExpression =
67-
pathToCurrentScope[pathToCurrentScope.length - 1] === '1'
68-
? pathToCurrentScope.slice(0, -1)
69-
: pathToCurrentScope
81+
// If parent is a keyword expression and the current scope's key is `1`, the current scope is
82+
// an expression argument.
83+
const expressionCurrentScopeIsArgumentOf = option.flatMap(
84+
option.filter(
85+
applyKeyPathToSemanticGraph(context.program, pathToParentScope),
86+
isExpression,
87+
),
88+
parent =>
89+
pathToCurrentScope[pathToCurrentScope.length - 1] === '1'
90+
? option.makeSome(parent)
91+
: option.none,
92+
)
7093

71-
const possibleLookedUpValue = option.flatMap(
72-
applyKeyPathToSemanticGraph(context.program, pathToPossibleExpression),
73-
scope =>
74-
either.match(readFunctionExpression(scope), {
75-
left: _ =>
76-
// Lookups should not resolve to expression properties.
77-
// For example the value of the lookup expression in `a => :parameter` (desugared:
78-
// `{@function, {parameter: a, body: {@lookup, {key: parameter}}}}`) should not be `a`.
79-
isExpression(scope)
80-
? option.none
81-
: applyKeyPathToSemanticGraph(scope, [key]),
82-
right: functionExpression =>
83-
functionExpression[1].parameter === key
84-
? // Keep an unelaborated `@lookup` around for resolution when the `@function` is called.
85-
option.makeSome(makeLookupExpression(key))
86-
: option.none,
87-
}),
94+
type LookupResult =
95+
| {
96+
readonly kind: 'found'
97+
readonly foundValue: SemanticGraph
98+
}
99+
| {
100+
readonly kind: 'notFound'
101+
readonly nextLocationToCheckFrom: KeyPath
102+
}
103+
104+
const result: LookupResult = option.match(
105+
expressionCurrentScopeIsArgumentOf,
106+
{
107+
some: parentExpression => {
108+
const parentFunctionResult = readFunctionExpression(parentExpression)
109+
// If enclosed in a `@function` expression, allow looking up the parameter.
110+
if (
111+
either.isRight(parentFunctionResult) &&
112+
parentFunctionResult.value[1].parameter === key
113+
) {
114+
// Keep an unelaborated `@lookup` around for resolution when the `@function` is called.
115+
return {
116+
kind: 'found',
117+
foundValue: makeLookupExpression(key),
118+
}
119+
} else {
120+
return {
121+
kind: 'notFound',
122+
// Skip a level; don't consider expression properties as potential `@lookup` targets.
123+
nextLocationToCheckFrom: pathToParentScope,
124+
}
125+
}
126+
},
127+
none: _ =>
128+
option.match(
129+
option.flatMap(
130+
applyKeyPathToSemanticGraph(context.program, pathToCurrentScope),
131+
currentScope => applyKeyPathToSemanticGraph(currentScope, [key]),
132+
),
133+
{
134+
some: foundValue => ({
135+
kind: 'found',
136+
foundValue,
137+
}),
138+
none: _ => ({
139+
kind: 'notFound',
140+
nextLocationToCheckFrom: pathToCurrentScope,
141+
}),
142+
},
143+
),
144+
},
88145
)
89146

90-
return option.match(possibleLookedUpValue, {
91-
none: () =>
92-
// Try the parent scope.
93-
lookup({
94-
key,
95-
context: {
96-
keywordHandlers: context.keywordHandlers,
97-
location: pathToCurrentScope,
98-
program: context.program,
99-
},
100-
}),
101-
some: lookedUpValue => either.makeRight(option.makeSome(lookedUpValue)),
102-
})
147+
if (result.kind === 'found') {
148+
return either.makeRight(option.makeSome(result.foundValue))
149+
} else {
150+
// Try the parent scope.
151+
return lookup({
152+
key,
153+
context: {
154+
keywordHandlers: context.keywordHandlers,
155+
location: result.nextLocationToCheckFrom,
156+
program: context.program,
157+
},
158+
})
159+
}
103160
}
104161
}
105162

0 commit comments

Comments
 (0)