CallStack, location -> frame, WIP

This commit is contained in:
Vyacheslav Matyukhin 2022-09-29 19:48:31 +04:00
parent 4c56b2fd07
commit 184584c9f3
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
21 changed files with 310 additions and 213 deletions

View File

@ -1,6 +1,8 @@
import * as RSError from "../rescript/SqError.gen";
export type SqLocation = RSError.location;
import * as RSCallStack from "../rescript/Reducer/Reducer_CallStack.gen";
export type SqFrame = RSCallStack.frame;
export class SqError {
constructor(private _value: RSError.t) {}
@ -17,13 +19,17 @@ export class SqError {
return new SqError(RSError.createOtherError(v));
}
toLocationArray() {
const stackTrace = RSError.getStackTrace(this._value);
return stackTrace ? RSError.StackTrace.toLocationArray(stackTrace) : [];
getTopFrame(): SqCallFrame | undefined {
const frame = RSCallStack.getTopFrame(RSError.getStackTrace(this._value));
return frame ? new SqCallFrame(frame) : undefined;
}
toLocation() {
return RSError.getLocation(this._value);
getFrameArray(): SqCallFrame[] {
const frames = RSError.getFrameArray(this._value);
return frames.map((frame) => new SqCallFrame(frame));
}
}
export class SqCallFrame {
constructor(private _value: SqFrame) {}
}

View File

@ -13,7 +13,7 @@ export {
environment,
defaultEnvironment,
} from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
export { SqError, SqLocation } from "./SqError";
export { SqError } from "./SqError";
export { SqShape } from "./SqPointSetDist";
export { resultMap } from "./types";

View File

@ -67,7 +67,7 @@ module Integration = {
let applyFunctionAtFloatToFloatOption = (point: float) => {
// Defined here so that it has access to environment, reducer
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
let resultAsInternalExpression = Reducer_Lambda.doLambdaCall(
aLambda,
[pointAsInternalExpression],
environment,
@ -308,7 +308,7 @@ module DiminishingReturns = {
let applyFunctionAtPoint = (lambda, point: float) => {
// Defined here so that it has access to environment, reducer
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
let resultAsInternalExpression = Reducer_Lambda.doLambdaCall(
lambda,
[pointAsInternalExpression],
environment,

View File

@ -76,7 +76,7 @@ let library = [
->Belt.Array.map(dictValue =>
switch dictValue {
| IEvRecord(dict) => dict
| _ => impossibleError->SqError.Message.toException
| _ => impossibleError->SqError.Message.throw
}
)
->Internals.mergeMany

View File

@ -22,7 +22,8 @@ module DistributionCreation = {
FnDefinition.make(
~name,
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
~run=(inputs, context, _) =>
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env=context.environment),
(),
)
}
@ -31,8 +32,10 @@ module DistributionCreation = {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
~run=(inputs, env, _) =>
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))->process(~fn, ~env),
~run=(inputs, context, _) =>
inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))
->process(~fn, ~env=context.environment),
(),
)
}
@ -41,10 +44,10 @@ module DistributionCreation = {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
~run=(inputs, env, _) =>
~run=(inputs, context, _) =>
inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev"))
->process(~fn, ~env),
->process(~fn, ~env=context.environment),
(),
)
}
@ -61,7 +64,8 @@ module DistributionCreation = {
FnDefinition.make(
~name,
~inputs=[FRTypeDistOrNumber],
~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
~run=(inputs, context, _) =>
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env=context.environment),
(),
)
}

View File

@ -313,7 +313,7 @@ module Old = {
| None =>
SqError.Message.REOther(
"Internal error in FR_GenericDist implementation",
)->SqError.Message.toException
)->SqError.Message.throw
}
}
@ -326,7 +326,7 @@ let makeProxyFn = (name: string, inputs: array<frType>) => {
FnDefinition.make(
~name,
~inputs,
~run=(inputs, env, _) => Old.dispatch((name, inputs), env),
~run=(inputs, context, _) => Old.dispatch((name, inputs), context.environment),
(),
),
],
@ -402,9 +402,9 @@ let library = E.A.concatMany([
])
// FIXME - impossible to implement with FR due to arbitrary parameters length;
let mxLambda = Reducer_Expression_Lambda.makeFFILambda((inputs, env, _) => {
switch Old.dispatch(("mx", inputs), env) {
let mxLambda = Reducer_Lambda.makeFFILambda("mx", (inputs, context, _) => {
switch Old.dispatch(("mx", inputs), context.environment) {
| Ok(value) => value
| Error(e) => e->SqError.Message.toException
| Error(e) => e->SqError.Message.throw
}
})

View File

@ -26,11 +26,11 @@ module Internals = {
let map = (
array: array<Reducer_T.value>,
eLambdaValue,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
): Reducer_T.value => {
Belt.Array.map(array, elem =>
Reducer_Expression_Lambda.doLambdaCall(eLambdaValue, [elem], env, reducer)
Reducer_Lambda.doLambdaCall(eLambdaValue, [elem], context, reducer)
)->Wrappers.evArray
}
@ -38,11 +38,11 @@ module Internals = {
aValueArray,
initialValue,
aLambdaValue,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) => {
aValueArray->E.A.reduce(initialValue, (acc, elem) =>
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer)
Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer)
)
}
@ -50,22 +50,22 @@ module Internals = {
aValueArray,
initialValue,
aLambdaValue,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) => {
aValueArray->Belt.Array.reduceReverse(initialValue, (acc, elem) =>
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer)
Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer)
)
}
let filter = (
aValueArray,
aLambdaValue,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) => {
Js.Array2.filter(aValueArray, elem => {
let result = Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [elem], env, reducer)
let result = Reducer_Lambda.doLambdaCall(aLambdaValue, [elem], context, reducer)
switch result {
| IEvBool(true) => true
| _ => false

View File

@ -16,16 +16,16 @@ let inputsToDist = (inputs: array<Reducer_T.value>, xyShapeToPointSetDist) => {
let yValue = map->Belt.Map.String.get("y")
switch (xValue, yValue) {
| (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y)
| _ => impossibleError->SqError.Message.toException
| _ => impossibleError->SqError.Message.throw
}
}
| _ => impossibleError->SqError.Message.toException
| _ => impossibleError->SqError.Message.throw
}
)
->Ok
->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString))
->E.R2.fmap(r => Reducer_T.IEvDistribution(PointSet(r->xyShapeToPointSetDist)))
| _ => impossibleError->SqError.Message.toException
| _ => impossibleError->SqError.Message.throw
}
}
@ -39,7 +39,7 @@ module Internal = {
}
let doLambdaCall = (aLambdaValue, list, env, reducer) =>
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
switch Reducer_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
| Reducer_T.IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
}
@ -61,13 +61,13 @@ let library = [
FnDefinition.make(
~name="fromDist",
~inputs=[FRTypeDist],
~run=(inputs, env, _) =>
~run=(inputs, context, _) =>
switch inputs {
| [IEvDistribution(dist)] =>
GenericDist.toPointSet(
dist,
~xyPointLength=env.xyPointLength,
~sampleCount=env.sampleCount,
~xyPointLength=context.environment.xyPointLength,
~sampleCount=context.environment.sampleCount,
(),
)
->E.R2.fmap(Wrappers.pointSet)

View File

@ -10,10 +10,10 @@ module Internal = {
let doLambdaCall = (
aLambdaValue,
list,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) =>
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
switch Reducer_Lambda.doLambdaCall(aLambdaValue, list, context, reducer) {
| IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
}
@ -25,26 +25,25 @@ module Internal = {
}
//TODO: I don't know why this seems to need at least one input
let fromFn = (aLambdaValue, environment: Reducer_T.environment, reducer: Reducer_T.reducerFn) => {
let sampleCount = environment.sampleCount
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer)
let fromFn = (aLambdaValue, context: Reducer_T.context, reducer: Reducer_T.reducerFn) => {
let sampleCount = context.environment.sampleCount
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer)
Belt_Array.makeBy(sampleCount, r => fn(r->Js.Int.toFloat))->E.A.R.firstErrorOrOpen
}
let map1 = (sampleSetDist: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer)
let map1 = (sampleSetDist: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer)
SampleSetDist.samplesMap(~fn, sampleSetDist)->toType
}
let map2 = (t1: t, t2: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = (a, b) =>
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], environment, reducer)
let map2 = (t1: t, t2: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = (a, b) => doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], context, reducer)
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
}
let map3 = (t1: t, t2: t, t3: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let map3 = (t1: t, t2: t, t3: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = (a, b, c) =>
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], environment, reducer)
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], context, reducer)
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
}
@ -60,7 +59,7 @@ module Internal = {
let mapN = (
aValueArray: array<Reducer_T.value>,
aLambdaValue,
environment: Reducer_T.environment,
context: Reducer_T.context,
reducer,
) => {
switch parseSampleSetArray(aValueArray) {
@ -69,7 +68,7 @@ module Internal = {
doLambdaCall(
aLambdaValue,
[IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))],
environment,
context,
reducer,
)
SampleSetDist.mapN(~fn, ~t1)->toType
@ -89,10 +88,10 @@ let libaryBase = [
FnDefinition.make(
~name="fromDist",
~inputs=[FRTypeDist],
~run=(inputs, environment, _) =>
~run=(inputs, context, _) =>
switch inputs {
| [IEvDistribution(dist)] =>
GenericDist.toSampleSetDist(dist, environment.sampleCount)
GenericDist.toSampleSetDist(dist, context.environment.sampleCount)
->E.R2.fmap(Wrappers.sampleSet)
->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => SqError.Message.REDistributionError(e))

View File

@ -30,15 +30,15 @@ let library = [
("prior", FRTypeDist),
]),
],
~run=(inputs, environment, _) => {
~run=(inputs, context, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(
inputs,
("estimate", "answer", "prior"),
) {
| Ok([IEvDistribution(estimate), IEvDistribution(d), IEvDistribution(prior)]) =>
runScoring(estimate, Score_Dist(d), Some(prior), environment)
runScoring(estimate, Score_Dist(d), Some(prior), context.environment)
| Ok([IEvDistribution(estimate), IEvNumber(d), IEvDistribution(prior)]) =>
runScoring(estimate, Score_Scalar(d), Some(prior), environment)
runScoring(estimate, Score_Scalar(d), Some(prior), context.environment)
| Error(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
@ -48,15 +48,15 @@ let library = [
FnDefinition.make(
~name="logScore",
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
~run=(inputs, environment, _) => {
~run=(inputs, context, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(
inputs,
("estimate", "answer"),
) {
| Ok([IEvDistribution(estimate), IEvDistribution(d)]) =>
runScoring(estimate, Score_Dist(d), None, environment)
runScoring(estimate, Score_Dist(d), None, context.environment)
| Ok([IEvDistribution(estimate), IEvNumber(d)]) =>
runScoring(estimate, Score_Scalar(d), None, environment)
runScoring(estimate, Score_Scalar(d), None, context.environment)
| Error(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
@ -76,10 +76,10 @@ let library = [
FnDefinition.make(
~name="klDivergence",
~inputs=[FRTypeDist, FRTypeDist],
~run=(inputs, environment, _) => {
~run=(inputs, context, _) => {
switch inputs {
| [IEvDistribution(estimate), IEvDistribution(d)] =>
runScoring(estimate, Score_Dist(d), None, environment)
runScoring(estimate, Score_Dist(d), None, context.environment)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},

View File

@ -1,9 +1,7 @@
@genType type squiggleValue_Lambda = Reducer_T.lambdaValue //re-export
@genType
let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringFunction(v)
let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringLambda(v)
@genType
let parameters = (v: squiggleValue_Lambda): array<string> => {
v.parameters
}
let parameters = (v: squiggleValue_Lambda): array<string> => Reducer_Lambda.parameters(v)

View File

@ -30,7 +30,7 @@ type fnDefinition = {
inputs: array<frType>,
run: (
array<Reducer_T.value>,
Reducer_T.environment,
Reducer_T.context,
Reducer_T.reducerFn,
) => result<Reducer_T.value, errorMessage>,
}
@ -122,11 +122,11 @@ module FnDefinition = {
let run = (
t: t,
args: array<Reducer_T.value>,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) => {
switch t->isMatch(args) {
| true => t.run(args, env, reducer)
| true => t.run(args, context, reducer)
| false => REOther("Incorrect Types")->Error
}
}
@ -164,7 +164,7 @@ module Function = {
nameSpace: nameSpace,
definitions: definitions,
output: output,
examples: examples |> E.O.default([]),
examples: examples->E.O2.default([]),
isExperimental: isExperimental,
requiresNamespace: requiresNamespace,
description: description,
@ -225,7 +225,7 @@ module Registry = {
registry,
fnName: string,
args: array<Reducer_T.value>,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
): result<Reducer_T.value, errorMessage> => {
switch Belt.Map.String.get(registry.fnNameDict, fnName) {
@ -241,7 +241,7 @@ module Registry = {
let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args))
switch match {
| Some(def) => def->FnDefinition.run(args, env, reducer)
| Some(def) => def->FnDefinition.run(args, context, reducer)
| None => REOther(showNameMatchDefinitions())->Error
}
}

View File

@ -0,0 +1,36 @@
@genType.opaque
type rec lambdaFrame = {location: Reducer_Peggy_Parse.location, name: string}
@genType.opaque and ffiFrame = {name: string}
@genType
and frame =
| InLambda(lambdaFrame)
| InFFI(ffiFrame)
let toStringFrame = (t: frame) =>
switch t {
| InLambda({location}) =>
`Line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}, source ${location.source}`
| InFFI({name}) => `Builtin ${name}`
}
@genType.opaque
type rec t = list<frame>
let make = (): t => list{}
let extend = (t: t, frame: frame) => t->Belt.List.add(frame)
let toString = (t: t) =>
t->Belt.List.map(s => " " ++ s->toStringFrame ++ "\n")->Belt.List.toArray->Js.Array2.joinWith("")
@genType
let toFrameArray = (t: t): array<frame> => t->Belt.List.toArray
@genType
let getTopFrame = (t: t): option<frame> => t->Belt.List.head
let isEmpty = (t: t): bool =>
switch t->Belt.List.head {
| Some(_) => true
| None => false
}

View File

@ -4,9 +4,8 @@ let defaultEnvironment: Reducer_T.environment = DistributionOperation.defaultEnv
let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environment): t => {
{
callStack: list{},
bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend,
environment: environment,
}
}
let createDefaultContext = (): t => createContext(SquiggleLibrary_StdLib.stdLib, defaultEnvironment)

View File

@ -1,5 +1,4 @@
module Bindings = Reducer_Bindings
module Lambda = Reducer_Expression_Lambda
module Result = Belt.Result
module T = Reducer_T
@ -7,8 +6,8 @@ let toLocation = (expression: T.expression): SqError.location => {
expression.ast.location
}
let throwFrom = (error: SqError.Message.t, expression: T.expression) =>
error->SqError.fromMessageWithLocation(expression->toLocation)->SqError.throw
let throwFrom = (error: SqError.Message.t, context: T.context) =>
error->SqError.throwMessage(context)
/*
Recursively evaluate the expression
@ -54,7 +53,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
let (key, _) = eKey->evaluate(context)
let keyString = switch key {
| IEvString(s) => s
| _ => REOther("Record keys must be strings")->throwFrom(expression)
| _ => REOther("Record keys must be strings")->throwFrom(context)
}
let (value, _) = eValue->evaluate(context)
(keyString, value)
@ -78,7 +77,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
| T.ESymbol(name) =>
switch context.bindings->Bindings.get(name) {
| Some(v) => (v, context)
| None => RESymbolNotFound(name)->throwFrom(expression)
| None => RESymbolNotFound(name)->throwFrom(context)
}
| T.EValue(value) => (value, context)
@ -87,12 +86,18 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
let (predicateResult, _) = predicate->evaluate(context)
switch predicateResult {
| T.IEvBool(value) => (value ? trueCase : falseCase)->evaluate(context)
| _ => REExpectedType("Boolean", "")->throwFrom(expression)
| _ => REExpectedType("Boolean", "")->throwFrom(context)
}
}
| T.ELambda(parameters, body) => (
Lambda.makeLambda(parameters, context.bindings, body)->T.IEvLambda,
Reducer_Lambda.makeLambda(
None, // TODO - pass function name from parser
parameters,
context.bindings,
body,
expression->toLocation,
)->T.IEvLambda,
context,
)
@ -103,14 +108,11 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
argValue
})
switch lambda {
| T.IEvLambda(lambda) =>
try {
let result = Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate)
| T.IEvLambda(lambda) => {
let result = Reducer_Lambda.doLambdaCall(lambda, argValues, context, evaluate)
(result, context)
} catch {
| exn => exn->SqError.fromException->SqError.extend(expression->toLocation)->SqError.throw
}
| _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression)
| _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(context)
}
}
}
@ -122,8 +124,11 @@ module BackCompatible = {
let parse = (peggyCode: string): result<T.expression, Reducer_Peggy_Parse.parseError> =>
peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode)
let createDefaultContext = () =>
Reducer_Context.createContext(SquiggleLibrary_StdLib.stdLib, Reducer_Context.defaultEnvironment)
let evaluate = (expression: T.expression): result<T.value, SqError.t> => {
let context = Reducer_Context.createDefaultContext()
let context = createDefaultContext()
try {
let (value, _) = expression->evaluate(context)
value->Ok

View File

@ -1,58 +0,0 @@
let doLambdaCall = (
lambdaValue: Reducer_T.lambdaValue,
args,
environment: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
): Reducer_T.value => {
lambdaValue.body(args, environment, reducer)
}
let makeLambda = (
parameters: array<string>,
bindings: Reducer_T.bindings,
body: Reducer_T.expression,
): Reducer_T.lambdaValue => {
// TODO - clone bindings to avoid later redefinitions affecting lambdas?
// Note: with this implementation, FFI lambdas (created by other methods than calling `makeLambda`) are allowed to violate the rules, pollute the bindings, etc.
// Not sure yet if that's a bug or a feature.
// FunctionRegistry functions are unaffected by this, their API is too limited.
let lambda = (
arguments: array<Reducer_T.value>,
environment: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) => {
let argsLength = arguments->Js.Array2.length
let parametersLength = parameters->Js.Array2.length
if argsLength !== parametersLength {
SqError.Message.REArityError(None, parametersLength, argsLength)->SqError.Message.toException
}
let localBindings = bindings->Reducer_Bindings.extend
let localBindingsWithParameters = parameters->Belt.Array.reduceWithIndex(localBindings, (
currentBindings,
parameter,
index,
) => {
currentBindings->Reducer_Bindings.set(parameter, arguments[index])
})
let (value, _) = reducer(
body,
{bindings: localBindingsWithParameters, environment: environment},
)
value
}
{
// context: bindings,
body: lambda,
parameters: parameters,
}
}
let makeFFILambda = (body: Reducer_T.lambdaBody): Reducer_T.lambdaValue => {
body: body,
parameters: ["..."],
}

View File

@ -0,0 +1,88 @@
type t = Reducer_T.lambdaValue
// user-defined functions, i.e. `add2 = {|x, y| x + y}`, are built by this method
let makeLambda = (
name: option<string>,
parameters: array<string>,
bindings: Reducer_T.bindings,
body: Reducer_T.expression,
location: Reducer_Peggy_Parse.location,
): t => {
let lambda = (
arguments: array<Reducer_T.value>,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) => {
let argsLength = arguments->E.A.length
let parametersLength = parameters->E.A.length
if argsLength !== parametersLength {
SqError.Message.REArityError(None, parametersLength, argsLength)->SqError.Message.throw
}
// create new bindings scope - technically not necessary, since bindings are immutable, but might help with debugging/new features in the future
let localBindings = bindings->Reducer_Bindings.extend
let localBindingsWithParameters = parameters->Belt.Array.reduceWithIndex(localBindings, (
currentBindings,
parameter,
index,
) => {
currentBindings->Reducer_Bindings.set(parameter, arguments[index])
})
let lambdaContext: Reducer_T.context = {
bindings: localBindingsWithParameters, // based on bindings at the moment of lambda creation
environment: context.environment, // environment at the moment when lambda is called
callStack: context.callStack, // extended by main `evaluate` function
}
let (value, _) = reducer(body, lambdaContext)
value
}
FnLambda({
// context: bindings,
name: name,
body: lambda,
parameters: parameters,
location: location,
})
}
// stdlib lambdas (everything in FunctionRegistry) is built by this method. Body is generated in SquiggleLibrary_StdLib.res
let makeFFILambda = (name: string, body: Reducer_T.lambdaBody): t => FnBuiltin({
// Note: current bindings could be accidentally exposed here through context (compare with native lambda implementation above, where we override them with local bindings).
// But FunctionRegistry API is too limited for that to matter. Please take care not to violate that in the future by accident.
body: body,
name: name,
})
let extendCallStack = (t: t, callStack: Reducer_CallStack.t): Reducer_CallStack.t => {
switch t {
| FnLambda({location}) =>
callStack->Reducer_CallStack.extend(InLambda({location: location, name: "TODO"})) // FIXME
| FnBuiltin({name}) => callStack->Reducer_CallStack.extend(InFFI({name: name}))
}
}
// this function doesn't scale to FunctionRegistry's polymorphic functions
let parameters = (t: t): array<string> => {
switch t {
| FnLambda({parameters}) => parameters
| FnBuiltin(_) => ["..."]
}
}
let doLambdaCall = (t: t, args, context: Reducer_Context.t, reducer) => {
let newContext = {
...context,
callStack: t->extendCallStack(context.callStack),
}
SqError.contextualizeAndRethrow(() => {
switch t {
| FnLambda({body}) => body(args, newContext, reducer)
| FnBuiltin({body}) => body(args, newContext, reducer)
}
}, newContext)
}

View File

@ -15,12 +15,16 @@ type rec value =
| IEvVoid
@genType.opaque and arrayValue = array<value>
@genType.opaque and map = Belt.Map.String.t<value>
and lambdaBody = (array<value>, environment, reducerFn) => value
and lambdaBody = (array<value>, context, reducerFn) => value
@genType.opaque
and lambdaValue = {
and lambdaValue =
| FnLambda({
parameters: array<string>,
body: lambdaBody,
}
location: Reducer_Peggy_Parse.location,
name: option<string>,
})
| FnBuiltin({body: lambdaBody, name: string})
@genType.opaque and lambdaDeclaration = Declaration.declaration<lambdaValue>
and expressionContent =
| EBlock(array<expression>)
@ -49,6 +53,7 @@ and bindings = {
and context = {
bindings: bindings,
environment: environment,
callStack: Reducer_CallStack.t,
}
and reducerFn = (expression, context) => (value, context)

View File

@ -28,10 +28,12 @@ and toStringCall = fName => `:${fName}`
and toStringDate = date => DateTime.Date.toString(date)
and toStringDeclaration = d => Declaration.toString(d, r => toString(IEvLambda(r)))
and toStringDistribution = dist => GenericDist.toString(dist)
and toStringLambda = (lambdaValue: T.lambdaValue) =>
`lambda(${Js.Array2.toString(lambdaValue.parameters)}=>internal code)`
and toStringFunction = (lambdaValue: T.lambdaValue) =>
`function(${Js.Array2.toString(lambdaValue.parameters)})`
and toStringLambda = (lambdaValue: T.lambdaValue) => {
switch lambdaValue {
| FnLambda({parameters}) => `lambda(${Js.Array2.toString(parameters)}=>internal code)`
| FnBuiltin(_) => "Builtin function"
}
}
and toStringNumber = aNumber => Js.String.make(aNumber)
and toStringRecord = aMap => aMap->toStringMap
and toStringString = aString => `'${aString}'`
@ -143,7 +145,7 @@ let arrayToValueArray = (arr: array<t>): array<t> => arr
let resultToValue = (rExpression: result<t, SqError.Message.t>): t =>
switch rExpression {
| Ok(expression) => expression
| Error(errorValue) => SqError.Message.toException(errorValue)
| Error(errorValue) => SqError.Message.throw(errorValue)
}
let recordToKeyValuePairs = (record: T.map): array<(string, t)> => record->Belt.Map.String.toArray

View File

@ -1,5 +1,7 @@
type location = Reducer_Peggy_Parse.location
// Messages don't contain any stack trace information.
// FunctionRegistry functions are allowed to throw MessageExceptions, though, because they will be caught and rewrapped by Reducer_Lambda code
module Message = {
@genType.opaque
type t =
@ -82,81 +84,79 @@ module Message = {
| _e => REOther("Unknown error")
}
let toException = (errorValue: t) => errorValue->MessageException->raise
}
module StackTrace = {
@genType.opaque
type rec t = {
location: location,
parent: option<t>,
}
let rec toString = ({location, parent}: t) => {
` Line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}, source ${location.source}\n` ++
switch parent {
| Some(parent) => toString(parent)
| None => ""
}
}
let rec toLocationList = (t: t): list<location> => {
switch t.parent {
| Some(parent) => Belt.List.add(toLocationList(parent), t.location)
| None => list{t.location}
}
}
@genType
let toLocationArray = (t: t): array<location> => t->toLocationList->Belt.List.toArray
let throw = (errorValue: t) => errorValue->MessageException->raise
}
@genType.opaque
type t = {
message: Message.t,
stackTrace: option<StackTrace.t>,
/*
Errors raised from internal functions can have empty location.
Also, location is not the same as the top of the stackTrace.
Consider this:
```
f() = {
x = 5
y = z // no such var
x + y
}
```
This code should report the location of assignment issue, but there's no function call there.
*/
location: option<location>,
stackTrace: Reducer_CallStack.t,
}
exception SqException(t)
@genType
let fromMessage = (errorMessage: Message.t): t => {
message: errorMessage,
stackTrace: None,
}
let fromMessageWithLocation = (errorMessage: Message.t, location: location): t => {
message: errorMessage,
stackTrace: Some({location: location, parent: None}),
}
let extend = ({message, stackTrace}: t, location: location) => {
// `context` should be specified for runtime errors, but can be left empty for errors from Reducer_Project and so on.
// `location` can be empty for errors raised from FunctionRegistry.
let fromMessage = (
message: Message.t,
context: option<Reducer_T.context>,
location: option<location>,
): t => {
message: message,
stackTrace: Some({location: location, parent: stackTrace}),
location: location,
stackTrace: switch context {
| Some(context) => context.callStack
| None => Reducer_CallStack.make()
},
}
@genType
let getLocation = (t: t): option<location> => t.stackTrace->E.O2.fmap(stack => stack.location)
let getTopFrame = (t: t): option<Reducer_CallStack.frame> =>
t.stackTrace->Reducer_CallStack.getTopFrame
@genType
let getStackTrace = (t: t): option<StackTrace.t> => t.stackTrace
let getStackTrace = (t: t): Reducer_CallStack.t => t.stackTrace
@genType
let toString = (t: t): string => t.message->Message.toString
@genType
let createOtherError = (v: string): t => Message.REOther(v)->fromMessage
let createOtherError = (v: string): t => Message.REOther(v)->fromMessage()
@genType
let getFrameArray = (t: t): array<Reducer_CallStack.frame> =>
t.stackTrace->Reducer_CallStack.toFrameArray
@genType
let toStringWithStackTrace = (t: t) =>
switch t.stackTrace {
| Some(stack) => "Traceback:\n" ++ stack->StackTrace.toString
| None => ""
if t.stackTrace->Reducer_CallStack.isEmpty {
"Traceback:\n" ++ t.stackTrace->Reducer_CallStack.toString
} else {
""
} ++
t->toString
let throw = (t: t) => t->SqException->raise
let throwMessage = (message: Message.t, context: Reducer_T.context, location: location) =>
fromMessage(message, context, location)->throw
// this shouldn't be used for most runtime errors - the resulting error would have an empty stacktrace
let fromException = exn =>
switch exn {
| SqException(e) => e
@ -164,3 +164,17 @@ let fromException = exn =>
| Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage
| _ => REOther("Unknown exception")->fromMessage
}
// converts raw exceptions into exceptions with stacktrace attached
// already converted exceptions won't be affected
let contextualizeAndRethrow = (fn: unit => 'a, context: Reducer_T.context) => {
try {
fn()
} catch {
| SqException(e) => e->throw
| Message.MessageException(e) => e->throwMessage(context)
| Js.Exn.Error(obj) =>
REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessage(context)
| _ => REOther("Unknown exception")->throwMessage(context)
}
}

View File

@ -1,5 +1,3 @@
let throwMessage = SqError.Message.toException
let stdLib: Reducer_T.namespace = {
// constants
let res =
@ -10,22 +8,23 @@ let stdLib: Reducer_T.namespace = {
// array and record lookups
let res = res->Reducer_Namespace.set(
"$_atIndex_$",
Reducer_Expression_Lambda.makeFFILambda((inputs, _, _) => {
Reducer_Lambda.makeFFILambda("$_atIndex_$", (inputs, _, _) => {
switch inputs {
| [IEvArray(aValueArray), IEvNumber(fIndex)] => {
let index = Belt.Int.fromFloat(fIndex) // TODO - fail on non-integer indices?
switch Belt.Array.get(aValueArray, index) {
| Some(value) => value
| None => REArrayIndexNotFound("Array index not found", index)->throwMessage
| None => REArrayIndexNotFound("Array index not found", index)->SqError.Message.throw
}
}
| [IEvRecord(dict), IEvString(sIndex)] =>
switch Belt.Map.String.get(dict, sIndex) {
| Some(value) => value
| None => RERecordPropertyNotFound("Record property not found", sIndex)->throwMessage
| None =>
RERecordPropertyNotFound("Record property not found", sIndex)->SqError.Message.throw
}
| _ => REOther("Trying to access key on wrong value")->throwMessage
| _ => REOther("Trying to access key on wrong value")->SqError.Message.throw
}
})->Reducer_T.IEvLambda,
)
@ -45,10 +44,10 @@ let stdLib: Reducer_T.namespace = {
->Belt.Array.reduce(res, (cur, name) => {
cur->Reducer_Namespace.set(
name,
Reducer_Expression_Lambda.makeFFILambda((arguments, environment, reducer) => {
switch FunctionRegistry_Library.call(name, arguments, environment, reducer) {
Reducer_Lambda.makeFFILambda(name, (arguments, context, reducer) => {
switch FunctionRegistry_Library.call(name, arguments, context, reducer) {
| Ok(value) => value
| Error(error) => error->SqError.Message.toException
| Error(error) => error->SqError.Message.throw
}
})->Reducer_T.IEvLambda,
)