Merge pull request #666 from quantified-uncertainty/reducer-modules

Reducer modules
This commit is contained in:
Ozzie Gooen 2022-06-11 07:18:53 -07:00 committed by GitHub
commit 2ced133889
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 158 additions and 74 deletions

View File

@ -0,0 +1,19 @@
module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface.ExpressionValue
module ErrorValue = Reducer_ErrorValue
module Bindings = Reducer_Category_Bindings
let removeDefaults = (ev: ExpressionT.expressionValue): ExpressionT.expressionValue =>
switch ev {
| EvRecord(extbindings) => {
let bindings: Bindings.t = Bindings.fromRecord(extbindings)
let keys = Js.Dict.keys(Reducer.defaultExternalBindings)
Belt.Map.String.keep(bindings, (key, _value) => {
let removeThis = Js.Array2.includes(keys, key)
!removeThis
})->Bindings.toExpressionValue
}
| value => value
}
let rRemoveDefaults = r => Belt.Result.map(r, ev => removeDefaults(ev))

View File

@ -235,6 +235,9 @@ describe("Peggy parse", () => {
testParse("1M", "{(::fromUnit_M 1)}")
testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}")
})
describe("Module", () => {
testParse("Math.pi", "{(::$_atIndex_$ @Math 'pi')}")
})
})
describe("parsing new line", () => {

View File

@ -23,8 +23,13 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
let a2 =
rExpr
->Result.flatMap(expr =>
Expression.reduceExpression(expr, Belt.Map.String.empty, ExpressionValue.defaultEnvironment)
Expression.reduceExpression(
expr,
ReducerInterface_DefaultExternalBindings.defaultInternalBindings,
ExpressionValue.defaultEnvironment,
)
)
->Reducer_Helpers.rRemoveDefaults
->ExpressionValue.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v))
}

View File

@ -181,4 +181,8 @@ describe("Peggy to Expression", () => {
(),
)
})
describe("module", () => {
testToExpression("Math.pi", "{(:$_atIndex_$ :Math 'pi')}", ~v="3.141592653589793", ())
})
})

View File

@ -1,6 +1,7 @@
module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface.ExpressionValue
module ErrorValue = Reducer_ErrorValue
module Bindings = Reducer_Category_Bindings
open Jest
open Expect
@ -17,7 +18,11 @@ let expectParseToBe = (expr: string, answer: string) =>
Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer)
let expectEvalToBe = (expr: string, answer: string) =>
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
Reducer.evaluate(expr)
->Reducer_Helpers.rRemoveDefaults
->ExpressionValue.toStringResult
->expect
->toBe(answer)
let expectEvalError = (expr: string) =>
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toMatch("Error\(")

View File

@ -0,0 +1,7 @@
open Jest
open Reducer_TestHelpers
describe("Math Library", () => {
testEvalToBe("Math.e", "Ok(2.718281828459045)")
testEvalToBe("Math.pi", "Ok(3.141592653589793)")
})

View File

@ -187,5 +187,16 @@ function createTsExport(
return tag("lambdaDeclaration", x.value);
case "EvTypeIdentifier":
return tag("typeIdentifier", x.value);
case "EvModule":
let moduleResult: tagged<
"module",
{ [key: string]: squiggleExpression }
> = tag(
"module",
_.mapValues(x.value, (x: unknown) =>
convertRawToTypescript(x as rescriptExport, environment)
)
);
return moduleResult;
}
}

View File

@ -73,6 +73,10 @@ export type rescriptExport =
| {
TAG: 13; // EvTypeIdentifier
_0: string;
}
| {
TAG: 14; // EvModule
_0: { [key: string]: rescriptExport };
};
type rescriptDist =
@ -125,7 +129,8 @@ export type squiggleExpression =
| tagged<"timeDuration", number>
| tagged<"lambdaDeclaration", lambdaDeclaration>
| tagged<"record", { [key: string]: squiggleExpression }>
| tagged<"typeIdentifier", string>;
| tagged<"typeIdentifier", string>
| tagged<"module", { [key: string]: squiggleExpression }>;
export { lambdaValue };
@ -177,6 +182,11 @@ export function convertRawToTypescript(
});
case 13: // EvSymbol
return tag("typeIdentifier", result._0);
case 14: // EvModule
return tag(
"module",
_.mapValues(result._0, (x) => convertRawToTypescript(x, environment))
);
}
}

View File

