// TODO: Restore run FFI? // TODO: Auto clean project based on topology module Bindings = Reducer_Bindings module Continuation = ReducerInterface_Value_Continuation module ErrorValue = Reducer_ErrorValue module ExternalExpressionValue = ReducerInterface_ExternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue module ProjectAccessorsT = ReducerProject_ProjectAccessors_T module ProjectItem = ReducerProject_ProjectItem module T = ReducerProject_T @genType.opaque type project = T.t type t = T.t module Private = { type internalProject = T.Private.t type t = T.Private.t let getSourceIds = (this: t): array => Belt.Map.String.keysToArray(this["items"]) let getItem = (this: t, sourceId: string) => Belt.Map.String.getWithDefault(this["items"], sourceId, ProjectItem.emptyItem) let getImmediateDependencies = (this: t, sourceId: string): ProjectItem.T.includesType => getItem(this, sourceId)->ProjectItem.getImmediateDependencies type topologicalSortState = (Belt.Map.String.t, list) let rec topologicalSortUtil = ( this: t, sourceId: string, state: topologicalSortState, ): topologicalSortState => { let dependencies = getImmediateDependencies(this, sourceId)->Belt.Result.getWithDefault([]) let (visited, stack) = state let myVisited = Belt.Map.String.set(visited, sourceId, true) let (newVisited, newStack) = dependencies->Belt.Array.reduce((myVisited, stack), ( (currVisited, currStack), dependency, ) => { if !Belt.Map.String.getWithDefault(currVisited, dependency, false) { topologicalSortUtil(this, dependency, (currVisited, currStack)) } else { (currVisited, currStack) } }) (newVisited, list{sourceId, ...newStack}) } let getTopologicalSort = (this: t): array => { let (_visited, stack) = getSourceIds(this)->Belt.Array.reduce((Belt.Map.String.empty, list{}), ( (currVisited, currStack), currId, ) => if !Belt.Map.String.getWithDefault(currVisited, currId, false) { topologicalSortUtil(this, currId, (currVisited, currStack)) } else { (currVisited, currStack) } ) Belt.List.reverse(stack)->Belt.List.toArray } let getTopologicalSortFor = (this: t, sourceId) => { let runOrder = getTopologicalSort(this) let index = runOrder->Js.Array2.indexOf(sourceId) let after = Belt.Array.sliceToEnd(runOrder, index + 1) let before = Js.Array2.slice(runOrder, ~start=0, ~end_=index + 1) (before, after) } let getRunOrder = getTopologicalSort let createProject = () => { let this: t = { "items": Belt.Map.String.empty, "stdLib": ReducerInterface_StdLib.internalStdLib, "environment": InternalExpressionValue.defaultEnvironment, } this } let getRunOrderFor = (this: t, sourceId: string) => { let (runOrder, _) = getTopologicalSortFor(this, sourceId) runOrder } let getDependencies = (this: t, sourceId: string): array => { let runOrder = getRunOrderFor(this, sourceId) let _ = Js.Array2.pop(runOrder) runOrder } let getDependents = (this: t, sourceId: string): array => { let (_, dependents) = getTopologicalSortFor(this, sourceId) dependents } let rec touchSource = (this: t, sourceId: string): unit => { let item = this->getItem(sourceId) let newItem = ProjectItem.touchSource(item) Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) touchDependents(this, sourceId) } and touchDependents = (this: t, sourceId: string): unit => { let _ = getDependents(this, sourceId)->Belt.Array.forEach(_, touchSource(this, _)) } let getSource = (this: t, sourceId: string): option => Belt.Map.String.get(this["items"], sourceId)->Belt.Option.map(ProjectItem.getSource) let setSource = (this: t, sourceId: string, value: string): unit => { let newItem = this->getItem(sourceId)->ProjectItem.setSource(value) Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) touchDependents(this, sourceId) } let clean = (this: t, sourceId: string): unit => { let newItem = this->getItem(sourceId)->ProjectItem.clean Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) } let cleanAll = (this: t): unit => getSourceIds(this)->Belt.Array.forEach(sourceId => clean(this, sourceId)) let cleanResults = (this: t, sourceId: string): unit => { let newItem = this->getItem(sourceId)->ProjectItem.cleanResults Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) } let cleanAllResults = (this: t): unit => getSourceIds(this)->Belt.Array.forEach(sourceId => cleanResults(this, sourceId)) let getIncludes = (this: t, sourceId: string): ProjectItem.T.includesType => this->getItem(sourceId)->ProjectItem.getIncludes let setContinues = (this: t, sourceId: string, continues: array): unit => { let newItem = this->getItem(sourceId)->ProjectItem.setContinues(continues) Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) touchSource(this, sourceId) } let getContinues = (this: t, sourceId: string): array => ProjectItem.getContinues(this->getItem(sourceId)) let removeContinues = (this: t, sourceId: string): unit => { let newItem = this->getItem(sourceId)->ProjectItem.removeContinues Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) touchSource(this, sourceId) } let getContinuation = (this: t, sourceId: string): ProjectItem.T.continuationArgumentType => this->getItem(sourceId)->ProjectItem.getContinuation let setContinuation = ( this: t, sourceId: string, continuation: ProjectItem.T.continuationArgumentType, ): unit => { let newItem = this->getItem(sourceId)->ProjectItem.setContinuation(continuation) Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) } let getResult = (this: t, sourceId: string): ProjectItem.T.resultType => this->getItem(sourceId)->ProjectItem.getResult let setResult = (this: t, sourceId: string, value: ProjectItem.T.resultArgumentType): unit => { let newItem = this->getItem(sourceId)->ProjectItem.setResult(value) Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) } let getExternalResult = (this: t, sourceId: string): ProjectItem.T.externalResultType => this->getItem(sourceId)->ProjectItem.getExternalResult let parseIncludes = (this: t, sourceId: string): unit => { let newItem = this->getItem(sourceId)->ProjectItem.parseIncludes Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) } let rawParse = (this: t, sourceId): unit => { let newItem = this->getItem(sourceId)->ProjectItem.rawParse Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) } let getStdLib = (this: t): Reducer_Bindings.t => this["stdLib"] let setStdLib = (this: t, value: Reducer_Bindings.t): unit => T.Private.setFieldStdLib(this, value) let getEnvironment = (this: t): InternalExpressionValue.environment => this["environment"] let setEnvironment = (this: t, value: InternalExpressionValue.environment): unit => T.Private.setFieldEnvironment(this, value) let getExternalBindings = ( this: t, sourceId: string, ): ProjectItem.T.externalBindingsArgumentType => { let those = this->getContinuation(sourceId) let these = this->getStdLib let ofUser = Continuation.minus(those, these) ofUser->InternalExpressionValue.nameSpaceToTypeScriptBindings } let buildProjectAccessors = (this: t): ProjectAccessorsT.t => { continuation: Bindings.emptyBindings, stdLib: getStdLib(this), environment: getEnvironment(this), } let doRunWithContinuation = ( this: t, sourceId: string, continuation: ProjectItem.T.continuation, ): unit => { let accessors = buildProjectAccessors(this) let newItem = this->getItem(sourceId)->ProjectItem.run(continuation, accessors) Belt.Map.String.set(this["items"], sourceId, newItem)->T.Private.setFieldItems(this, _) setContinuation(this, sourceId, accessors.continuation) } type runState = (ProjectItem.T.resultArgumentType, ProjectItem.T.continuation) let tryRunWithContinuation = ( this: t, sourceId: string, (rPrevResult: ProjectItem.T.resultArgumentType, continuation: ProjectItem.T.continuation), ): (ProjectItem.T.resultArgumentType, ProjectItem.T.continuation) => { switch getResult(this, sourceId) { | Some(result) => (result, getContinuation(this, sourceId)) // already ran | None => switch rPrevResult { | Error(error) => { setResult(this, sourceId, Error(error)) (Error(error), continuation) } | Ok(_prevResult) => { doRunWithContinuation(this, sourceId, continuation) ( getResult(this, sourceId)->Belt.Option.getWithDefault(rPrevResult), getContinuation(this, sourceId), ) } } } } let runAll = (this: t): unit => { let runOrder = getTopologicalSort(this) let initialState = (Ok(InternalExpressionValue.IEvVoid), getStdLib(this)) let _finalState = Belt.Array.reduce(runOrder, initialState, (currState, currId) => tryRunWithContinuation(this, currId, currState) ) } let run = (this: t, sourceId: string): unit => { let runOrder = getRunOrderFor(this, sourceId) let initialState = (Ok(InternalExpressionValue.IEvVoid), getStdLib(this)) let _finalState = Belt.Array.reduce(runOrder, initialState, (currState, currId) => tryRunWithContinuation(this, currId, currState) ) } let evaluate = (sourceCode: string) => { let project = createProject() setSource(project, "main", sourceCode) runAll(project) ( getResult(project, "main")->Belt.Option.getWithDefault(IEvVoid->Ok), getContinuation(project, "main"), ) } } /* PUBLIC FUNCTIONS */ // Create a new this to hold the sources, executables, bindings and other data. // The this is a mutable object for use in TypeScript. let createProject = (): t => Private.createProject()->T.Private.castFromInternalProject // Answers the array of existing source ids to enumerate over. let getSourceIds = (this: t): array => this->T.Private.castToInternalProject->Private.getSourceIds // Sets the source for a given source id. let setSource = (this: t, sourceId: string, value: string): unit => this->T.Private.castToInternalProject->Private.setSource(sourceId, value) // Gets the source for a given source id. let getSource = (this: t, sourceId: string): option => this->T.Private.castToInternalProject->Private.getSource(sourceId) // Touches the source for a given source id. This forces the dependency graph to be re-evaluated. // Touching source code clears the includes so that they can be reevaluated. let touchSource = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.touchSource(sourceId) // Cleans the compilation artifacts for a given source id. The results stay untouched. let clean = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.clean(sourceId) // Cleans all compilation artifacts for all the this. The results stay untouched. let cleanAll = (this: t): unit => this->T.Private.castToInternalProject->Private.cleanAll // Cleans results. Compilation stays untouched to rerun the source. let cleanResults = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.cleanResults(sourceId) // Cleans all results. Compilations stays untouched to rerun the source. let cleanAllResults = (this: t): unit => this->T.Private.castToInternalProject->Private.cleanAllResults let getIncludes = (this: t, sourceId: string): ProjectItem.T.includesType => this->T.Private.castToInternalProject->Private.getIncludes(sourceId) let getContinues = (this: t, sourceId: string): array => this->T.Private.castToInternalProject->Private.getContinues(sourceId) // setContinues acts like an include hidden in the source. It is used to define a continuation. let setContinues = (this: t, sourceId: string, continues: array): unit => this->T.Private.castToInternalProject->Private.setContinues(sourceId, continues) // This source is not continuing any other source. It is a standalone source. // Touches this source also. let removeContinues = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.removeContinues(sourceId) // Gets includes and continues for a given source id. SourceId is depending on them let getDependencies = (this: t, sourceId: string): array => this->T.Private.castToInternalProject->Private.getDependencies(sourceId) // Get source ids depending on a given source id. let getDependents = (this: t, sourceId: string): array => this->T.Private.castToInternalProject->Private.getDependents(sourceId) // Get run order for all sources. It is a topological sort of the dependency graph. let getRunOrder = (this: t) => this->T.Private.castToInternalProject->Private.getRunOrder // Get run order for a given source id. It is a topological sort of the dependency graph. let getRunOrderFor = (this: t, sourceId: string) => this->T.Private.castToInternalProject->Private.getRunOrderFor(sourceId) // Parse includes so that you can load them before running. Use getIncludes to get the includes. // It is your responsibility to load the includes before running. let parseIncludes = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.parseIncludes(sourceId) // Parse the source code if it is not done already. Use getRawParse to get the parse tree let rawParse = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.rawParse(sourceId) // Runs the source code. // The code is parsed if it is not already done. // If it continues/includes another source then it will run that source also if is not already done. let run = (this: t, sourceId: string): unit => this->T.Private.castToInternalProject->Private.run(sourceId) // Runs all the sources. let runAll = (this: t): unit => this->T.Private.castToInternalProject->Private.runAll // WARNING" getExternalBindings will be deprecated. Cyclic directed graph problems // Get the bindings after running the source code. let getExternalBindings = (this: t, sourceId: string): ExternalExpressionValue.record => this->T.Private.castToInternalProject->Private.getExternalBindings(sourceId) //WARNING: externalResult will be deprecated. Cyclic directed graph problems let getExternalResult = (this: t, sourceId: string): option< result, > => this->T.Private.castToInternalProject->Private.getExternalResult(sourceId) // This is a convenience function to get the result of a single source. // You cannot use includes let evaluate = (sourceCode: string): ('r, 'b) => { let (result, continuation) = Private.evaluate(sourceCode) ( result->Belt.Result.map(InternalExpressionValue.toExternal), continuation->InternalExpressionValue.nameSpaceToTypeScriptBindings, ) } let foreignFunctionInterface = ( lambdaValue: ExternalExpressionValue.lambdaValue, argArray: array, environment: ExternalExpressionValue.environment, ) => { let internallambdaValue = InternalExpressionValue.lambdaValueToInternal(lambdaValue) let internalArgArray = argArray->Js.Array2.map(InternalExpressionValue.toInternal) let accessors = ProjectAccessorsT.identityAccessorsWithEnvironment(environment) Reducer_Expression_Lambda.foreignFunctionInterface( internallambdaValue, internalArgArray, accessors, Reducer_Expression.reduceExpressionInProject, )->Belt.Result.map(InternalExpressionValue.toExternal) }