more WIP
This commit is contained in:
parent
fea89abff9
commit
6dc1cea045
|
@ -62,7 +62,7 @@ type function = {
|
|||
isExperimental: bool,
|
||||
}
|
||||
|
||||
type fnNameDict = Js.Dict.t<array<function>>
|
||||
type fnNameDict = Js.Dict.t<array<fnDefinition>>
|
||||
type registry = {functions: array<function>, fnNameDict: fnNameDict}
|
||||
|
||||
module FRType = {
|
||||
|
@ -181,188 +181,6 @@ module FRType = {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This module, Matcher, is fairly lengthy. However, only two functions from it
|
||||
are meant to be used outside of it. These are findMatches and matchToDef in Matches.Registry.
|
||||
The rest of it is just called from those two functions.
|
||||
|
||||
Update: This really should be completely re-done sometime, and tested. It works, but it's pretty messy. I'm sure
|
||||
there are internal bugs, but the end functionality works, so I'm not too worried.
|
||||
*/
|
||||
module Matcher = {
|
||||
module MatchSimple = {
|
||||
type t = DifferentName | SameNameDifferentArguments | FullMatch
|
||||
|
||||
let isFullMatch = (match: t) =>
|
||||
switch match {
|
||||
| FullMatch => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let isNameMatchOnly = (match: t) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments => true
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
module Match = {
|
||||
type t<'a, 'b> = DifferentName | SameNameDifferentArguments('a) | FullMatch('b)
|
||||
|
||||
let isFullMatch = (match: t<'a, 'b>): bool =>
|
||||
switch match {
|
||||
| FullMatch(_) => true
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let isNameMatchOnly = (match: t<'a, 'b>) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments(_) => true
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
module FnDefinition = {
|
||||
let matchAssumingSameName = (f: fnDefinition, args: array<internalExpressionValue>) => {
|
||||
switch FRType.matchWithExpressionValueArray(f.inputs, args) {
|
||||
| Some(_) => MatchSimple.FullMatch
|
||||
| None => MatchSimple.SameNameDifferentArguments
|
||||
}
|
||||
}
|
||||
|
||||
let match = (f: fnDefinition, fnName: string, args: array<internalExpressionValue>) => {
|
||||
if f.name !== fnName {
|
||||
MatchSimple.DifferentName
|
||||
} else {
|
||||
matchAssumingSameName(f, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Function = {
|
||||
type definitionId = int
|
||||
type match = Match.t<array<definitionId>, definitionId>
|
||||
|
||||
let match = (
|
||||
f: function,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
): match => {
|
||||
switch nameSpace {
|
||||
| Some(ns) if ns !== f.nameSpace => Match.DifferentName
|
||||
| _ => {
|
||||
let matchedDefinition = () =>
|
||||
E.A.getIndexBy(f.definitions, r =>
|
||||
MatchSimple.isFullMatch(FnDefinition.match(r, fnName, args))
|
||||
) |> E.O.fmap(r => Match.FullMatch(r))
|
||||
let getMatchedNameOnlyDefinition = () => {
|
||||
let nameMatchIndexes =
|
||||
f.definitions
|
||||
->E.A2.fmapi((index, r) =>
|
||||
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args))
|
||||
? Some(index)
|
||||
: None
|
||||
)
|
||||
->E.A.O.concatSomes
|
||||
switch nameMatchIndexes {
|
||||
| [] => None
|
||||
| elements => Some(Match.SameNameDifferentArguments(elements))
|
||||
}
|
||||
}
|
||||
|
||||
E.A.O.firstSomeFnWithDefault(
|
||||
[matchedDefinition, getMatchedNameOnlyDefinition],
|
||||
Match.DifferentName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module RegistryMatch = {
|
||||
type match = {
|
||||
nameSpace: string,
|
||||
fnName: string,
|
||||
inputIndex: int,
|
||||
}
|
||||
let makeMatch = (nameSpace: string, fnName: string, inputIndex: int) => {
|
||||
nameSpace: nameSpace,
|
||||
fnName: fnName,
|
||||
inputIndex: inputIndex,
|
||||
}
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
let _findExactMatches = (
|
||||
r: registry,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
) => {
|
||||
let functionMatchPairs =
|
||||
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
|
||||
let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match))
|
||||
fullMatch->E.O.bind(((fn, match)) =>
|
||||
switch match {
|
||||
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let _findNameMatches = (
|
||||
r: registry,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
) => {
|
||||
let functionMatchPairs =
|
||||
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
|
||||
let getNameMatches =
|
||||
functionMatchPairs
|
||||
->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None)
|
||||
->E.A.O.concatSomes
|
||||
let matches =
|
||||
getNameMatches
|
||||
->E.A2.fmap(((fn, match)) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments(indexes) =>
|
||||
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
|
||||
| _ => []
|
||||
}
|
||||
)
|
||||
->Belt.Array.concatMany
|
||||
E.A.toNoneIfEmpty(matches)
|
||||
}
|
||||
|
||||
let findMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
let fnNameInParts = Js.String.split(".", fnName)
|
||||
let fnToSearch = E.A.get(fnNameInParts, 1) |> E.O.default(fnNameInParts[0])
|
||||
let nameSpace = E.A.length(fnNameInParts) > 1 ? Some(fnNameInParts[0]) : None
|
||||
|
||||
switch _findExactMatches(r, nameSpace, fnToSearch, args) {
|
||||
| Some(r) => Match.FullMatch(r)
|
||||
| None =>
|
||||
switch _findNameMatches(r, nameSpace, fnToSearch, args) {
|
||||
| Some(r) => Match.SameNameDifferentArguments(r)
|
||||
| None => Match.DifferentName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let matchToDef = (
|
||||
registry: registry,
|
||||
{nameSpace, fnName, inputIndex}: RegistryMatch.match,
|
||||
): option<fnDefinition> =>
|
||||
registry.functions
|
||||
->E.A.getBy(fn => {
|
||||
nameSpace === fn.nameSpace && fnName === fn.name
|
||||
})
|
||||
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
|
||||
}
|
||||
}
|
||||
|
||||
module FnDefinition = {
|
||||
type t = fnDefinition
|
||||
|
||||
|
@ -440,13 +258,6 @@ module Function = {
|
|||
}
|
||||
}
|
||||
|
||||
module NameSpace = {
|
||||
type t = {name: string, functions: array<function>}
|
||||
let definitions = (t: t) => t.functions->E.A2.fmap(f => f.definitions)->E.A.concatMany
|
||||
let uniqueFnNames = (t: t) => definitions(t)->E.A2.fmap(r => r.name)->E.A.uniq
|
||||
let nameToDefinitions = (t: t, name: string) => definitions(t)->E.A2.filter(r => r.name == name)
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
let toJson = (r: registry) => r.functions->E.A2.fmap(Function.toJson)
|
||||
let allExamples = (r: registry) => r.functions->E.A2.fmap(r => r.examples)->E.A.concatMany
|
||||
|
@ -454,29 +265,24 @@ module Registry = {
|
|||
r.functions->E.A2.fmap(fn => fn.examples->E.A2.fmap(example => (fn, example)))->E.A.concatMany
|
||||
|
||||
let _buildFnNameDict = (r: array<function>): fnNameDict => {
|
||||
let allDefinitionsWithFns =
|
||||
r
|
||||
->E.A2.fmap(fn => fn.definitions->E.A2.fmap(definitions => (fn, definitions)))
|
||||
->E.A.concatMany
|
||||
let functionsWithFnNames =
|
||||
allDefinitionsWithFns
|
||||
->E.A2.fmap(((fn, def)) => {
|
||||
// Sorry for the imperative style of this. But it's much easier/less buggy than the previous version.
|
||||
let res: fnNameDict = Js.Dict.empty()
|
||||
r->Js.Array2.forEach(fn =>
|
||||
fn.definitions->Js.Array2.forEach(def => {
|
||||
let nameWithNamespace = `${fn.nameSpace}.${def.name}`
|
||||
let nameWithoutNamespace = def.name
|
||||
fn.requiresNamespace
|
||||
? [(nameWithNamespace, fn)]
|
||||
: [(nameWithNamespace, fn), (nameWithoutNamespace, fn)]
|
||||
let names = fn.requiresNamespace ? [nameWithNamespace] : [nameWithNamespace, nameWithoutNamespace]
|
||||
names->Js.Array2.forEach(name => {
|
||||
switch res->Js.Dict.get(name) {
|
||||
| Some(fns) => {
|
||||
let _ = fns->Js.Array2.push(def)
|
||||
}
|
||||
| None => res->Js.Dict.set(name, [def])
|
||||
}
|
||||
})
|
||||
})
|
||||
->E.A.concatMany
|
||||
let uniqueNames = functionsWithFnNames->E.A2.fmap(((name, _)) => name)->E.A.uniq
|
||||
let cacheAsArray: array<(string, array<function>)> = uniqueNames->E.A2.fmap(uniqueName => {
|
||||
let relevantItems =
|
||||
E.A2.filter(functionsWithFnNames, ((defName, _)) => defName == uniqueName)->E.A2.fmap(
|
||||
E.Tuple2.second,
|
||||
)
|
||||
(uniqueName, relevantItems)
|
||||
})
|
||||
cacheAsArray->Js.Dict.fromArray
|
||||
)
|
||||
res
|
||||
}
|
||||
|
||||
let make = (fns: array<function>): registry => {
|
||||
|
@ -484,48 +290,33 @@ module Registry = {
|
|||
{functions: fns, fnNameDict: dict}
|
||||
}
|
||||
|
||||
/*
|
||||
There's a (potential+minor) bug here: If a function definition is called outside of the calls
|
||||
to the registry, then it's possible that there could be a match after the registry is
|
||||
called. However, for now, we could just call the registry last.
|
||||
*/
|
||||
let _matchAndRun = (
|
||||
~registry: registry,
|
||||
~fnName: string,
|
||||
~args: array<internalExpressionValue>,
|
||||
~env: Reducer_T.environment,
|
||||
~reducer: Reducer_T.reducerFn,
|
||||
) => {
|
||||
let relevantFunctions = Js.Dict.get(registry.fnNameDict, fnName) |> E.O.default([])
|
||||
let modified = {functions: relevantFunctions, fnNameDict: registry.fnNameDict}
|
||||
let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
|
||||
let showNameMatchDefinitions = matches => {
|
||||
let defs =
|
||||
matches
|
||||
->E.A2.fmap(matchToDef)
|
||||
->E.A.O.concatSomes
|
||||
->E.A2.fmap(FnDefinition.toString)
|
||||
->E.A2.fmap(r => `[${r}]`)
|
||||
->E.A2.joinWith("; ")
|
||||
`There are function matches for ${fnName}(), but with different arguments: ${defs}`
|
||||
}
|
||||
|
||||
switch Matcher.Registry.findMatches(modified, fnName, args) {
|
||||
| Matcher.Match.FullMatch(match) =>
|
||||
match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env, reducer))
|
||||
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m)))
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
|
||||
let dispatch = (
|
||||
let call = (
|
||||
registry,
|
||||
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
env: Reducer_T.environment,
|
||||
reducer: Reducer_T.reducerFn,
|
||||
) => {
|
||||
_matchAndRun(~registry, ~fnName, ~args, ~env, ~reducer)->E.O2.fmap(
|
||||
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
||||
)
|
||||
): result<internalExpressionValue, Reducer_ErrorValue.errorValue> => {
|
||||
switch Js.Dict.get(registry.fnNameDict, fnName) {
|
||||
| Some(definitions) => {
|
||||
let showNameMatchDefinitions = () => {
|
||||
let defsString =
|
||||
definitions
|
||||
->E.A2.fmap(FnDefinition.toString)
|
||||
->E.A2.fmap(r => `[${r}]`)
|
||||
->E.A2.joinWith("; ")
|
||||
`There are function matches for ${fnName}(), but with different arguments: ${defsString}`
|
||||
}
|
||||
|
||||
let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args))
|
||||
switch match {
|
||||
| Some(def) => def->FnDefinition.run(args, env, reducer)->E.R2.errMap(e => Reducer_ErrorValue.REOther(e))
|
||||
| None => {
|
||||
Reducer_ErrorValue.REOther(showNameMatchDefinitions())->Error
|
||||
}
|
||||
}
|
||||
}
|
||||
| None => Reducer_ErrorValue.RESymbolNotFound(fnName)->Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@ let fnList = Belt.Array.concatMany([
|
|||
])
|
||||
|
||||
let registry = FunctionRegistry_Core.Registry.make(fnList)
|
||||
let dispatch = FunctionRegistry_Core.Registry.dispatch(registry)
|
||||
let call = FunctionRegistry_Core.Registry.call(registry)
|
||||
|
|
|
@ -45,6 +45,28 @@ let library = [
|
|||
},
|
||||
()
|
||||
),
|
||||
FnDefinition.make(
|
||||
~name="add",
|
||||
~inputs=[FRTypeNumber, FRTypeNumber],
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => IEvNumber(x+.y)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
()
|
||||
),
|
||||
FnDefinition.make(
|
||||
~name="add",
|
||||
~inputs=[FRTypeNumber, FRTypeNumber],
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvNumber(x), IEvNumber(y)] => IEvNumber(x+.y)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
()
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
|
|
|
@ -264,7 +264,7 @@ lambda
|
|||
{ statements.push(finalExpression)
|
||||
return h.nodeLambda(args, h.nodeBlock(statements)) }
|
||||
/ '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}'
|
||||
{ return h.nodeLambda(args, h.nodeBlock([finalExpression])) }
|
||||
{ return h.nodeLambda(args, finalExpression) }
|
||||
|
||||
arrayConstructor 'array'
|
||||
= '[' _nl ']'
|
||||
|
|
|
@ -29,7 +29,7 @@ type nodeIdentifier = {...node, "value": string}
|
|||
type nodeInteger = {...node, "value": int}
|
||||
type nodeKeyValue = {...node, "key": node, "value": node}
|
||||
type nodeRecord = {...node, "elements": array<nodeKeyValue>}
|
||||
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": nodeBlock}
|
||||
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": node}
|
||||
type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
|
||||
type nodeModuleIdentifier = {...node, "value": string}
|
||||
type nodeString = {...node, "value": string}
|
||||
|
@ -122,7 +122,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
|||
| PgNodeInteger(node) => node["value"]->Js.String.make
|
||||
| PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"])
|
||||
| PgNodeLambda(node) =>
|
||||
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}"
|
||||
"{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}"
|
||||
| PgNodeLetStatement(node) =>
|
||||
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
|
||||
| PgNodeModuleIdentifier(node) => `@${node["value"]}`
|
||||
|
|
|
@ -15,7 +15,7 @@ let rec fromNode = (node: Parse.node): expression => {
|
|||
let args =
|
||||
nodeLambda["args"]
|
||||
->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"])
|
||||
let body = nodeLambda["body"]->caseBlock
|
||||
let body = nodeLambda["body"]->fromNode
|
||||
|
||||
ExpressionBuilder.eLambda(args, body)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ let dispatch = (
|
|||
() => ReducerInterface_Date.dispatch(call, environment),
|
||||
() => ReducerInterface_Duration.dispatch(call, environment),
|
||||
() => ReducerInterface_Number.dispatch(call, environment),
|
||||
() => FunctionRegistry_Library.dispatch(call, environment, reducer),
|
||||
// () => FunctionRegistry_Library.dispatch(call, environment, reducer),
|
||||
])->E.O2.defaultFn(() => chain(call, environment, reducer))
|
||||
}
|
||||
|
||||
|
|
|
@ -63,14 +63,9 @@ let internalStdLib: Reducer_Bindings.t = {
|
|||
(name) => {
|
||||
let _ = res->Reducer_Bindings.set(name, Reducer_Expression_Lambda.makeFFILambda(
|
||||
(arguments, environment, reducer) => {
|
||||
switch FunctionRegistry_Library.dispatch((name, arguments), environment, reducer) {
|
||||
| Some(result) => {
|
||||
switch result {
|
||||
| Ok(value) => value
|
||||
| Error(error) => error->Reducer_ErrorValue.ErrorException->raise
|
||||
}
|
||||
}
|
||||
| None => Reducer_ErrorValue.RESymbolNotFound("Not found in registry")->Reducer_ErrorValue.ErrorException->raise
|
||||
switch FunctionRegistry_Library.call(name, arguments, environment, reducer) {
|
||||
| Ok(value) => value
|
||||
| Error(error) => error->Reducer_ErrorValue.ErrorException->raise
|
||||
}
|
||||
}
|
||||
)->Reducer_T.IEvLambda)
|
||||
|
|
Loading…
Reference in New Issue
Block a user