This commit is contained in:
Vyacheslav Matyukhin 2022-09-12 03:07:34 +04:00
parent fea89abff9
commit 6dc1cea045
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
8 changed files with 72 additions and 264 deletions

View File

@ -62,7 +62,7 @@ type function = {
isExperimental: bool, isExperimental: bool,
} }
type fnNameDict = Js.Dict.t<array<function>> type fnNameDict = Js.Dict.t<array<fnDefinition>>
type registry = {functions: array<function>, fnNameDict: fnNameDict} type registry = {functions: array<function>, fnNameDict: fnNameDict}
module FRType = { 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 = { module FnDefinition = {
type t = 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 = { module Registry = {
let toJson = (r: registry) => r.functions->E.A2.fmap(Function.toJson) 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 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 r.functions->E.A2.fmap(fn => fn.examples->E.A2.fmap(example => (fn, example)))->E.A.concatMany
let _buildFnNameDict = (r: array<function>): fnNameDict => { let _buildFnNameDict = (r: array<function>): fnNameDict => {
let allDefinitionsWithFns = // Sorry for the imperative style of this. But it's much easier/less buggy than the previous version.
r let res: fnNameDict = Js.Dict.empty()
->E.A2.fmap(fn => fn.definitions->E.A2.fmap(definitions => (fn, definitions))) r->Js.Array2.forEach(fn =>
->E.A.concatMany fn.definitions->Js.Array2.forEach(def => {
let functionsWithFnNames =
allDefinitionsWithFns
->E.A2.fmap(((fn, def)) => {
let nameWithNamespace = `${fn.nameSpace}.${def.name}` let nameWithNamespace = `${fn.nameSpace}.${def.name}`
let nameWithoutNamespace = def.name let nameWithoutNamespace = def.name
fn.requiresNamespace let names = fn.requiresNamespace ? [nameWithNamespace] : [nameWithNamespace, nameWithoutNamespace]
? [(nameWithNamespace, fn)] names->Js.Array2.forEach(name => {
: [(nameWithNamespace, fn), (nameWithoutNamespace, fn)] 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 res
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
} }
let make = (fns: array<function>): registry => { let make = (fns: array<function>): registry => {
@ -484,48 +290,33 @@ module Registry = {
{functions: fns, fnNameDict: dict} {functions: fns, fnNameDict: dict}
} }
/* let call = (
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 = (
registry, registry,
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall, fnName: string,
args: array<internalExpressionValue>,
env: Reducer_T.environment, env: Reducer_T.environment,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
) => { ): result<internalExpressionValue, Reducer_ErrorValue.errorValue> => {
_matchAndRun(~registry, ~fnName, ~args, ~env, ~reducer)->E.O2.fmap( switch Js.Dict.get(registry.fnNameDict, fnName) {
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)), | 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
}
} }
} }

View File

@ -12,4 +12,4 @@ let fnList = Belt.Array.concatMany([
]) ])
let registry = FunctionRegistry_Core.Registry.make(fnList) let registry = FunctionRegistry_Core.Registry.make(fnList)
let dispatch = FunctionRegistry_Core.Registry.dispatch(registry) let call = FunctionRegistry_Core.Registry.call(registry)

View File

@ -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)
}
},
()
),
], ],
(), (),
), ),

View File

@ -264,7 +264,7 @@ lambda
{ statements.push(finalExpression) { statements.push(finalExpression)
return h.nodeLambda(args, h.nodeBlock(statements)) } return h.nodeLambda(args, h.nodeBlock(statements)) }
/ '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}' / '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}'
{ return h.nodeLambda(args, h.nodeBlock([finalExpression])) } { return h.nodeLambda(args, finalExpression) }
arrayConstructor 'array' arrayConstructor 'array'
= '[' _nl ']' = '[' _nl ']'

View File

@ -29,7 +29,7 @@ type nodeIdentifier = {...node, "value": string}
type nodeInteger = {...node, "value": int} type nodeInteger = {...node, "value": int}
type nodeKeyValue = {...node, "key": node, "value": node} type nodeKeyValue = {...node, "key": node, "value": node}
type nodeRecord = {...node, "elements": array<nodeKeyValue>} 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 nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
type nodeModuleIdentifier = {...node, "value": string} type nodeModuleIdentifier = {...node, "value": string}
type nodeString = {...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 | PgNodeInteger(node) => node["value"]->Js.String.make
| PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"]) | PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"])
| PgNodeLambda(node) => | PgNodeLambda(node) =>
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}" "{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}"
| PgNodeLetStatement(node) => | PgNodeLetStatement(node) =>
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"]) pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
| PgNodeModuleIdentifier(node) => `@${node["value"]}` | PgNodeModuleIdentifier(node) => `@${node["value"]}`

View File

@ -15,7 +15,7 @@ let rec fromNode = (node: Parse.node): expression => {
let args = let args =
nodeLambda["args"] nodeLambda["args"]
->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) ->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"])
let body = nodeLambda["body"]->caseBlock let body = nodeLambda["body"]->fromNode
ExpressionBuilder.eLambda(args, body) ExpressionBuilder.eLambda(args, body)

View File

@ -14,7 +14,7 @@ let dispatch = (
() => ReducerInterface_Date.dispatch(call, environment), () => ReducerInterface_Date.dispatch(call, environment),
() => ReducerInterface_Duration.dispatch(call, environment), () => ReducerInterface_Duration.dispatch(call, environment),
() => ReducerInterface_Number.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)) ])->E.O2.defaultFn(() => chain(call, environment, reducer))
} }

View File

@ -63,14 +63,9 @@ let internalStdLib: Reducer_Bindings.t = {
(name) => { (name) => {
let _ = res->Reducer_Bindings.set(name, Reducer_Expression_Lambda.makeFFILambda( let _ = res->Reducer_Bindings.set(name, Reducer_Expression_Lambda.makeFFILambda(
(arguments, environment, reducer) => { (arguments, environment, reducer) => {
switch FunctionRegistry_Library.dispatch((name, arguments), environment, reducer) { switch FunctionRegistry_Library.call(name, arguments, environment, reducer) {
| Some(result) => { | Ok(value) => value
switch result { | Error(error) => error->Reducer_ErrorValue.ErrorException->raise
| Ok(value) => value
| Error(error) => error->Reducer_ErrorValue.ErrorException->raise
}
}
| None => Reducer_ErrorValue.RESymbolNotFound("Not found in registry")->Reducer_ErrorValue.ErrorException->raise
} }
} }
)->Reducer_T.IEvLambda) )->Reducer_T.IEvLambda)