Skip to content

Commit dd9cc36

Browse files
committed
Statically fix the set of keywords
1 parent aa4d611 commit dd9cc36

File tree

10 files changed

+64
-68
lines changed

10 files changed

+64
-68
lines changed

src/language/compiling.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { compile } from './compiling/compiler.js'
2-
export { handlers as keywordHandlers } from './compiling/semantics/keywords.js'
2+
export { keywordHandlers } from './compiling/semantics/keywords.js'

src/language/compiling/compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import type { CompilationError } from '../errors.js'
33
import type { JSONValueForbiddingSymbolicKeys } from '../parsing.js'
44
import { canonicalize } from '../parsing.js'
55
import { elaborate, serialize, type Output } from '../semantics.js'
6-
import * as keywordModule from './semantics/keywords.js'
6+
import { keywordHandlers } from './semantics/keywords.js'
77

88
export const compile = (
99
input: JSONValueForbiddingSymbolicKeys,
1010
): Either<CompilationError, Output> => {
1111
const syntaxTree = canonicalize(input)
12-
const semanticGraphResult = elaborate(syntaxTree, keywordModule)
12+
const semanticGraphResult = elaborate(syntaxTree, keywordHandlers)
1313
return either.flatMap(semanticGraphResult, serialize)
1414
}

src/language/compiling/semantics.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import {
1313
type ObjectNode,
1414
} from '../semantics.js'
1515
import type { SemanticGraph } from '../semantics/semantic-graph.js'
16-
import * as keywordModule from './semantics/keywords.js'
16+
import { keywordHandlers } from './semantics/keywords.js'
1717
import { prelude } from './semantics/prelude.js'
1818

1919
const elaborationSuite = testCases(
2020
(input: Atom | Molecule) =>
21-
elaborate(withPhantomData<never>()(input), keywordModule),
21+
elaborate(withPhantomData<never>()(input), keywordHandlers),
2222
input => `elaborating expressions in \`${JSON.stringify(input)}\``,
2323
)
2424

