From 184584c9f3032f3ba83ac84ba5c1803f33e7bd71 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Thu, 29 Sep 2022 19:48:31 +0400 Subject: [PATCH] CallStack, location -> frame, WIP --- packages/squiggle-lang/src/js/SqError.ts | 20 ++-- packages/squiggle-lang/src/js/index.ts | 2 +- .../src/rescript/FR/FR_Danger.res | 4 +- .../squiggle-lang/src/rescript/FR/FR_Dict.res | 2 +- .../squiggle-lang/src/rescript/FR/FR_Dist.res | 16 ++- .../src/rescript/FR/FR_GenericDist.res | 10 +- .../squiggle-lang/src/rescript/FR/FR_List.res | 20 ++-- .../src/rescript/FR/FR_Pointset.res | 14 +-- .../src/rescript/FR/FR_Sampleset.res | 31 +++-- .../src/rescript/FR/FR_Scoring.res | 16 +-- .../ForTS_SquiggleValue_Lambda.res | 6 +- .../FunctionRegistry_Core.res | 12 +- .../rescript/Reducer/Reducer_CallStack.res | 36 ++++++ .../src/rescript/Reducer/Reducer_Context.res | 3 +- .../Reducer_Expression/Reducer_Expression.res | 33 +++--- .../Reducer_Expression_Lambda.res | 58 ---------- .../src/rescript/Reducer/Reducer_Lambda.res | 88 ++++++++++++++ .../src/rescript/Reducer/Reducer_T.res | 15 ++- .../src/rescript/Reducer/Reducer_Value.res | 12 +- .../squiggle-lang/src/rescript/SqError.res | 108 ++++++++++-------- .../SquiggleLibrary_StdLib.res | 17 ++- 21 files changed, 310 insertions(+), 213 deletions(-) create mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res delete mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res create mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index 5e599e51..61d09096 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -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) {} +} diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index 82e12103..aac26bd5 100644 --- a/packages/squiggle-lang/src/js/index.ts +++ b/packages/squiggle-lang/src/js/index.ts @@ -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"; diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res index 0474c1f4..cd8c1c88 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res @@ -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, diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Dict.res b/packages/squiggle-lang/src/rescript/FR/FR_Dict.res index f844ceac..a858e1bb 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Dict.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Dict.res @@ -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 diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Dist.res b/packages/squiggle-lang/src/rescript/FR/FR_Dist.res index 270e2b4a..8f5ec0a1 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Dist.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Dist.res @@ -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), (), ) } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res b/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res index 72249d19..ad456650 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res @@ -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) => { 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 } }) diff --git a/packages/squiggle-lang/src/rescript/FR/FR_List.res b/packages/squiggle-lang/src/rescript/FR/FR_List.res index fb675417..fdcacd0d 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_List.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_List.res @@ -26,11 +26,11 @@ module Internals = { let map = ( array: array, 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 @@ -109,8 +109,8 @@ let library = [ ~inputs=[FRTypeNumber, FRTypeNumber], ~run=(inputs, _, _) => switch inputs { - | [IEvNumber(low), IEvNumber(high)] => Internals.upTo(low, high)->Ok - | _ => impossibleError->Error + | [IEvNumber(low), IEvNumber(high)] => Internals.upTo(low, high)->Ok + | _ => impossibleError->Error }, (), ), diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res b/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res index 28684f34..f7caaabb 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res @@ -16,16 +16,16 @@ let inputsToDist = (inputs: array, 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) diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res b/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res index 7f11ba7a..40ce8714 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res @@ -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, 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)) diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res b/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res index 60cb82e5..ffe10384 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res @@ -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) } }, diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res index a9793e87..58213c04 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res @@ -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 => { - v.parameters -} +let parameters = (v: squiggleValue_Lambda): array => Reducer_Lambda.parameters(v) diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res index f39eecc4..d2d0cfbe 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res @@ -30,7 +30,7 @@ type fnDefinition = { inputs: array, run: ( array, - Reducer_T.environment, + Reducer_T.context, Reducer_T.reducerFn, ) => result, } @@ -122,11 +122,11 @@ module FnDefinition = { let run = ( t: t, args: array, - 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, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ): result => { 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 } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res new file mode 100644 index 00000000..520ce6eb --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res @@ -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 + +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 => t->Belt.List.toArray + +@genType +let getTopFrame = (t: t): option => t->Belt.List.head + +let isEmpty = (t: t): bool => + switch t->Belt.List.head { + | Some(_) => true + | None => false + } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res index c45994bb..af596dc2 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res @@ -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) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res index 10e026a3..030ca71d 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res @@ -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 => 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 => { - let context = Reducer_Context.createDefaultContext() + let context = createDefaultContext() try { let (value, _) = expression->evaluate(context) value->Ok diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res deleted file mode 100644 index 36e43b25..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res +++ /dev/null @@ -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, - 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, - 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: ["..."], -} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res new file mode 100644 index 00000000..81e30cfb --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res @@ -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, + parameters: array, + bindings: Reducer_T.bindings, + body: Reducer_T.expression, + location: Reducer_Peggy_Parse.location, +): t => { + let lambda = ( + arguments: array, + 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 => { + 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) +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res index 2a704b23..cb0edfd2 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -15,12 +15,16 @@ type rec value = | IEvVoid @genType.opaque and arrayValue = array @genType.opaque and map = Belt.Map.String.t -and lambdaBody = (array, environment, reducerFn) => value +and lambdaBody = (array, context, reducerFn) => value @genType.opaque -and lambdaValue = { - parameters: array, - body: lambdaBody, -} +and lambdaValue = + | FnLambda({ + parameters: array, + body: lambdaBody, + location: Reducer_Peggy_Parse.location, + name: option, + }) + | FnBuiltin({body: lambdaBody, name: string}) @genType.opaque and lambdaDeclaration = Declaration.declaration and expressionContent = | EBlock(array) @@ -49,6 +53,7 @@ and bindings = { and context = { bindings: bindings, environment: environment, + callStack: Reducer_CallStack.t, } and reducerFn = (expression, context) => (value, context) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res index 7abecb92..01f97647 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res @@ -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): array => arr let resultToValue = (rExpression: result): 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 diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res index 2d04fcaf..3cf3dfe2 100644 --- a/packages/squiggle-lang/src/rescript/SqError.res +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -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, - } - - 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 => { - switch t.parent { - | Some(parent) => Belt.List.add(toLocationList(parent), t.location) - | None => list{t.location} - } - } - - @genType - let toLocationArray = (t: t): array => t->toLocationList->Belt.List.toArray + let throw = (errorValue: t) => errorValue->MessageException->raise } @genType.opaque type t = { message: Message.t, - stackTrace: option, + /* + 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, + 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, + location: option, +): 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 => t.stackTrace->E.O2.fmap(stack => stack.location) +let getTopFrame = (t: t): option => + t.stackTrace->Reducer_CallStack.getTopFrame @genType -let getStackTrace = (t: t): option => 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 => + 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) + } +} diff --git a/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res b/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res index 57311827..9b2fea1d 100644 --- a/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res +++ b/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res @@ -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, )