Merge pull request #845 from quantified-uncertainty/reducer-typecheck

Reducer typecheck
This commit is contained in:
Ozzie Gooen 2022-07-18 22:25:40 -07:00 committed by GitHub
commit 5891d23462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 484 additions and 109 deletions

View File

@ -88,7 +88,7 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
let chartPointsData: point[] = chartPointsToRender.map((x) => { let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment); let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") { if (result.tag === "Ok") {
if (result.value.tag == "distribution") { if (result.value.tag === "distribution") {
return { x, value: { tag: "Ok", value: result.value.value } }; return { x, value: { tag: "Ok", value: result.value.value } };
} else { } else {
return { return {
@ -165,12 +165,14 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
setMouseOverlay(NaN); setMouseOverlay(NaN);
} }
const signalListeners = { mousemove: handleHover, mouseout: handleOut }; const signalListeners = { mousemove: handleHover, mouseout: handleOut };
//TODO: This custom error handling is a bit hacky and should be improved.
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
? runForeign(fn, [mouseOverlay], environment) ? runForeign(fn, [mouseOverlay], environment)
: { : {
tag: "Error", tag: "Error",
value: { value: {
tag: "REExpectedType", tag: "RETodo",
value: "Hover x-coordinate returned NaN. Expected a number.", value: "Hover x-coordinate returned NaN. Expected a number.",
}, },
}; };

View File

@ -20,7 +20,7 @@ describe("Peggy parse type", () => {
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}",
) )
}) })
describe("high priority modifier", () => { describe("high priority contract", () => {
testParse( testParse(
"answer: number<-min<-max(100)|string", "answer: number<-min<-max(100)|string",
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}", "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}",
@ -30,7 +30,7 @@ describe("Peggy parse type", () => {
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}",
) )
}) })
describe("low priority modifier", () => { describe("low priority contract", () => {
testParse( testParse(
"answer: number | string $ opaque", "answer: number | string $ opaque",
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
@ -63,14 +63,14 @@ describe("Peggy parse type", () => {
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}", "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}",
) )
}) })
describe("type paranthesis", () => { describe("type parenthesis", () => {
//$ is introduced to avoid paranthesis //$ is introduced to avoid parenthesis
testParse( testParse(
"answer: (number|string)<-opaque", "answer: (number|string)<-opaque",
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
) )
}) })
describe("squiggle expressions in type modifiers", () => { describe("squiggle expressions in type contracts", () => {
testParse( testParse(
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
"{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}", "{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}",

View File

@ -3,11 +3,10 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest open Jest
open Reducer_Peggy_TestHelpers open Reducer_Peggy_TestHelpers
open Expect
describe("Peggy to Expression", () => { describe("Peggy to Expression", () => {
describe("literals operators parenthesis", () => { describe("literals operators parenthesis", () => {
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement
testToExpression("1", "{1}", ~v="1", ()) testToExpression("1", "{1}", ~v="1", ())
testToExpression("'hello'", "{'hello'}", ~v="'hello'", ()) testToExpression("'hello'", "{'hello'}", ~v="'hello'", ())
testToExpression("true", "{true}", ~v="true", ()) testToExpression("true", "{true}", ~v="true", ())

View File

@ -40,7 +40,7 @@ describe("Peggy Types to Expression", () => {
(), (),
) )
}) })
describe("high priority modifier", () => { describe("high priority contract", () => {
testToExpression( testToExpression(
"answer: number<-min(1)<-max(100)|string", "answer: number<-min(1)<-max(100)|string",
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}",
@ -78,7 +78,7 @@ describe("Peggy Types to Expression", () => {
(), (),
) )
}) })
describe("low priority modifier", () => { describe("low priority contract", () => {
testToExpression( testToExpression(
"answer: number | string $ opaque", "answer: number | string $ opaque",
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}",
@ -86,7 +86,7 @@ describe("Peggy Types to Expression", () => {
(), (),
) )
}) })
describe("squiggle expressions in type modifiers", () => { describe("squiggle expressions in type contracts", () => {
testToExpression( testToExpression(
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
"{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}", "{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}",

View File

@ -0,0 +1,52 @@
module Expression = Reducer_Expression
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeCompile = Reducer_Type_Compile
open Jest
open Expect
let myIevEval = (aTypeSourceCode: string) =>
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
let myIevEvalToString = (aTypeSourceCode: string) =>
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
let myIevExpectEqual = (aTypeSourceCode, answer) =>
expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer)
let myIevTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
let myTypeEval = (aTypeSourceCode: string) =>
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
let myTypeExpectEqual = (aTypeSourceCode, answer) =>
expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer)
let myTypeTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer))
// | ItTypeIdentifier(string)
myTypeTest(test, "number", "number")
myTypeTest(test, "(number)", "number")
// | ItModifiedType({modifiedType: iType})
myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})")
myTypeTest(test, "number<-min(0)", "number<-min(0)")
// | ItTypeOr({typeOr: array<iType>})
myTypeTest(test, "number | string", "(number | string)")
// | ItTypeFunction({inputs: array<iType>, output: iType})
myTypeTest(test, "number => number => number", "(number => number => number)")
// | ItTypeArray({element: iType})
myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})")
myTypeTest(test, "[number]", "[number]")
// | ItTypeTuple({elements: array<iType>})
myTypeTest(test, "[number, string]", "[number, string]")
// | ItTypeRecord({properties: Belt.Map.String.t<iType>})
myIevTest(
test,
"{age: number, name: string}",
"Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})",
)
myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}")

View File

@ -0,0 +1,41 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
open Jest
open Expect
let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpression
let rResult =
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
)
rResult->Belt.Result.flatMap(result =>
switch result {
| IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn)
| _ => Js.Exn.raiseError("Arguments has to be an array")
}
)
}
let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string =>
switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer)
let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer))
myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok")

View File

@ -0,0 +1,70 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
open Jest
open Expect
// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn).
// isTypeOfSourceCode is written to use strings instead of expression values.
let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpression
let rResult =
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
)
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
}
let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string =>
switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer)
let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer))
myTypeCheckTest(test, "number", "1", "Ok")
myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'")
myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3")
myTypeCheckTest(test, "string", "'a'", "Ok")
myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok")
myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok")
myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2")
myTypeCheckTest(
test,
"[number, string, string]",
"[1,'a']",
"Expected type: [number, string, string] but got: [1,'a']",
)
myTypeCheckTest(
test,
"[number, string]",
"[1,'a', 3]",
"Expected type: [number, string] but got: [1,'a',3]",
)
myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok")
myTypeCheckTest(
test,
"{age: number, name: string}",
"{age: 1, name: 'a', job: 'IT'}",
"Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}",
)
myTypeCheckTest(test, "number | string", "1", "Ok")
myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1")
myTypeCheckTest(test, "number<-min(10)", "10", "Ok")
myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0")

