Skip to content

Commit 884904b

Browse files
authored
Merge pull request #68 from mkantor/serializable-runtime-context
Make runtime context functions serializable
2 parents f57a3ed + cf5f602 commit 884904b

File tree

5 files changed

+136
-116
lines changed

5 files changed

+136
-116
lines changed

src/language/runtime/keywords.ts

Lines changed: 117 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,145 @@
11
import either from '@matt.kantor/either'
2-
import option from '@matt.kantor/option'
2+
import option, { type Option } from '@matt.kantor/option'
33
import { parseArgs } from 'node:util'
44
import { writeOutput } from '../cli/output.js'
55
import { keywordHandlers as compilerKeywordHandlers } from '../compiling.js'
66
import {
77
isFunctionNode,
8+
keyPathToLookupExpression,
89
makeFunctionNode,
910
makeObjectNode,
1011
readRuntimeExpression,
1112
serialize,
1213
types,
1314
type KeywordHandlers,
1415
} from '../semantics.js'
16+
import type { NonEmptyKeyPath } from '../semantics/key-path.js'
1517
import { prettyJson } from '../unparsing.js'
1618

17-
const unserializableFunction = () =>
18-
either.makeLeft({
19-
kind: 'unserializableValue',
20-
message: 'this function cannot be serialized',
21-
})
19+
const serializeFunction =
20+
(runtimeFunctionParameterName: Option<string>) =>
21+
(keyPath: NonEmptyKeyPath) => {
22+
const serialize = either.map(
23+
option.match(runtimeFunctionParameterName, {
24+
none: _ =>
25+
either.makeLeft({
26+
kind: 'unserializableValue',
27+
message: 'the runtime function cannot be serialized',
28+
}),
29+
some: either.makeRight,
30+
}),
31+
parameterName => keyPathToLookupExpression([parameterName, ...keyPath]),
32+
)
33+
return () => serialize
34+
}
2235