src/language/compiling/semantics/expressions/function-expression.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
type SemanticGraph,
2222
type unelaboratedKey,
2323
} from '../../../semantics/semantic-graph.js'
24-
import { handlers, isKeyword } from '../keywords.js'
24+
import { keywordHandlers } from '../keywords.js'
2525
import {
2626
asSemanticGraph,
2727
readArgumentsFromExpression,
@@ -115,7 +115,9 @@ const apply = (
115115
updatedProgram =>
116116
elaborateWithContext(
117117
serializedBody,
118-
{ handlers, isKeyword },
118+
// TODO: This should use compile-time or runtime handlers when appropriate. Perhaps
119+
// keyword handlers should be part of `ExpressionContext`?
120+
keywordHandlers,
119121
{
120122
location: [...context.location, returnKey],
121123
program: updatedProgram,

src/language/compiling/semantics/keywords.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type KeywordModule } from '../../semantics.js'
1+
import { type KeywordHandlers } from '../../semantics.js'
22
import {
33
applyKeyword,
44
applyKeywordHandler,
@@ -24,7 +24,7 @@ import {
2424
todoKeywordHandler,
2525
} from './expressions/todo-expression.js'
2626

27-
export const handlers = {
27+
export const keywordHandlers: KeywordHandlers = {
2828
/**
2929
* Calls the given function with a given argument.
3030
*/
@@ -54,11 +54,4 @@ export const handlers = {
5454
* Ignores all properties and evaluates to an empty object.
5555
*/
5656
[todoKeyword]: todoKeywordHandler,
57-
} satisfies KeywordModule<`@${string}`>['handlers']
58-
59-
export type Keyword = keyof typeof handlers
60-
61-
// `isKeyword` is correct as long as `handlers` does not have excess properties.
62-
const allKeywords = new Set(Object.keys(handlers))
63-
export const isKeyword = (input: string): input is Keyword =>
64-
allKeywords.has(input)
57+
}

src/language/runtime/evaluator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import type { RuntimeError } from '../errors.js'
33
import type { JSONValueForbiddingSymbolicKeys } from '../parsing.js'
44
import { canonicalize } from '../parsing.js'
55
import { elaborate, serialize, type Output } from '../semantics.js'
6-
import * as keywordModule from './keywords.js'
6+
import { keywordHandlers } from './keywords.js'
77

88
export const evaluate = (
99
input: JSONValueForbiddingSymbolicKeys,
1010
): Either<RuntimeError, Output> => {
1111
const syntaxTree = canonicalize(input)
12-
const semanticGraphResult = elaborate(syntaxTree, keywordModule)
12+
const semanticGraphResult = elaborate(syntaxTree, keywordHandlers)
1313
return either.flatMap(semanticGraphResult, serialize)
1414
}

src/language/runtime/keywords.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {
1010
serialize,
1111
types,
1212
type Expression,
13-
type KeywordElaborationResult,
14-
type KeywordModule,
13+
type KeywordHandlers,
1514
type SemanticGraph,
1615
} from '../semantics.js'
1716
import { lookupPropertyOfObjectNode } from '../semantics/object-node.js'
@@ -125,12 +124,12 @@ const runtimeContext = makeObjectNode({
125124
}),
126125
})
127126

128-
export const handlers = {
127+
export const keywordHandlers: KeywordHandlers = {
129128
...compilerKeywordHandlers,
130129
/**
131130
* Evaluates the given function, passing runtime context captured in `world`.
132131
*/
133-
'@runtime': (expression): KeywordElaborationResult => {
132+
'@runtime': expression => {
134133
const runtimeFunction = lookupWithinExpression(
135134
['function', '1'],
136135
expression,
@@ -158,14 +157,7 @@ export const handlers = {
158157
}
159158
}
160159
},
161-
} satisfies KeywordModule<`@${string}`>['handlers']
162-
163-
export type Keyword = keyof typeof handlers
164-
165-
// `isKeyword` is correct as long as `handlers` does not have excess properties.
166-
const allKeywords = new Set(Object.keys(handlers))
167-
export const isKeyword = (input: string): input is Keyword =>
168-
allKeywords.has(input)
160+
}
169161

170162
const lookupWithinExpression = (
171163
keyAliases: [Atom, ...(readonly Atom[])],

src/language/semantics.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export {
33
type ElaboratedSemanticGraph,
44
type ExpressionContext,
55
type KeywordElaborationResult,
6-
type KeywordModule,
6+
type KeywordHandlers,
77
} from './semantics/expression-elaboration.js'
88
export { isExpression, type Expression } from './semantics/expression.js'
99
export {
@@ -12,6 +12,7 @@ export {
1212
type FunctionNode,
1313
} from './semantics/function-node.js'
1414
export { type KeyPath } from './semantics/key-path.js'
15+
export { isKeyword, type Keyword } from './semantics/keyword.js'
1516
export {
1617
isObjectNode,
1718
makeObjectNode,

src/language/semantics/expression-elaboration.ts

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { withPhantomData, type WithPhantomData } from '../../phantom-data.js'
33
import type { Writable } from '../../utility-types.js'
44
import type { ElaborationError, InvalidSyntaxTreeError } from '../errors.js'
55
import type { Atom, Molecule, SyntaxTree } from '../parsing.js'
6+
import type { Expression } from './expression.js'
7+
import type { KeyPath } from './key-path.js'
8+
import { isKeyword, type Keyword } from './keyword.js'
69
import {
710
makeObjectNode,
811
makeUnelaboratedObjectNode,
9-
type KeyPath,
1012
type ObjectNode,
11-
} from '../semantics.js'
12-
import type { Expression } from './expression.js'
13+
} from './object-node.js'
1314
import {
1415
extractStringValueIfPossible,
1516
updateValueAtKeyPathInSemanticGraph,
@@ -32,16 +33,13 @@ export type KeywordHandler = (
3233
context: ExpressionContext,
3334
) => KeywordElaborationResult
3435

35-
export type KeywordModule<Keyword extends `@${string}`> = {
36-
readonly isKeyword: (input: string) => input is Keyword
37-
readonly handlers: Readonly<Record<Keyword, KeywordHandler>>
38-
}
36+
export type KeywordHandlers = Readonly<Record<Keyword, KeywordHandler>>
3937

4038
export const elaborate = (
4139
program: SyntaxTree,
42-
keywordModule: KeywordModule<`@${string}`>,
40+
keywordHandlers: KeywordHandlers,
4341
): Either<ElaborationError, ElaboratedSemanticGraph> =>
44-
elaborateWithContext(program, keywordModule, {
42+
elaborateWithContext(program, keywordHandlers, {
4543
location: [],
4644
program:
4745
typeof program === 'string'
@@ -51,19 +49,19 @@ export const elaborate = (
5149

5250
export const elaborateWithContext = (
5351
program: SyntaxTree,
54-
keywordModule: KeywordModule<`@${string}`>,
52+
keywordHandlers: KeywordHandlers,
5553
context: ExpressionContext,
5654
): Either<ElaborationError, ElaboratedSemanticGraph> =>
5755
either.map(
5856
typeof program === 'string'
5957
? handleAtomWhichMayNotBeAKeyword(program)
60-
: elaborateWithinMolecule(program, keywordModule, context),
58+
: elaborateWithinMolecule(program, keywordHandlers, context),
6159
withPhantomData<Elaborated>(),
6260
)
6361

6462
const elaborateWithinMolecule = (
6563
molecule: Molecule,
66-
keywordModule: KeywordModule<`@${string}`>,
64+
keywordHandlers: KeywordHandlers,
6765
context: ExpressionContext,
6866
): Either<ElaborationError, SemanticGraph> => {
6967
const possibleExpressionAsObjectNode: Writable<ObjectNode> = makeObjectNode(
@@ -83,7 +81,7 @@ const elaborateWithinMolecule = (
8381
} else {
8482
const elaborationResult = elaborateWithinMolecule(
8583
value,
86-
keywordModule,
84+
keywordHandlers,
8785
{
8886
location: [...context.location, key],
8987
program: updatedProgram,
@@ -148,7 +146,7 @@ const elaborateWithinMolecule = (
148146
...possibleExpressionAsObjectNode,
149147
0: possibleKeywordAsString,
150148
},
151-
keywordModule,
149+
keywordHandlers,
152150
{
153151
program: updatedProgram,
154152
location: context.location,
@@ -158,32 +156,29 @@ const elaborateWithinMolecule = (
158156
}
159157
}
160158

161-
const handleObjectNodeWhichMayBeAExpression = <Keyword extends `@${string}`>(
159+
const handleObjectNodeWhichMayBeAExpression = (
162160
node: ObjectNode & { readonly 0: Atom },
163-
keywordModule: KeywordModule<Keyword>,
161+
keywordHandlers: KeywordHandlers,
164162
context: ExpressionContext,
165163
): Either<ElaborationError, SemanticGraph> => {
166164
const { 0: possibleKeyword, ...possibleArguments } = node
167-
return option.match(
168-
option.fromPredicate(possibleKeyword, keywordModule.isKeyword),
169-
{
170-
none: () =>
171-
/^@[^@]/.test(possibleKeyword)
172-
? either.makeLeft({
173-
kind: 'unknownKeyword',
174-
message: `unknown keyword: \`${possibleKeyword}\``,
175-
})
176-
: either.makeRight({
177-
...node,
178-
0: unescapeKeywordSigil(possibleKeyword),
179-
}),
180-
some: keyword =>
181-
keywordModule.handlers[keyword](
182-
makeObjectNode({ ...possibleArguments, 0: keyword }),
183-
context,
184-
),
185-
},
186-
)
165+
return option.match(option.fromPredicate(possibleKeyword, isKeyword), {
166+
none: () =>
167+
/^@[^@]/.test(possibleKeyword)
168+
? either.makeLeft({
169+
kind: 'unknownKeyword',
170+
message: `unknown keyword: \`${possibleKeyword}\``,
171+
})
172+
: either.makeRight({
173+
...node,
174+
0: unescapeKeywordSigil(possibleKeyword),
175+
}),
176+
some: keyword =>
177+
keywordHandlers[keyword](
178+
makeObjectNode({ ...possibleArguments, 0: keyword }),
179+
context,
180+
),
181+
})
187182
}
188183

189184
const handleAtomWhichMayNotBeAKeyword = (

src/language/semantics/keyword.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const isKeyword = (input: string) =>
2+
input === '@apply' ||
3+
input === '@check' ||
4+
input === '@function' ||
5+
input === '@lookup' ||
6+
input === '@runtime' ||
7+
input === '@todo'
8+
9+
export type Keyword = typeof isKeyword extends (
10+
input: string,
11+
) => input is string & infer Keyword
12+
? Keyword
13+
: never

0 commit comments

Comments
 (0)