From d299777d8b3e45449c140bcf343f6daa4200afa0 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Fri, 9 May 2025 09:57:33 -0400 Subject: [PATCH 1/2] Move `keyPathToLookupExpression` --- src/language/semantics.ts | 1 + .../expressions/lookup-expression.ts | 15 +++++++++++++ src/language/semantics/key-path.ts | 1 + .../semantics/stdlib/stdlib-utilities.ts | 21 ++----------------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/language/semantics.ts b/src/language/semantics.ts index 7c1afa7..58efc1d 100644 --- a/src/language/semantics.ts +++ b/src/language/semantics.ts @@ -35,6 +35,7 @@ export { type IndexExpression, } from './semantics/expressions/index-expression.js' export { + keyPathToLookupExpression, makeLookupExpression, readLookupExpression, type LookupExpression, diff --git a/src/language/semantics/expressions/lookup-expression.ts b/src/language/semantics/expressions/lookup-expression.ts index 6970663..3988534 100644 --- a/src/language/semantics/expressions/lookup-expression.ts +++ b/src/language/semantics/expressions/lookup-expression.ts @@ -2,6 +2,7 @@ import either, { type Either } from '@matt.kantor/either' import type { ElaborationError } from '../../errors.js' import type { Atom, Molecule } from '../../parsing.js' import { isExpressionWithArgument } from '../expression.js' +import { keyPathToMolecule, type NonEmptyKeyPath } from '../key-path.js' import { makeObjectNode, type ObjectNode } from '../object-node.js' import { stringifySemanticGraphForEndUser, @@ -11,6 +12,7 @@ import { asSemanticGraph, readArgumentsFromExpression, } from './expression-utilities.js' +import { makeIndexExpression } from './index-expression.js' export type LookupExpression = ObjectNode & { readonly 0: '@lookup' @@ -45,3 +47,16 @@ export const makeLookupExpression = (key: Atom): LookupExpression => 0: '@lookup', 1: makeObjectNode({ key }), }) + +export const keyPathToLookupExpression = (keyPath: NonEmptyKeyPath) => { + const [initialKey, ...indexes] = keyPath + const initialLookup = makeLookupExpression(initialKey) + if (indexes.length === 0) { + return initialLookup + } else { + return makeIndexExpression({ + object: initialLookup, + query: keyPathToMolecule(indexes), + }) + } +} diff --git a/src/language/semantics/key-path.ts b/src/language/semantics/key-path.ts index 0c0e55f..55f1ecb 100644 --- a/src/language/semantics/key-path.ts +++ b/src/language/semantics/key-path.ts @@ -5,6 +5,7 @@ import { inlinePlz, unparse } from '../unparsing.js' import type { ObjectNode } from './object-node.js' export type KeyPath = readonly Atom[] +export type NonEmptyKeyPath = readonly [Atom, ...KeyPath] export const stringifyKeyPathForEndUser = (keyPath: KeyPath): string => either.match(unparse(keyPathToMolecule(keyPath), inlinePlz), { diff --git a/src/language/semantics/stdlib/stdlib-utilities.ts b/src/language/semantics/stdlib/stdlib-utilities.ts index 6123c18..782c391 100644 --- a/src/language/semantics/stdlib/stdlib-utilities.ts +++ b/src/language/semantics/stdlib/stdlib-utilities.ts @@ -1,14 +1,12 @@ import either, { type Either } from '@matt.kantor/either' import option from '@matt.kantor/option' import type { DependencyUnavailable, Panic } from '../../errors.js' -import type { Atom } from '../../parsing.js' import { + keyPathToLookupExpression, makeApplyExpression, - makeIndexExpression, - makeLookupExpression, } from '../../semantics.js' import { makeFunctionNode } from '../function-node.js' -import { keyPathToMolecule, type KeyPath } from '../key-path.js' +import { type NonEmptyKeyPath } from '../key-path.js' import { containsAnyUnelaboratedNodes, type SemanticGraph, @@ -34,21 +32,6 @@ const handleUnavailableDependencies = } } -type NonEmptyKeyPath = readonly [Atom, ...KeyPath] - -const keyPathToLookupExpression = (keyPath: NonEmptyKeyPath) => { - const [initialKey, ...indexes] = keyPath - const initialLookup = makeLookupExpression(initialKey) - if (indexes.length === 0) { - return initialLookup - } else { - return makeIndexExpression({ - object: initialLookup, - query: keyPathToMolecule(indexes), - }) - } -} - export const serializeOnceAppliedFunction = (keyPath: NonEmptyKeyPath, argument: SemanticGraph) => () => either.makeRight( From cf5f60270a52c60fed02dcbc996d845e8c70b8ea Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Fri, 9 May 2025 09:58:54 -0400 Subject: [PATCH 2/2] Make runtime context functions serializable --- src/language/runtime/keywords.ts | 214 +++++++++++++++++-------------- 1 file changed, 117 insertions(+), 97 deletions(-) diff --git a/src/language/runtime/keywords.ts b/src/language/runtime/keywords.ts index b8d2790..61495f9 100644 --- a/src/language/runtime/keywords.ts +++ b/src/language/runtime/keywords.ts @@ -1,10 +1,11 @@ import either from '@matt.kantor/either' -import option from '@matt.kantor/option' +import option, { type Option } from '@matt.kantor/option' import { parseArgs } from 'node:util' import { writeOutput } from '../cli/output.js' import { keywordHandlers as compilerKeywordHandlers } from '../compiling.js' import { isFunctionNode, + keyPathToLookupExpression, makeFunctionNode, makeObjectNode, readRuntimeExpression, @@ -12,116 +13,133 @@ import { types, type KeywordHandlers, } from '../semantics.js' +import type { NonEmptyKeyPath } from '../semantics/key-path.js' import { prettyJson } from '../unparsing.js' -const unserializableFunction = () => - either.makeLeft({ - kind: 'unserializableValue', - message: 'this function cannot be serialized', - }) +const serializeFunction = + (runtimeFunctionParameterName: Option) => + (keyPath: NonEmptyKeyPath) => { + const serialize = either.map( + option.match(runtimeFunctionParameterName, { + none: _ => + either.makeLeft({ + kind: 'unserializableValue', + message: 'the runtime function cannot be serialized', + }), + some: either.makeRight, + }), + parameterName => keyPathToLookupExpression([parameterName, ...keyPath]), + ) + return () => serialize + } -const runtimeContext = makeObjectNode({ - arguments: makeObjectNode({ - lookup: makeFunctionNode( - { - parameter: types.atom, - return: types.option(types.atom), - }, - unserializableFunction, - option.none, - key => { - if (typeof key !== 'string') { - return either.makeLeft({ - kind: 'panic', - message: 'key was not an atom', - }) - } else { - const { values: argumentValues } = parseArgs({ - args: process.argv, - strict: false, - options: { - [key]: { type: 'string' }, - }, - }) - const argument = argumentValues[key] - if (typeof argument !== 'string') { - return either.makeRight( - makeObjectNode({ - tag: 'none', - value: makeObjectNode({}), - }), - ) +const runtimeContext = (runtimeFunctionParameterName: Option) => { + const serializeRuntimeContextFunction = serializeFunction( + runtimeFunctionParameterName, + ) + return makeObjectNode({ + arguments: makeObjectNode({ + lookup: makeFunctionNode( + { + parameter: types.atom, + return: types.option(types.atom), + }, + serializeRuntimeContextFunction(['arguments', 'lookup']), + option.none, + key => { + if (typeof key !== 'string') { + return either.makeLeft({ + kind: 'panic', + message: 'key was not an atom', + }) } else { - return either.makeRight( - makeObjectNode({ - tag: 'some', - value: argument, - }), - ) + const { values: argumentValues } = parseArgs({ + args: process.argv, + strict: false, + options: { + [key]: { type: 'string' }, + }, + }) + const argument = argumentValues[key] + if (typeof argument !== 'string') { + return either.makeRight( + makeObjectNode({ + tag: 'none', + value: makeObjectNode({}), + }), + ) + } else { + return either.makeRight( + makeObjectNode({ + tag: 'some', + value: argument, + }), + ) + } } - } - }, - ), - }), - environment: makeObjectNode({ - lookup: makeFunctionNode( + }, + ), + }), + environment: makeObjectNode({ + lookup: makeFunctionNode( + { + parameter: types.atom, + return: types.option(types.atom), + }, + serializeRuntimeContextFunction(['environment', 'lookup']), + option.none, + key => { + if (typeof key !== 'string') { + return either.makeLeft({ + kind: 'panic', + message: 'key was not an atom', + }) + } else { + const environmentVariable = process.env[key] + if (environmentVariable === undefined) { + return either.makeRight( + makeObjectNode({ + tag: 'none', + value: makeObjectNode({}), + }), + ) + } else { + return either.makeRight( + makeObjectNode({ + tag: 'some', + value: environmentVariable, + }), + ) + } + } + }, + ), + }), + log: makeFunctionNode( { - parameter: types.atom, - return: types.option(types.atom), + parameter: types.something, + return: types.object, }, - unserializableFunction, + serializeRuntimeContextFunction(['log']), option.none, - key => { - if (typeof key !== 'string') { + output => { + const serializationResult = serialize(output) + if (either.isLeft(serializationResult)) { return either.makeLeft({ kind: 'panic', - message: 'key was not an atom', + message: serializationResult.value.message, }) } else { - const environmentVariable = process.env[key] - if (environmentVariable === undefined) { - return either.makeRight( - makeObjectNode({ - tag: 'none', - value: makeObjectNode({}), - }), - ) - } else { - return either.makeRight( - makeObjectNode({ - tag: 'some', - value: environmentVariable, - }), - ) - } + writeOutput(process.stderr, prettyJson, serializationResult.value) + return either.makeRight(output) } }, ), - }), - log: makeFunctionNode( - { - parameter: types.something, - return: types.object, - }, - unserializableFunction, - option.none, - output => { - const serializationResult = serialize(output) - if (either.isLeft(serializationResult)) { - return either.makeLeft({ - kind: 'panic', - message: serializationResult.value.message, - }) - } else { - writeOutput(process.stderr, prettyJson, serializationResult.value) - return either.makeRight(output) - } - }, - ), - program: makeObjectNode({ - start_time: new Date().toISOString(), - }), -}) + program: makeObjectNode({ + start_time: new Date().toISOString(), + }), + }) +} export const keywordHandlers: KeywordHandlers = { ...compilerKeywordHandlers, @@ -139,7 +157,9 @@ export const keywordHandlers: KeywordHandlers = { 'a function must be provided via the property `function` or `0`', }) } else { - const result = runtimeFunction(runtimeContext) + const result = runtimeFunction( + runtimeContext(runtimeFunction.parameterName), + ) if (either.isLeft(result)) { // The runtime function panicked or had an unavailable dependency (which results in a panic // anyway in this context).