@ -24,4 +24,4 @@ let foreignFunctionInterface = (
let defaultEnvironment = ExpressionValue.defaultEnvironment
let defaultExternalBindings = ExpressionValue.defaultExternalBindings
let defaultExternalBindings = ReducerInterface_DefaultExternalBindings.defaultExternalBindings

View File

@ -0,0 +1,12 @@
include Reducer_Category_Module // Bindings inherit from Module
open ReducerInterface_ExpressionValue
let emptyBindings = emptyModule
let toExpressionValue = (container: t): expressionValue => EvRecord(toRecord(container))
let fromExpressionValue = (aValue: expressionValue): t =>
switch aValue {
| EvRecord(r) => fromRecord(r)
| _ => emptyBindings
}

View File

@ -0,0 +1,33 @@
module ExpressionT = Reducer_Expression_T
open ReducerInterface_ExpressionValue
let expressionValueToString = toString
type t = ExpressionT.bindings
let typeAliasesKey = "_typeAliases_"
let typeReferencesKey = "_typeReferences_"
let emptyModule: t = Belt.Map.String.empty
let cloneRecord = (r: record): record => r->Js.Dict.entries->Js.Dict.fromArray
let fromRecord = (r: record): t => Js.Dict.entries(r)->Belt.Map.String.fromArray
let toRecord = (container: t): record => Belt.Map.String.toArray(container)->Js.Dict.fromArray
let toExpressionValue = (container: t): expressionValue => EvModule(toRecord(container))
let fromExpressionValue = (aValue: expressionValue): t =>
switch aValue {
| EvModule(r) => fromRecord(r)
| _ => emptyModule
}
let toString = (container: t): string => container->toRecord->EvRecord->expressionValueToString
// -- Module definition
let define = (container: t, identifier: string, ev: expressionValue): t =>
Belt.Map.String.set(container, identifier, ev) // TODO build lambda for polymorphic functions here
let defineNumber = (container: t, identifier: string, value: float): t =>
container->define(identifier, EvNumber(value))
let defineModule = (container: t, identifier: string, value: t): t =>
container->define(identifier, toExpressionValue(value))

View File

@ -256,6 +256,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
switch call {
| ("$_atIndex_$", [EvArray(aValueArray), EvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
| ("$_atIndex_$", [EvModule(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
| ("$_atIndex_$", [EvRecord(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
| ("$_constructArray_$", [EvArray(aValueArray)]) => EvArray(aValueArray)->Ok
| ("$_constructRecord_$", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)

View File

@ -123,7 +123,7 @@ let evaluateUsingOptions = (
let anExternalBindings = switch externalBindings {
| Some(bindings) => bindings
| None => ReducerInterface_ExpressionValue.defaultExternalBindings
| None => ReducerInterface_DefaultExternalBindings.defaultExternalBindings
}
let bindings = anExternalBindings->Bindings.fromExternalBindings

View File

@ -2,77 +2,25 @@ module ErrorValue = Reducer_ErrorValue
module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface.ExpressionValue
module Result = Belt.Result
module Bindings = Reducer_Category_Bindings
type errorValue = Reducer_ErrorValue.errorValue
type expression = ExpressionT.expression
type expressionValue = ExpressionValue.expressionValue
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
let defaultBindings: ExpressionT.bindings = Belt.Map.String.empty
let emptyBindings = Reducer_Category_Bindings.emptyBindings
let typeAliasesKey = "_typeAliases_"
let typeReferencesKey = "_typeReferences_"
let typeAliasesKey = Bindings.typeAliasesKey
let typeReferencesKey = Bindings.typeReferencesKey
let toExternalBindings = (bindings: ExpressionT.bindings): externalBindings => {
let keys = Belt.Map.String.keysToArray(bindings)
keys->Belt.Array.reduce(Js.Dict.empty(), (acc, key) => {
let value = bindings->Belt.Map.String.getExn(key)
Js.Dict.set(acc, key, value)
acc
})
}
let toExternalBindings = (bindings: ExpressionT.bindings): externalBindings =>
Bindings.toRecord(bindings)
let fromExternalBindings_ = (externalBindings: externalBindings): ExpressionT.bindings => {
let keys = Js.Dict.keys(externalBindings)
keys->Belt.Array.reduce(defaultBindings, (acc, key) => {
let value = Js.Dict.unsafeGet(externalBindings, key)
acc->Belt.Map.String.set(key, value)
})
}
let fromExternalBindings = (externalBindings: externalBindings): ExpressionT.bindings =>
Bindings.fromRecord(externalBindings)
let fromExternalBindings = (externalBindings: externalBindings): ExpressionT.bindings => {
// TODO: This code will be removed in the future when maps are used instead of records. Please don't mind this function for now.
let internalBindings0 = fromExternalBindings_(externalBindings)
let oExistingTypeAliases = Belt.Map.String.get(internalBindings0, typeAliasesKey)
let internalBindings1 = Belt.Option.mapWithDefault(
oExistingTypeAliases,
internalBindings0,
existingTypeAliases => {
let newTypeAliases = switch existingTypeAliases {
| EvRecord(actualTypeAliases) =>
actualTypeAliases->fromExternalBindings_->toExternalBindings->ExpressionValue.EvRecord
| _ => existingTypeAliases
}
Belt.Map.String.set(internalBindings0, typeAliasesKey, newTypeAliases)
},
)
let oExistingTypeReferences = Belt.Map.String.get(internalBindings1, typeReferencesKey)
let internalBindings2 = Belt.Option.mapWithDefault(
oExistingTypeReferences,
internalBindings1,
existingTypeReferences => {
let newTypeReferences = switch existingTypeReferences {
| EvRecord(actualTypeReferences) =>
actualTypeReferences->fromExternalBindings_->toExternalBindings->ExpressionValue.EvRecord
| _ => existingTypeReferences
}
Belt.Map.String.set(internalBindings0, typeReferencesKey, newTypeReferences)
},
)
internalBindings2
}
let fromValue = (aValue: expressionValue) =>
switch aValue {
| EvRecord(externalBindings) => fromExternalBindings(externalBindings)
| _ => defaultBindings
}
let externalFromArray = anArray => Js.Dict.fromArray(anArray)
let fromValue = (aValue: expressionValue) => Bindings.fromExpressionValue(aValue)
let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$")

View File

@ -65,5 +65,7 @@ let eBindExpression = (bindingExpr: expression, expression: expression): express
let eBindExpressionDefault = (expression: expression): expression =>
eFunction("$$_bindExpression_$$", list{expression})
let eIdentifier = (name: string): expression => name->BExpressionValue.EvSymbol->BExpressionT.EValue
let eTypeIdentifier = (name: string): expression =>
name->BExpressionValue.EvTypeIdentifier->BExpressionT.EValue

View File

@ -64,6 +64,7 @@
return {type: 'KeyValue', key: key, value: value}}
function nodeLambda(args, body) {return {type: 'Lambda', args: args, body: body}}
function nodeLetStatment(variable, value) {return {type: 'LetStatement', variable: variable, value: value}}
function nodeModuleIdentifier(value) {return {type: 'ModuleIdentifier', value: value}}
function nodeString(value) {return {type: 'String', value: value}}
function nodeTernary(condition, trueExpression, falseExpression) {return {type: 'Ternary', condition: condition, trueExpression: trueExpression, falseExpression: falseExpression}}
@ -256,11 +257,11 @@ basicLiteral
dollarIdentifierWithModule 'identifier'
= head:moduleIdentifier
tail:('.' _nl @moduleIdentifier)* '.' _nl
final:dollarIdentifier
tail:('.' _nl @$moduleIdentifier)* '.' _nl
final:$dollarIdentifier
{ tail.push(final);
return tail.reduce(function(result, element) {
return makeFunctionCall(postOperatorToFunction['[]'], [result, element])
return makeFunctionCall(postOperatorToFunction['[]'], [result, nodeString(element)])
}, head)}
identifier 'identifier'
@ -273,7 +274,7 @@ dollarIdentifier '$identifier'
= ([\$_a-z]+[\$_a-z0-9]i*) {return nodeIdentifier(text())}
moduleIdentifier 'identifier'
= ([A-Z]+[_a-z0-9]i*) {return nodeIdentifier(text())}
= ([A-Z]+[_a-z0-9]i*) {return nodeModuleIdentifier(text())}
string 'string'

View File

@ -22,6 +22,7 @@ type nodeInteger = {...node, "value": int}
type nodeKeyValue = {...node, "key": node, "value": node}
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": nodeBlock}
type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
type nodeModuleIdentifier = {...node, "value": string}
type nodeString = {...node, "value": string}
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
type nodeTypeIdentifier = {...node, "value": string}
@ -37,6 +38,7 @@ type peggyNode =
| PgNodeKeyValue(nodeKeyValue)
| PgNodeLambda(nodeLambda)
| PgNodeLetStatement(nodeLetStatement)
| PgNodeModuleIdentifier(nodeModuleIdentifier)
| PgNodeString(nodeString)
| PgNodeTernary(nodeTernary)
| PgNodeTypeIdentifier(nodeTypeIdentifier)
@ -51,6 +53,7 @@ external castNodeInteger: node => nodeInteger = "%identity"
external castNodeKeyValue: node => nodeKeyValue = "%identity"
external castNodeLambda: node => nodeLambda = "%identity"
external castNodeLetStatement: node => nodeLetStatement = "%identity"
external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity"
external castNodeString: node => nodeString = "%identity"
external castNodeTernary: node => nodeTernary = "%identity"
external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity"
@ -68,6 +71,7 @@ let castNodeType = (node: node) =>
| "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue
| "Lambda" => node->castNodeLambda->PgNodeLambda
| "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement
| "ModuleIdentifier" => node->castNodeModuleIdentifier->PgNodeModuleIdentifier
| "String" => node->castNodeString->PgNodeString
| "Ternary" => node->castNodeTernary->PgNodeTernary
| "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier
@ -94,6 +98,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}"
| PgNodeLetStatement(node) =>
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
| PgNodeModuleIdentifier(node) => `@${node["value"]}`
| PgNodeString(node) => `'${node["value"]->Js.String.make}'`
| PgNodeTernary(node) =>
"(::$$_ternary_$$ " ++

View File

@ -34,6 +34,8 @@ let rec fromNode = (node: Parse.node): expression => {
nodeLetStatement["variable"]["value"],
fromNode(nodeLetStatement["value"]),
)
| PgNodeModuleIdentifier(nodeModuleIdentifier) =>
ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"])
| PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
| PgNodeTernary(nodeTernary) =>
ExpressionBuilder.eFunction(

View File

@ -0,0 +1,6 @@
module Bindings = Reducer_Category_Bindings
let defaultInternalBindings = Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings
@genType
let defaultExternalBindings = defaultInternalBindings->Bindings.toRecord

View File

@ -4,7 +4,6 @@
*/
module Extra_Array = Reducer_Extra_Array
module ErrorValue = Reducer_ErrorValue
@genType.opaque
type internalCode = Object
@ -24,6 +23,7 @@ type rec expressionValue =
| EvTimeDuration(float)
| EvDeclaration(lambdaDeclaration)
| EvTypeIdentifier(string)
| EvModule(record)
and record = Js.Dict.t<expressionValue>
and externalBindings = record
and lambdaValue = {
@ -33,9 +33,6 @@ and lambdaValue = {
}
and lambdaDeclaration = Declaration.declaration<lambdaValue>
@genType
let defaultExternalBindings: externalBindings = Js.Dict.empty()
type functionCall = (string, array<expressionValue>)
let rec toString = aValue =>
@ -60,6 +57,7 @@ let rec toString = aValue =>
| EvTimeDuration(t) => DateTime.Duration.toString(t)
| EvDeclaration(d) => Declaration.toString(d, r => toString(EvLambda(r)))
| EvTypeIdentifier(id) => `#${id}`
| EvModule(m) => `@${m->toStringRecord}`
}
and toStringRecord = aRecord => {
let pairs =
@ -86,6 +84,7 @@ let toStringWithType = aValue =>
| EvTimeDuration(_) => `Date::${toString(aValue)}`
| EvDeclaration(_) => `Declaration::${toString(aValue)}`
| EvTypeIdentifier(_) => `TypeIdentifier::${toString(aValue)}`
| EvModule(_) => `Module::${toString(aValue)}`
}
let argsToString = (args: array<expressionValue>): string => {
@ -133,6 +132,7 @@ type expressionValueType =
| EvtTimeDuration
| EvtDeclaration
| EvtTypeIdentifier
| EvtModule
type functionCallSignature = CallSignature(string, array<expressionValueType>)
type functionDefinitionSignature =
@ -154,6 +154,7 @@ let valueToValueType = value =>
| EvTimeDuration(_) => EvtTimeDuration
| EvDeclaration(_) => EvtDeclaration
| EvTypeIdentifier(_) => EvtTypeIdentifier
| EvModule(_) => EvtModule
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
@ -177,6 +178,7 @@ let valueTypeToString = (valueType: expressionValueType): string =>
| EvtTimeDuration => `Duration`
| EvtDeclaration => `Declaration`
| EvtTypeIdentifier => `TypeIdentifier`
| EvtModule => `Module`
}
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {

View File

@ -0,0 +1,8 @@
module Bindings = Reducer_Category_Bindings
module Module = Reducer_Category_Module
let m =
Module.emptyModule->Module.defineNumber("pi", Js.Math._PI)->Module.defineNumber("e", Js.Math._E)
let makeBindings = (previousBindings: Bindings.t): Bindings.t =>
previousBindings->Bindings.defineModule("Math", m)