23-
const runtimeContext = makeObjectNode({
24-
arguments: makeObjectNode({
25-
lookup: makeFunctionNode(
26-
{
27-
parameter: types.atom,
28-
return: types.option(types.atom),
29-
},
30-
unserializableFunction,
31-
option.none,
32-
key => {
33-
if (typeof key !== 'string') {
34-
return either.makeLeft({
35-
kind: 'panic',
36-
message: 'key was not an atom',
37-
})
38-
} else {
39-
const { values: argumentValues } = parseArgs({
40-
args: process.argv,
41-
strict: false,
42-
options: {
43-
[key]: { type: 'string' },
44-
},
45-
})
46-
const argument = argumentValues[key]
47-
if (typeof argument !== 'string') {
48-
return either.makeRight(
49-
makeObjectNode({
50-
tag: 'none',
51-
value: makeObjectNode({}),
52-
}),
53-
)
36+
const runtimeContext = (runtimeFunctionParameterName: Option<string>) => {
37+
const serializeRuntimeContextFunction = serializeFunction(
38+
runtimeFunctionParameterName,
39+
)
40+
return makeObjectNode({
41+
arguments: makeObjectNode({
42+
lookup: makeFunctionNode(
43+
{
44+
parameter: types.atom,
45+
return: types.option(types.atom),
46+
},
47+
serializeRuntimeContextFunction(['arguments', 'lookup']),
48+
option.none,
49+
key => {
50+
if (typeof key !== 'string') {
51+
return either.makeLeft({
52+
kind: 'panic',
53+
message: 'key was not an atom',
54+
})
5455
} else {
55-
return either.makeRight(
56-
makeObjectNode({
57-
tag: 'some',
58-
value: argument,
59-
}),
60-
)
56+
const { values: argumentValues } = parseArgs({
57+
args: process.argv,
58+
strict: false,
59+
options: {
60+
[key]: { type: 'string' },
61+
},
62+
})
63+
const argument = argumentValues[key]
64+
if (typeof argument !== 'string') {
65+
return either.makeRight(
66+
makeObjectNode({
67+
tag: 'none',
68+
value: makeObjectNode({}),
69+
}),
70+
)
71+
} else {
72+
return either.makeRight(
73+
makeObjectNode({
74+
tag: 'some',
75+
value: argument,
76+
}),
77+
)
78+
}
6179
}
62-
}
63-
},
64-
),
65-
}),
66-
environment: makeObjectNode({
67-
lookup: makeFunctionNode(
80+
},
81+
),
82+
}),
83+
environment: makeObjectNode({
84+
lookup: makeFunctionNode(
85+
{
86+
parameter: types.atom,
87+
return: types.option(types.atom),
88+
},
89+
serializeRuntimeContextFunction(['environment', 'lookup']),
90+
option.none,
91+
key => {
92+
if (typeof key !== 'string') {
93+
return either.makeLeft({
94+
kind: 'panic',
95+
message: 'key was not an atom',
96+
})
97+
} else {
98+
const environmentVariable = process.env[key]
99+
if (environmentVariable === undefined) {
100+
return either.makeRight(
101+
makeObjectNode({
102+
tag: 'none',
103+
value: makeObjectNode({}),
104+
}),
105+
)
106+
} else {
107+
return either.makeRight(
108+
makeObjectNode({
109+
tag: 'some',
110+
value: environmentVariable,
111+
}),
112+
)
113+
}
114+
}
115+
},
116+
),
117+
}),
118+
log: makeFunctionNode(
68119
{
69-
parameter: types.atom,
70-
return: types.option(types.atom),
120+
parameter: types.something,
121+
return: types.object,
71122
},
72-
unserializableFunction,
123+
serializeRuntimeContextFunction(['log']),
73124
option.none,
74-
key => {
75-
if (typeof key !== 'string') {
125+
output => {
126+
const serializationResult = serialize(output)
127+
if (either.isLeft(serializationResult)) {
76128
return either.makeLeft({
77129
kind: 'panic',
78-
message: 'key was not an atom',
130+
message: serializationResult.value.message,
79131
})
80132
} else {
81-
const environmentVariable = process.env[key]
82-
if (environmentVariable === undefined) {
83-
return either.makeRight(
84-
makeObjectNode({
85-
tag: 'none',
86-
value: makeObjectNode({}),
87-
}),
88-
)
89-
} else {
90-
return either.makeRight(
91-
makeObjectNode({
92-
tag: 'some',
93-
value: environmentVariable,
94-
}),
95-
)
96-
}
133+
writeOutput(process.stderr, prettyJson, serializationResult.value)
134+
return either.makeRight(output)
97135
}
98136
},
99137
),
100-
}),
101-
log: makeFunctionNode(
102-
{
103-
parameter: types.something,
104-
return: types.object,
105-
},
106-
unserializableFunction,
107-
option.none,
108-
output => {
109-
const serializationResult = serialize(output)
110-
if (either.isLeft(serializationResult)) {
111-
return either.makeLeft({
112-
kind: 'panic',
113-
message: serializationResult.value.message,
114-
})
115-
} else {
116-
writeOutput(process.stderr, prettyJson, serializationResult.value)
117-
return either.makeRight(output)
118-
}
119-
},
120-
),
121-
program: makeObjectNode({
122-
start_time: new Date().toISOString(),
123-
}),
124-
})
138+
program: makeObjectNode({
139+
start_time: new Date().toISOString(),
140+
}),
141+
})
142+
}
125143

126144
export const keywordHandlers: KeywordHandlers = {
127145
...compilerKeywordHandlers,
@@ -139,7 +157,9 @@ export const keywordHandlers: KeywordHandlers = {
139157
'a function must be provided via the property `function` or `0`',
140158
})
141159
} else {
142-
const result = runtimeFunction(runtimeContext)
160+
const result = runtimeFunction(
161+
runtimeContext(runtimeFunction.parameterName),
162+
)
143163
if (either.isLeft(result)) {
144164
// The runtime function panicked or had an unavailable dependency (which results in a panic
145165
// anyway in this context).

src/language/semantics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export {
3535
type IndexExpression,
3636
} from './semantics/expressions/index-expression.js'
3737
export {
38+
keyPathToLookupExpression,
3839
makeLookupExpression,
3940
readLookupExpression,
4041
type LookupExpression,

src/language/semantics/expressions/lookup-expression.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import either, { type Either } from '@matt.kantor/either'
22
import type { ElaborationError } from '../../errors.js'
33
import type { Atom, Molecule } from '../../parsing.js'
44
import { isExpressionWithArgument } from '../expression.js'
5+
import { keyPathToMolecule, type NonEmptyKeyPath } from '../key-path.js'
56
import { makeObjectNode, type ObjectNode } from '../object-node.js'
67
import {
78
stringifySemanticGraphForEndUser,
@@ -11,6 +12,7 @@ import {
1112
asSemanticGraph,
1213
readArgumentsFromExpression,
1314
} from './expression-utilities.js'
15+
import { makeIndexExpression } from './index-expression.js'
1416

1517
export type LookupExpression = ObjectNode & {
1618
readonly 0: '@lookup'
@@ -45,3 +47,16 @@ export const makeLookupExpression = (key: Atom): LookupExpression =>
4547
0: '@lookup',
4648
1: makeObjectNode({ key }),
4749
})
50+
51+
export const keyPathToLookupExpression = (keyPath: NonEmptyKeyPath) => {
52+
const [initialKey, ...indexes] = keyPath
53+
const initialLookup = makeLookupExpression(initialKey)
54+
if (indexes.length === 0) {
55+
return initialLookup
56+
} else {
57+
return makeIndexExpression({
58+
object: initialLookup,
59+
query: keyPathToMolecule(indexes),
60+
})
61+
}
62+
}

src/language/semantics/key-path.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { inlinePlz, unparse } from '../unparsing.js'
55
import type { ObjectNode } from './object-node.js'
66

77
export type KeyPath = readonly Atom[]
8+
export type NonEmptyKeyPath = readonly [Atom, ...KeyPath]
89

910
export const stringifyKeyPathForEndUser = (keyPath: KeyPath): string =>
1011
either.match(unparse(keyPathToMolecule(keyPath), inlinePlz), {

src/language/semantics/stdlib/stdlib-utilities.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import either, { type Either } from '@matt.kantor/either'
22
import option from '@matt.kantor/option'
33
import type { DependencyUnavailable, Panic } from '../../errors.js'
4-
import type { Atom } from '../../parsing.js'
54
import {
5+
keyPathToLookupExpression,
66
makeApplyExpression,
7-
makeIndexExpression,
8-
makeLookupExpression,
97
} from '../../semantics.js'
108
import { makeFunctionNode } from '../function-node.js'
11-
import { keyPathToMolecule, type KeyPath } from '../key-path.js'
9+
import { type NonEmptyKeyPath } from '../key-path.js'
1210
import {
1311
containsAnyUnelaboratedNodes,
1412
type SemanticGraph,
@@ -34,21 +32,6 @@ const handleUnavailableDependencies =
3432
}
3533
}
3634

37-
type NonEmptyKeyPath = readonly [Atom, ...KeyPath]
38-
39-
const keyPathToLookupExpression = (keyPath: NonEmptyKeyPath) => {
40-
const [initialKey, ...indexes] = keyPath
41-
const initialLookup = makeLookupExpression(initialKey)
42-
if (indexes.length === 0) {
43-
return initialLookup
44-
} else {
45-
return makeIndexExpression({
46-
object: initialLookup,
47-
query: keyPathToMolecule(indexes),
48-
})
49-
}
50-
}
51-
5235
export const serializeOnceAppliedFunction =
5336
(keyPath: NonEmptyKeyPath, argument: SemanticGraph) => () =>
5437
either.makeRight(

0 commit comments

Comments
 (0)