View File

@ -10,5 +10,5 @@ describe("Evaluate ternary operator", () => {
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')") testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')") testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')")
testEvalToBe("2 <= 1 ? 'YES' : 'NO'", "Ok('NO')") testEvalToBe("2 <= 1 ? 'YES' : 'NO'", "Ok('NO')")
testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean)") testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean but got: )")
}) })

View File

@ -1,3 +1,6 @@
// Only Bindings as the global module is supported
// Other module operations such as import export will be prepreocessed jobs
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue open Reducer_ErrorValue

View File

@ -10,8 +10,8 @@ open ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue open Reducer_ErrorValue
/* /*
MathJs provides default implementations for builtins MathJs provides default implementations for built-ins
This is where all the expected builtins like + = * / sin cos log ln etc are handled This is where all the expected built-ins like + = * / sin cos log ln etc are handled
DO NOT try to add external function mapping here! DO NOT try to add external function mapping here!
*/ */
@ -219,7 +219,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
| ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr) | ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr)
| ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems) | ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems)
| ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem) | ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem)
| ("$_typeRecord_$", [IEvArray(arrayOfPairs)]) => TypeBuilder.typeRecord(arrayOfPairs) | ("$_typeRecord_$", [IEvRecord(propertyMap)]) => TypeBuilder.typeRecord(propertyMap)
| ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) => | ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) =>
doAddArray(aValueArray, bValueArray) doAddArray(aValueArray, bValueArray)
| ("concat", [IEvString(aValueString), IEvString(bValueString)]) => | ("concat", [IEvString(aValueString), IEvString(bValueString)]) =>
@ -277,11 +277,10 @@ let dispatch = (call: functionCall, environment, reducer: ExpressionT.reducerFn)
errorValue, errorValue,
> => > =>
try { try {
let callInternalWithReducer = (call, environment) => callInternal(call, environment, reducer)
let (fn, args) = call let (fn, args) = call
// There is a bug that prevents string match in patterns // There is a bug that prevents string match in patterns
// So we have to recreate a copy of the string // So we have to recreate a copy of the string
ExternalLibrary.dispatch((Js.String.make(fn), args), environment, callInternalWithReducer) ExternalLibrary.dispatch((Js.String.make(fn), args), environment, reducer, callInternal)
} catch { } catch {
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error | Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
| _ => RETodo("unhandled rescript exception")->Error | _ => RETodo("unhandled rescript exception")->Error

View File

@ -144,7 +144,7 @@ let dispatchMacroCall = (
let ifTrueBlock = eBlock(list{ifTrue}) let ifTrueBlock = eBlock(list{ifTrue})
ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok
} }
| _ => REExpectedType("Boolean")->Error | _ => REExpectedType("Boolean", "")->Error
} }
) )
} }

