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,
}
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
let call = (
registry,
fnName: string,
args: array<internalExpressionValue>,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
): 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: ${defs}`
`There are function matches for ${fnName}(), but with different arguments: ${defsString}`
}
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 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
}
}
let dispatch = (
registry,
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall,
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)),
)
}
| 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 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)
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 ']'

View File

@ -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"]}`

View File

@ -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)

View File

@ -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))
}

View File

@ -63,16 +63,11 @@ 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 {
switch FunctionRegistry_Library.call(name, arguments, environment, reducer) {
| 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)
}
)