View File

@ -7,7 +7,7 @@ type errorValue =
| REArrayIndexNotFound(string, int) | REArrayIndexNotFound(string, int)
| REAssignmentExpected | REAssignmentExpected
| REDistributionError(DistributionTypes.error) | REDistributionError(DistributionTypes.error)
| REExpectedType(string) | REExpectedType(string, string)
| REExpressionExpected | REExpressionExpected
| REFunctionExpected(string) | REFunctionExpected(string)
| REFunctionNotFound(string) | REFunctionNotFound(string)
@ -55,6 +55,6 @@ let errorToString = err =>
| RESymbolNotFound(symbolName) => `${symbolName} is not defined` | RESymbolNotFound(symbolName) => `${symbolName} is not defined`
| RESyntaxError(desc, _) => `Syntax Error: ${desc}` | RESyntaxError(desc, _) => `Syntax Error: ${desc}`
| RETodo(msg) => `TODO: ${msg}` | RETodo(msg) => `TODO: ${msg}`
| REExpectedType(typeName) => `Expected type: ${typeName}` | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}`
| REUnitNotFound(unitName) => `Unit not found: ${unitName}` | REUnitNotFound(unitName) => `Unit not found: ${unitName}`
} }

View File

@ -1,3 +1,3 @@
// There are switch stament cases in the code which are impossible to reach by design. // There are switch statement cases in the code which are impossible to reach by design.
// ImpossibleException is a sign of programming error. // ImpossibleException is a sign of programming error.
exception ImpossibleException exception ImpossibleException(string)

View File

@ -30,12 +30,12 @@ let rec toString = expression =>
switch expression { switch expression {
| EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) => | EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) =>
`{${Belt.List.map(statements, aValue => toString(aValue)) `{${Belt.List.map(statements, aValue => toString(aValue))
->Extra.List.interperse("; ") ->Extra.List.intersperse("; ")
->Belt.List.toArray ->Belt.List.toArray
->Js.String.concatMany("")}}` ->Js.String.concatMany("")}}`
| EList(aList) => | EList(aList) =>
`(${Belt.List.map(aList, aValue => toString(aValue)) `(${Belt.List.map(aList, aValue => toString(aValue))
->Extra.List.interperse(" ") ->Extra.List.intersperse(" ")
->Belt.List.toArray ->Belt.List.toArray
->Js.String.concatMany("")})` ->Js.String.concatMany("")})`
| EValue(aValue) => InternalExpressionValue.toString(aValue) | EValue(aValue) => InternalExpressionValue.toString(aValue)

View File

@ -3,5 +3,5 @@
*/ */
module ExtraList = Reducer_Extra_List module ExtraList = Reducer_Extra_List
let interperse = (anArray, seperator) => let intersperse = (anArray, seperator) =>
anArray->Belt.List.fromArray->ExtraList.interperse(seperator)->Belt.List.toArray anArray->Belt.List.fromArray->ExtraList.intersperse(seperator)->Belt.List.toArray

View File

@ -1,9 +1,9 @@
/* /*
Insert seperator between the elements of a list Insert seperator between the elements of a list
*/ */
let rec interperse = (aList, seperator) => let rec intersperse = (aList, seperator) =>
switch aList { switch aList {
| list{} => list{} | list{} => list{}
| list{a} => list{a} | list{a} => list{a}
| list{a, ...rest} => list{a, seperator, ...interperse(rest, seperator)} | list{a, ...rest} => list{a, seperator, ...intersperse(rest, seperator)}
} }

View File

@ -91,7 +91,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString
let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string => let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string =>
nodes->Js.Array2.map(toString)->Extra.Array.interperse(separator)->Js.String.concatMany("") nodes->Js.Array2.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("")
switch peggyNode { switch peggyNode {
| PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}" | PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"

View File

@ -0,0 +1,40 @@
module ErrorValue = Reducer_ErrorValue
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
let ievFromTypeExpression = (
typeExpressionSourceCode: string,
reducerFn: ExpressionT.reducerFn,
): result<InternalExpressionValue.t, ErrorValue.t> => {
let sIndex = "compiled"
let sourceCode = `type ${sIndex}=${typeExpressionSourceCode}`
Reducer_Expression.parse(sourceCode)->Belt.Result.flatMap(expr => {
let rContext = reducerFn(
expr,
Bindings.emptyBindings,
InternalExpressionValue.defaultEnvironment,
)
Belt.Result.map(rContext, context =>
switch context {
| IEvBindings(nameSpace) =>
switch Bindings.getType(nameSpace, sIndex) {
| Some(value) => value
| None => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-none"))
}
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-raise"))
}
)
})
}
let fromTypeExpression = (
typeExpressionSourceCode: string,
reducerFn: ExpressionT.reducerFn,
): result<T.t, ErrorValue.t> => {
ievFromTypeExpression(
(typeExpressionSourceCode: string),
(reducerFn: ExpressionT.reducerFn),
)->Belt.Result.map(T.fromIEvValue)
}

View File

@ -0,0 +1,53 @@
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module T = Reducer_Type_T
let isMin = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => {
let pair = (modifierArg, aValue)
switch pair {
| (IEvNumber(a), IEvNumber(b)) => a <= b
| _ => false
}
}
let isMax = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => {
let pair = (modifierArg, aValue)
switch pair {
| (IEvNumber(a), IEvNumber(b)) => a >= b
| _ => false
}
}
let isMemberOf = (
modifierArg: InternalExpressionValue.t,
aValue: InternalExpressionValue.t,
): bool => {
let pair = (modifierArg, aValue)
switch pair {
| (ievA, IEvArray(b)) => Js.Array2.includes(b, ievA)
| _ => false
}
}
let checkModifier = (
key: string,
modifierArg: InternalExpressionValue.t,
aValue: InternalExpressionValue.t,
): bool =>
switch key {
| "min" => isMin(modifierArg, aValue)
| "max" => isMax(modifierArg, aValue)
| "isMemberOf" => isMemberOf(modifierArg, aValue)
| _ => false
}
let checkModifiers = (
contracts: Belt.Map.String.t<InternalExpressionValue.t>,
aValue: InternalExpressionValue.t,
): bool => {
contracts->Belt.Map.String.reduce(true, (acc, key, modifierArg) =>
switch acc {
| true => checkModifier(key, modifierArg, aValue)
| _ => acc
}
)
}

View File

@ -3,13 +3,42 @@ open InternalExpressionValue
type rec iType = type rec iType =
| ItTypeIdentifier(string) | ItTypeIdentifier(string)
| ItModifiedType({modifiedType: iType}) | ItModifiedType({modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>})
| ItTypeOr({typeOr: array<iType>}) | ItTypeOr({typeOr: array<iType>})
| ItTypeFunction({inputs: array<iType>, output: iType}) | ItTypeFunction({inputs: array<iType>, output: iType})
| ItTypeArray({element: iType}) | ItTypeArray({element: iType})
| ItTypeTuple({elements: array<iType>}) | ItTypeTuple({elements: array<iType>})
| ItTypeRecord({properties: Belt.Map.String.t<iType>}) | ItTypeRecord({properties: Belt.Map.String.t<iType>})
type t = iType
type typeErrorValue = TypeMismatch(t, InternalExpressionValue.t)
let rec toString = (t: t): string => {
switch t {
| ItTypeIdentifier(s) => s
| ItModifiedType({modifiedType, contracts}) =>
`${toString(modifiedType)}${contracts->Belt.Map.String.reduce("", (acc, k, v) =>
Js.String2.concatMany(acc, ["<-", k, "(", InternalExpressionValue.toString(v), ")"])
)}`
| ItTypeOr({typeOr}) => `(${Js.Array2.map(typeOr, toString)->Js.Array2.joinWith(" | ")})`
| ItTypeFunction({inputs, output}) =>
`(${inputs->Js.Array2.map(toString)->Js.Array2.joinWith(" => ")} => ${toString(output)})`
| ItTypeArray({element}) => `[${toString(element)}]`
| ItTypeTuple({elements}) => `[${Js.Array2.map(elements, toString)->Js.Array2.joinWith(", ")}]`
| ItTypeRecord({properties}) =>
`{${properties
->Belt.Map.String.toArray
->Js.Array2.map(((k, v)) => Js.String2.concatMany(k, [": ", toString(v)]))
->Js.Array2.joinWith(", ")}}`
}
}
let toStringResult = (rt: result<t, ErrorValue.t>) =>
switch rt {
| Ok(t) => toString(t)
| Error(e) => ErrorValue.errorToString(e)
}
let rec fromTypeMap = typeMap => { let rec fromTypeMap = typeMap => {
let default = IEvString("") let default = IEvString("")
let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault( let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
@ -52,31 +81,39 @@ let rec fromTypeMap = typeMap => {
"properties", "properties",
default, default,
) )
//TODO: map type modifiers
switch evTypeTag { let contracts =
| IEvString("typeIdentifier") => ItModifiedType({modifiedType: fromIEvValue(evTypeIdentifier)}) typeMap->Belt.Map.String.keep((k, _v) => ["min", "max", "memberOf"]->Js.Array2.includes(k))
let makeIt = switch evTypeTag {
| IEvString("typeIdentifier") => fromIEvValue(evTypeIdentifier)
| IEvString("typeOr") => ItTypeOr({typeOr: fromIEvArray(evTypeOr)}) | IEvString("typeOr") => ItTypeOr({typeOr: fromIEvArray(evTypeOr)})
| IEvString("typeFunction") => | IEvString("typeFunction") =>
ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)}) ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)})
| IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)}) | IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)})
| IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)}) | IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)})
| IEvString("typeRecord") => ItTypeRecord({properties: fromIEvRecord(evProperties)}) | IEvString("typeRecord") => ItTypeRecord({properties: fromIEvRecord(evProperties)})
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-evTypeTag"))
} }
Belt.Map.String.isEmpty(contracts)
? makeIt
: ItModifiedType({modifiedType: makeIt, contracts: contracts})
} }
and fromIEvValue = (ievValue: InternalExpressionValue.t) =>
and fromIEvValue = (ievValue: InternalExpressionValue.t): iType =>
switch ievValue { switch ievValue {
| IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier}) | IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier})
| IEvType(typeMap) => fromTypeMap(typeMap) | IEvType(typeMap) => fromTypeMap(typeMap)
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievValue"))
} }
and fromIEvArray = (ievArray: InternalExpressionValue.t) => and fromIEvArray = (ievArray: InternalExpressionValue.t) =>
switch ievArray { switch ievArray {
| IEvArray(array) => array->Belt.Array.map(fromIEvValue) | IEvArray(array) => array->Belt.Array.map(fromIEvValue)
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievArray"))
} }
and fromIEvRecord = (ievRecord: InternalExpressionValue.t) => and fromIEvRecord = (ievRecord: InternalExpressionValue.t) =>
switch ievRecord { switch ievRecord {
| IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue) | IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue)
| _ => raise(Reducer_Exception.ImpossibleException) | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievRecord"))
} }

View File

@ -56,7 +56,7 @@ let typeFunction = anArray => {
let typeArray = element => { let typeArray = element => {
let newRecord = Belt.Map.String.fromArray([ let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeTuple")), ("typeTag", IEvString("typeArray")),
("element", element), ("element", element),
]) ])
newRecord->IEvType->Ok newRecord->IEvType->Ok
@ -64,22 +64,14 @@ let typeArray = element => {
let typeTuple = anArray => { let typeTuple = anArray => {
let newRecord = Belt.Map.String.fromArray([ let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeArray")), ("typeTag", IEvString("typeTuple")),
("elements", IEvArray(anArray)), ("elements", IEvArray(anArray)),
]) ])
newRecord->IEvType->Ok newRecord->IEvType->Ok
} }
let typeRecord = arrayOfPairs => { let typeRecord = propertyMap => {
let newProperties = let newProperties = propertyMap->IEvRecord
Belt.Array.map(arrayOfPairs, pairValue =>
switch pairValue {
| IEvArray([IEvString(key), valueValue]) => (key, valueValue)
| _ => ("wrong key type", pairValue->toStringWithType->IEvString)
}
)
->Belt.Map.String.fromArray
->IEvRecord
let newRecord = Belt.Map.String.fromArray([ let newRecord = Belt.Map.String.fromArray([
("typeTag", IEvString("typeRecord")), ("typeTag", IEvString("typeRecord")),
("properties", newProperties), ("properties", newProperties),

View File

@ -1,81 +1,168 @@
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module T = Reducer_Type_T module T = Reducer_Type_T
module TypeBuilder = Reducer_Type_TypeBuilder module TypeContracts = Reducer_Type_Contracts
open InternalExpressionValue open InternalExpressionValue
type typeErrorValue = let rec isITypeOf = (anIType: T.iType, aValue): result<bool, T.typeErrorValue> => {
| TypeError(T.iType, InternalExpressionValue.t)
| TypeErrorWithPosition(T.iType, InternalExpressionValue.t, int)
| TypeErrorWithProperty(T.iType, InternalExpressionValue.t, string)
let rec isOfResolvedIType = (anIType: T.iType, aValue): result<bool, typeErrorValue> => {
let caseTypeIdentifier = (anUpperTypeName, aValue) => { let caseTypeIdentifier = (anUpperTypeName, aValue) => {
let aTypeName = anUpperTypeName->Js.String2.toLowerCase let aTypeName = anUpperTypeName->Js.String2.toLowerCase
let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase
switch aTypeName === valueTypeName { switch aTypeName == valueTypeName {
| true => Ok(true) | true => Ok(true)
| false => TypeError(anIType, aValue)->Error | false => T.TypeMismatch(anIType, aValue)->Error
} }
} }
let _caseRecord = (anIType, evValue, propertyMap, map) => { let caseRecord = (anIType, propertyMap: Belt.Map.String.t<T.iType>, evValue) =>
Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => { switch evValue {
| IEvRecord(aRecord) =>
if (
Js.Array2.length(propertyMap->Belt.Map.String.keysToArray) ==
Js.Array2.length(aRecord->Belt.Map.String.keysToArray)
) {
Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => {
Belt.Result.flatMap(acc, _ =>
switch Belt.Map.String.get(aRecord, property) {
| Some(propertyValue) => isITypeOf(propertyType, propertyValue)
| None => T.TypeMismatch(anIType, evValue)->Error
}
)
})
} else {
T.TypeMismatch(anIType, evValue)->Error
}
| _ => T.TypeMismatch(anIType, evValue)->Error
}
let caseArray = (anIType, elementType, evValue) =>
switch evValue {
| IEvArray(anArray) =>
Belt.Array.reduce(anArray, Ok(true), (acc, element) =>
Belt.Result.flatMap(acc, _ =>
switch isITypeOf(elementType, element) {
| Ok(_) => Ok(true)
| Error(error) => error->Error
}
)
)
| _ => T.TypeMismatch(anIType, evValue)->Error
}
let caseTuple = (anIType, elementTypes, evValue) =>
switch evValue {
| IEvArray(anArray) =>
if Js.Array2.length(elementTypes) == Js.Array2.length(anArray) {
let zipped = Belt.Array.zip(elementTypes, anArray)
Belt.Array.reduce(zipped, Ok(true), (acc, (elementType, element)) =>
switch acc {
| Ok(_) =>
switch isITypeOf(elementType, element) {
| Ok(_) => acc
| Error(error) => Error(error)
}
| _ => acc
}
)
} else {
T.TypeMismatch(anIType, evValue)->Error
}
| _ => T.TypeMismatch(anIType, evValue)->Error
}
let caseOr = (anIType, anITypeArray, evValue) =>
switch Belt.Array.reduce(anITypeArray, Ok(false), (acc, anIType) =>
Belt.Result.flatMap(acc, _ => Belt.Result.flatMap(acc, _ =>
switch Belt.Map.String.get(map, property) { switch acc {
| Some(propertyValue) => isOfResolvedIType(propertyType, propertyValue) | Ok(false) =>
| None => TypeErrorWithProperty(anIType, evValue, property)->Error switch isITypeOf(anIType, evValue) {
| Ok(_) => Ok(true)
| Error(_) => acc
}
| _ => acc
} }
) )
}) ) {
} | Ok(true) => Ok(true)
let _caseArray = (anIType, evValue, elementType, anArray) => { | Ok(false) => T.TypeMismatch(anIType, evValue)->Error
Belt.Array.reduceWithIndex(anArray, Ok(true), (acc, element, index) => { | Error(error) => Error(error)
switch isOfResolvedIType(elementType, element) { }
| Ok(_) => acc
| Error(_) => TypeErrorWithPosition(anIType, evValue, index)->Error let caseModifiedType = (
anIType: T.iType,
modifiedType: T.iType,
contracts: Belt.Map.String.t<InternalExpressionValue.t>,
aValue: InternalExpressionValue.t,
) => {
isITypeOf(modifiedType, aValue)->Belt.Result.flatMap(_result => {
if TypeContracts.checkModifiers(contracts, aValue) {
Ok(true)
} else {
T.TypeMismatch(anIType, aValue)->Error
} }
}) })
} }
switch anIType { switch anIType {
| ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue) | ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue)
// TODO: Work in progress. Code is commented to make an a release of other features | ItModifiedType({modifiedType, contracts}) =>
// | ItModifiedType({modifiedType: anIType}) => raise(Reducer_Exception.ImpossibleException) caseModifiedType(anIType, modifiedType, contracts, aValue) //{modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>}
// | ItTypeOr({typeOr: anITypeArray}) => raise(Reducer_Exception.ImpossibleException) | ItTypeOr({typeOr}) => caseOr(anIType, typeOr, aValue)
// | ItTypeFunction({inputs: anITypeArray, output: anIType}) => | ItTypeFunction(_) =>
// raise(Reducer_Exception.ImpossibleException) raise(
// | ItTypeArray({element: anIType}) => raise(Reducer_Exception.ImpossibleException) Reducer_Exception.ImpossibleException(
// | ItTypeTuple({elements: anITypeArray}) => raise(Reducer_Exception.ImpossibleException) "Reducer_TypeChecker-functions are without a type at the moment",
// | ItTypeRecord({properties: anITypeMap}) => raise(Reducer_Exception.ImpossibleException) ),
| _ => raise(Reducer_Exception.ImpossibleException) )
| ItTypeArray({element}) => caseArray(anIType, element, aValue)
| ItTypeTuple({elements}) => caseTuple(anIType, elements, aValue)
| ItTypeRecord({properties}) => caseRecord(anIType, properties, aValue)
} }
} }
let isOfResolvedType = (aType: InternalExpressionValue.t, aValue): result<bool, typeErrorValue> => let isTypeOf = (
aType->T.fromIEvValue->isOfResolvedIType(aValue) typeExpressionSourceCode: string,
aValue: InternalExpressionValue.t,
reducerFn: ExpressionT.reducerFn,
): result<InternalExpressionValue.t, ErrorValue.t> => {
switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) {
| Ok(anIType) =>
switch isITypeOf(anIType, aValue) {
| Ok(_) => Ok(aValue)
| Error(T.TypeMismatch(anIType, evValue)) =>
Error(
ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString),
)
}
| Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch
}
}
// TODO: Work in progress. Code is commented to make an a release of other features let checkITypeArguments = (anIType: T.iType, args: array<InternalExpressionValue.t>): result<
// let checkArguments = ( bool,
// evFunctionType: InternalExpressionValue.t, T.typeErrorValue,
// args: array<InternalExpressionValue.t>, > => {
// ) => { switch anIType {
// let functionType = switch evFunctionType { | T.ItTypeFunction({inputs}) => isITypeOf(T.ItTypeTuple({elements: inputs}), args->IEvArray)
// | IEvRecord(functionType) => functionType | _ => T.TypeMismatch(anIType, args->IEvArray)->Error
// | _ => raise(Reducer_Exception.ImpossibleException) }
// } }
// let evInputs = functionType->Belt.Map.String.getWithDefault("inputs", []->IEvArray)
// let inputs = switch evInputs {
// | IEvArray(inputs) => inputs
// | _ => raise(Reducer_Exception.ImpossibleException)
// }
// let rTupleType = TypeBuilder.typeTuple(inputs)
// Belt.Result.flatMap(rTupleType, tuppleType => isOfResolvedType(tuppleType, args->IEvArray))
// }
// let compileTypeExpression = (typeExpression: string, bindings: ExpressionT.bindings, reducerFn: ExpressionT.reducerFn) => { let checkArguments = (
// statement = `type compiled=${typeExpression}` typeExpressionSourceCode: string,
args: array<InternalExpressionValue.t>,
// } reducerFn: ExpressionT.reducerFn,
): result<InternalExpressionValue.t, ErrorValue.t> => {
//TODO: asGuard switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) {
| Ok(anIType) =>
switch checkITypeArguments(anIType, args) {
| Ok(_) => Ok(args->IEvArray)
| Error(T.TypeMismatch(anIType, evValue)) =>
Error(
ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString),
)
}
| Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch
}
}

View File

@ -23,7 +23,7 @@ let tryRegistry = ((fnName, args): InternalExpressionValue.functionCall, env) =>
) )
} }
let dispatch = (call: InternalExpressionValue.functionCall, environment, chain): result< let dispatch = (call: InternalExpressionValue.functionCall, environment, reducer, chain): result<
internalExpressionValue, internalExpressionValue,
'e, 'e,
> => { > => {
@ -33,7 +33,7 @@ let dispatch = (call: InternalExpressionValue.functionCall, environment, chain):
() => ReducerInterface_Duration.dispatch(call, environment), () => ReducerInterface_Duration.dispatch(call, environment),
() => ReducerInterface_Number.dispatch(call, environment), () => ReducerInterface_Number.dispatch(call, environment),
() => tryRegistry(call, environment), () => tryRegistry(call, environment),
])->E.O2.default(chain(call, environment)) ])->E.O2.default(chain(call, environment, reducer))
} }
/* /*
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally. If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.

View File

@ -84,7 +84,7 @@ let toStringWithType = aValue =>
| IEvDeclaration(_) => `Declaration::${toString(aValue)}` | IEvDeclaration(_) => `Declaration::${toString(aValue)}`
| IEvDistribution(_) => `Distribution::${toString(aValue)}` | IEvDistribution(_) => `Distribution::${toString(aValue)}`
| IEvLambda(_) => `Lambda::${toString(aValue)}` | IEvLambda(_) => `Lambda::${toString(aValue)}`
| IEvBindings(_) => `Module::${toString(aValue)}` | IEvBindings(_) => `Bindings::${toString(aValue)}`
| IEvNumber(_) => `Number::${toString(aValue)}` | IEvNumber(_) => `Number::${toString(aValue)}`
| IEvRecord(_) => `Record::${toString(aValue)}` | IEvRecord(_) => `Record::${toString(aValue)}`
| IEvString(_) => `String::${toString(aValue)}` | IEvString(_) => `String::${toString(aValue)}`