From 41574e08c97a3c6c2f7509127f13edc647ad1721 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Sun, 25 Sep 2022 03:16:14 +0400 Subject: [PATCH 01/18] error stacktraces and locations (initial take, WIP) --- .../src/components/SquiggleErrorAlert.tsx | 6 +- .../SquiggleViewer/ExpressionViewer.tsx | 4 +- .../Reducer_Peggy_TestHelpers.res | 5 +- .../ReducerProject_includes_test.res | 6 +- ...educerProject_tutorial_3_includes_test.res | 6 +- packages/squiggle-lang/scripts/run.mjs | 11 +- packages/squiggle-lang/src/js/SqError.ts | 6 +- packages/squiggle-lang/src/js/SqProject.ts | 11 +- .../src/rescript/FR/FR_Danger.res | 4 +- .../rescript/ForTS/ForTS_ReducerProject.res | 34 +--- .../ForTS/ForTS_Reducer_ErrorValue.res | 19 ++- .../ForTS_SquiggleValue.res | 4 +- .../src/rescript/ForTS/ForTS__Types.res | 2 +- .../rescript/Reducer/Reducer_ErrorValue.res | 68 +++++++- .../Reducer_Expression/Reducer_Expression.res | 49 ++++-- .../Reducer_Expression_ExpressionBuilder.res | 27 +-- .../Reducer_Expression_T.res | 6 +- .../Reducer_Peggy_GeneratedParser.peggy | 76 ++++----- .../Reducer_Peggy/Reducer_Peggy_Parse.res | 157 ++++++++++-------- .../Reducer_Peggy_ToExpression.res | 110 ++++++------ .../rescript/Reducer/Reducer_Peggy/helpers.ts | 145 ++++++++++------ .../src/rescript/Reducer/Reducer_T.res | 7 +- .../src/rescript/Reducer/Reducer_Value.res | 14 +- .../ReducerProject/ReducerProject.res | 4 +- .../ReducerProject_ProjectItem.res | 34 +++- .../ReducerProject_ProjectItem_T.res | 3 +- .../ReducerProject/ReducerProject_T.res | 2 +- 27 files changed, 480 insertions(+), 340 deletions(-) diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index 49179497..d03e237f 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -7,5 +7,9 @@ type Props = { }; export const SquiggleErrorAlert: React.FC = ({ error }) => { - return {error.toString()}; + return ( + +
{error.toString()}
+
+ ); }; diff --git a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx index 8bd32292..829f323f 100644 --- a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx +++ b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx @@ -298,7 +298,9 @@ export const ExpressionViewer: React.FC = ({ value, width }) => { {() => (
No display for type: {" "} - {value.tag} + + {(value as any).tag} +
)} diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res index 0dadae86..4b9073dd 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res @@ -9,12 +9,12 @@ open Jest open Expect let expectParseToBe = (expr, answer) => - Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer) + Parse.parse(expr, "test")->Parse.toStringResult->expect->toBe(answer) let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { - let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode) + let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode) let a1 = rExpr->ExpressionT.toStringResultOkless if v == "_" { @@ -22,6 +22,7 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { } else { let a2 = rExpr + ->E.R2.errMap(e => e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res index 07660044..a6b35ec4 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res @@ -25,7 +25,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common"] - | Error(error) => fail(error->Reducer_ErrorValue.errorToString) + | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) } }) test("past chain", () => { @@ -60,7 +60,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "myModule"] - | Error(error) => fail(error->Reducer_ErrorValue.errorToString) + | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) } }) @@ -99,7 +99,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "common2", "myModule"] - | Error(error) => fail(error->Reducer_ErrorValue.errorToString) + | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) } }) test("direct past chain", () => { diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res index 2c63dfff..995ce4d3 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res @@ -36,7 +36,7 @@ Here we will finally proceed to a real life scenario. */ /* Parse includes has set the includes */ switch project->Project.getIncludes("main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->Reducer_ErrorValue.errorToString->fail + | Error(err) => err->Reducer_ErrorValue.errorValueToString->fail } /* If the includes cannot be parsed then you get a syntax error. Otherwise you get the includes. @@ -85,7 +85,7 @@ Here we will finally proceed to a real life scenario. */ let rIncludes = project->Project.getIncludes(sourceName) switch rIncludes { /* Maybe there is an include syntax error */ - | Error(err) => err->Reducer_ErrorValue.errorToString->Js.Exn.raiseError + | Error(err) => err->Reducer_ErrorValue.errorValueToString->Js.Exn.raiseError | Ok(includes) => includes->Belt.Array.forEach(newIncludeName => { @@ -169,7 +169,7 @@ Here we will finally proceed to a real life scenario. */ test("getIncludes", () => { switch Project.getIncludes(project, "main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->Reducer_ErrorValue.errorToString->fail + | Error(err) => err->Reducer_ErrorValue.errorValueToString->fail } }) }) diff --git a/packages/squiggle-lang/scripts/run.mjs b/packages/squiggle-lang/scripts/run.mjs index 6dde265f..91058f33 100755 --- a/packages/squiggle-lang/scripts/run.mjs +++ b/packages/squiggle-lang/scripts/run.mjs @@ -1,11 +1,18 @@ #!/usr/bin/env node import { run } from "./lib.mjs"; -const src = process.argv[2]; +import { Command } from "commander"; + +const program = new Command(); + +program.arguments(""); + +const options = program.parse(process.argv); + +const src = program.args[0]; if (!src) { throw new Error("Expected src"); } -console.log(`Running ${src}`); const sampleCount = process.env.SAMPLE_COUNT; diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index 317fde4f..c0c509b4 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -1,16 +1,12 @@ import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; export class SqError { - constructor(private _value: RSErrorValue.reducerErrorValue) {} + constructor(private _value: RSErrorValue.reducerError) {} toString() { return RSErrorValue.toString(this._value); } - static createTodoError(v: string) { - return new SqError(RSErrorValue.createTodoError(v)); - } - static createOtherError(v: string) { return new SqError(RSErrorValue.createOtherError(v)); } diff --git a/packages/squiggle-lang/src/js/SqProject.ts b/packages/squiggle-lang/src/js/SqProject.ts index 9e79b564..22f2f78a 100644 --- a/packages/squiggle-lang/src/js/SqProject.ts +++ b/packages/squiggle-lang/src/js/SqProject.ts @@ -1,5 +1,9 @@ import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; -import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; +import { + reducerError, + reducerErrorValue, + attachEmptyStackTraceToErrorValue, +} from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen"; import { SqError } from "./SqError"; import { SqRecord } from "./SqRecord"; @@ -50,7 +54,8 @@ export class SqProject { return resultMap2( RSProject.getIncludes(this._value, sourceId), (a) => a, - (v: reducerErrorValue) => new SqError(v) + (v: reducerErrorValue) => + new SqError(attachEmptyStackTraceToErrorValue(v)) ); } @@ -104,7 +109,7 @@ export class SqProject { items: [], }) ), - (v: reducerErrorValue) => new SqError(v) + (v: reducerError) => new SqError(v) ); } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res index 2d60f556..7767f8c1 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res @@ -143,7 +143,7 @@ module Integration = { | Error(b) => ("Integration error 2 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead." ++ "Original error: " ++ - b->Reducer_ErrorValue.errorToString) + b->Reducer_ErrorValue.errorValueToString) ->Reducer_ErrorValue.REOther ->Error } @@ -225,7 +225,7 @@ module Integration = { reducer, )->E.R2.errMap(b => ("Integration error 7 in Danger.integrate. Something went wrong along the way: " ++ - b->Reducer_ErrorValue.errorToString)->Reducer_ErrorValue.REOther + b->Reducer_ErrorValue.errorValueToString)->Reducer_ErrorValue.REOther ) | _ => "Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))" diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res index ac9c167c..857044d7 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res @@ -1,5 +1,6 @@ @genType type reducerProject = ReducerProject_T.project //re-export +type reducerError = ForTS_Reducer_ErrorValue.reducerError //use type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use @@ -191,10 +192,8 @@ let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Rec Get the result after running this source file or the project */ @genType -let getResult = (project: reducerProject, sourceId: string): result< - squiggleValue, - reducerErrorValue, -> => project->Private.getResult(sourceId) +let getResult = (project: reducerProject, sourceId: string): result => + project->Private.getResult(sourceId) /* This is a convenience function to get the result of a single source without creating a project. @@ -202,10 +201,8 @@ However, without a project, you cannot handle include directives. The source has to be include free */ @genType -let evaluate = (sourceCode: string): ( - result, - squiggleValue_Record, -) => Private.evaluate(sourceCode) +let evaluate = (sourceCode: string): (result, squiggleValue_Record) => + Private.evaluate(sourceCode) @genType let setEnvironment = (project: reducerProject, environment: environment): unit => @@ -213,24 +210,3 @@ let setEnvironment = (project: reducerProject, environment: environment): unit = @genType let getEnvironment = (project: reducerProject): environment => project->Private.getEnvironment - -/* -Foreign function interface is intentionally demolished. -There is another way to do that: Umur. -Also there is no more conversion from javascript to squiggle values currently. -If the conversion to the new project is too difficult, I can add it later. -*/ - -// let foreignFunctionInterface = ( -// lambdaValue: squiggleValue_Lambda, -// argArray: array, -// environment: environment, -// ): result => { -// let accessors = ReducerProject_ProjectAccessors_T.identityAccessorsWithEnvironment(environment) -// Reducer_Expression_Lambda.foreignFunctionInterface( -// lambdaValue, -// argArray, -// accessors, -// Reducer_Expression.reduceExpressionInProject, -// ) -// } diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res index d4a8059b..4edc4ed3 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res @@ -1,18 +1,21 @@ +@genType type reducerError = Reducer_ErrorValue.error //alias @genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias -@genType type syntaxErrorLocation = Reducer_ErrorValue.syntaxErrorLocation //alias +@genType type location = Reducer_ErrorValue.location //alias @genType -let toString = (e: reducerErrorValue): string => Reducer_ErrorValue.errorToString(e) +let toString = (e: reducerError): string => Reducer_ErrorValue.errorToString(e) @genType -let getLocation = (e: reducerErrorValue): option => - switch e { - | RESyntaxError(_, optionalLocation) => optionalLocation - | _ => None +let getLocation = (e: reducerError): option => + switch e.stackTrace { + | Some(stack) => Some(stack.location) + | None => None } @genType -let createTodoError = (v: string) => Reducer_ErrorValue.RETodo(v) +let createOtherError = (v: string): reducerError => + Reducer_ErrorValue.REOther(v)->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue @genType -let createOtherError = (v: string) => Reducer_ErrorValue.REOther(v) +let attachEmptyStackTraceToErrorValue = (v: reducerErrorValue): reducerError => + Reducer_ErrorValue.attachEmptyStackTraceToErrorValue(v) diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res index 8331e776..ff029c95 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res @@ -1,5 +1,5 @@ @genType type squiggleValue = Reducer_T.value //re-export -type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use +type reducerError = ForTS_Reducer_ErrorValue.reducerError //use @genType type squiggleValue_Array = Reducer_T.arrayValue //re-export recursive type @genType type squiggleValue_Record = Reducer_T.map //re-export recursive type @@ -69,7 +69,7 @@ let toString = (variant: squiggleValue) => Reducer_Value.toString(variant) // This is a useful method for unit tests. // Convert the result along with the error message to a string. @genType -let toStringResult = (variantResult: result) => +let toStringResult = (variantResult: result) => Reducer_Value.toStringResult(variantResult) @genType diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res index 1cd74827..949801c7 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res @@ -1,5 +1,5 @@ @genType type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //re-export -@genType type syntaxErrorLocation = ForTS_Reducer_ErrorValue.syntaxErrorLocation //re-export +@genType type location = ForTS_Reducer_ErrorValue.location //re-export @genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index b6baeaf5..4a3d863f 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -1,6 +1,16 @@ -//TODO: Do not export here but in ForTS__Types -@gentype.import("peggy") @genType.as("LocationRange") -type syntaxErrorLocation +// Do not gentype this, use LocationRange from peggy types instead +// TODO - rename locationPoint -> location, location -> locationRange to match peggy +@genType +type locationPoint = { + line: int, + column: int, +} +@genType +type location = { + source: string, + start: locationPoint, + end: locationPoint, +} @genType.opaque type errorValue = @@ -18,7 +28,7 @@ type errorValue = | REOperationError(Operation.operationError) | RERecordPropertyNotFound(string, string) | RESymbolNotFound(string) - | RESyntaxError(string, option) + | RESyntaxError(string, option) | RETodo(string) // To do | REUnitNotFound(string) | RENeedToRun @@ -28,7 +38,20 @@ type t = errorValue exception ErrorException(errorValue) -let errorToString = err => +type rec stackTrace = { + location: location, + parent: option, +} + +@genType.opaque +type error = { + error: t, + stackTrace: option, +} + +exception ExceptionWithStackTrace(error) + +let errorValueToString = (err: errorValue) => switch err { | REArityError(_oFnName, arity, usedArity) => `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( @@ -80,4 +103,37 @@ let fromException = exn => | _e => REOther("Unknown error") } -let toException = (errorValue: t) => raise(ErrorException(errorValue)) +let rec stackTraceToString = ({location, parent}: stackTrace) => { + ` Line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}, source ${location.source}\n` ++ + switch parent { + | Some(parent) => stackTraceToString(parent) + | None => "" + } +} + +let errorToString = (err: error) => + switch err.stackTrace { + | Some(stack) => "Traceback:\n" ++ stack->stackTraceToString + | None => "" + } ++ + err.error->errorValueToString + +let toException = (errorValue: t) => errorValue->ErrorException->raise + +let attachEmptyStackTraceToErrorValue = (errorValue: t) => { + error: errorValue, + stackTrace: None, +} + +let attachLocationToErrorValue = (errorValue: t, location: location): error => { + error: errorValue, + stackTrace: Some({location: location, parent: None}), +} + +let raiseNewExceptionWithStackTrace = (errorValue: t, location: location) => + errorValue->attachLocationToErrorValue(location)->ExceptionWithStackTrace->raise + +let raiseExtendedExceptionWithStackTrace = ({error, stackTrace}: error, location: location) => + {error: error, stackTrace: Some({location: location, parent: stackTrace})} + ->ExceptionWithStackTrace + ->raise 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 c28e121e..5ab213a0 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 @@ -5,12 +5,19 @@ module T = Reducer_T type errorValue = Reducer_ErrorValue.errorValue +let toLocation = (expression: T.expression): Reducer_ErrorValue.location => { + expression.ast.location +} + +let throwFrom = (error: errorValue, expression: T.expression) => + error->Reducer_ErrorValue.raiseNewExceptionWithStackTrace(expression->toLocation) + /* Recursively evaluate the expression */ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { // Js.log(`reduce: ${expression->Reducer_Expression_T.toString}`) - switch expression { + switch expression.content { | T.EBlock(statements) => { let innerContext = {...context, bindings: context.bindings->Bindings.extend} let (value, _) = @@ -49,7 +56,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")->Reducer_ErrorValue.ErrorException->raise + | _ => REOther("Record keys must be strings")->throwFrom(expression) } let (value, _) = eValue->evaluate(context) (keyString, value) @@ -73,7 +80,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 => Reducer_ErrorValue.RESymbolNotFound(name)->Reducer_ErrorValue.ErrorException->raise + | None => RESymbolNotFound(name)->throwFrom(expression) } | T.EValue(value) => (value, context) @@ -82,7 +89,7 @@ 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", "")->Reducer_ErrorValue.ErrorException->raise + | _ => REExpectedType("Boolean", "")->throwFrom(expression) } } @@ -98,12 +105,18 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { argValue }) switch lambda { - | T.IEvLambda(lambda) => ( - Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate), - context, - ) - | _ => - RENotAFunction(lambda->Reducer_Value.toString)->Reducer_ErrorValue.ErrorException->raise + | T.IEvLambda(lambda) => + try { + let result = Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate) + (result, context) + } catch { + | Reducer_ErrorValue.ErrorException(e) => e->throwFrom(expression) // function implementation returned an error without location + | Reducer_ErrorValue.ExceptionWithStackTrace(e) => + Reducer_ErrorValue.raiseExtendedExceptionWithStackTrace(e, expression->toLocation) // function implementation probably called a lambda that threw an exception + | Js.Exn.Error(obj) => + REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwFrom(expression) + } + | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression) } } } @@ -113,18 +126,24 @@ module BackCompatible = { // Those methods are used to support the existing tests // If they are used outside limited testing context, error location reporting will fail let parse = (peggyCode: string): result => - peggyCode->Reducer_Peggy_Parse.parse->Result.map(Reducer_Peggy_ToExpression.fromNode) + peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) - let evaluate = (expression: T.expression): result => { + let evaluate = (expression: T.expression): result => { let context = Reducer_Context.createDefaultContext() try { let (value, _) = expression->evaluate(context) value->Ok } catch { - | exn => Reducer_ErrorValue.fromException(exn)->Error + | exn => + exn + ->Reducer_ErrorValue.fromException + ->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue + ->Error // TODO - this could be done better (see ReducerProject) } } - let evaluateString = (peggyCode: string): result => - parse(peggyCode)->Result.flatMap(evaluate) + let evaluateString = (peggyCode: string): result => + parse(peggyCode) + ->E.R2.errMap(e => e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + ->Result.flatMap(evaluate) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res index 9f1ed5fb..bd0a044a 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res @@ -3,14 +3,18 @@ module T = Reducer_T type errorValue = BErrorValue.errorValue type expression = Reducer_T.expression +type expressionContent = Reducer_T.expressionContent -let eArray = (anArray: array) => anArray->T.EArray +let eArray = (anArray: array): expressionContent => anArray->T.EArray let eBool = aBool => aBool->T.IEvBool->T.EValue -let eCall = (fn: expression, args: array): expression => T.ECall(fn, args) +let eCall = (fn: expression, args: array): expressionContent => T.ECall(fn, args) -let eLambda = (parameters: array, expr: expression) => T.ELambda(parameters, expr) +let eLambda = (parameters: array, expr: expression): expressionContent => T.ELambda( + parameters, + expr, +) let eNumber = aNumber => aNumber->T.IEvNumber->T.EValue @@ -18,13 +22,13 @@ let eRecord = (aMap: array<(T.expression, T.expression)>) => aMap->T.ERecord let eString = aString => aString->T.IEvString->T.EValue -let eSymbol = (name: string): expression => T.ESymbol(name) +let eSymbol = (name: string): expressionContent => T.ESymbol(name) -let eBlock = (exprs: array): expression => T.EBlock(exprs) +let eBlock = (exprs: array): expressionContent => T.EBlock(exprs) -let eProgram = (exprs: array): expression => T.EProgram(exprs) +let eProgram = (exprs: array): expressionContent => T.EProgram(exprs) -let eLetStatement = (symbol: string, valueExpression: expression): expression => T.EAssign( +let eLetStatement = (symbol: string, valueExpression: expression): expressionContent => T.EAssign( symbol, valueExpression, ) @@ -33,11 +37,8 @@ let eTernary = ( predicate: expression, trueCase: expression, falseCase: expression, -): expression => T.ETernary(predicate, trueCase, falseCase) +): expressionContent => T.ETernary(predicate, trueCase, falseCase) -let eIdentifier = (name: string): expression => name->T.ESymbol +let eIdentifier = (name: string): expressionContent => name->T.ESymbol -// let eTypeIdentifier = (name: string): expression => -// name->T.IEvTypeIdentifier->T.EValue - -let eVoid: expression = T.IEvVoid->T.EValue +let eVoid: expressionContent = T.IEvVoid->T.EValue diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index 71995c0a..2aca6cdb 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -12,7 +12,7 @@ let semicolonJoin = values => Converts the expression to String */ let rec toString = (expression: t) => - switch expression { + switch expression.content { | EBlock(statements) => `{${Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin}}` | EProgram(statements) => Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin @@ -31,13 +31,13 @@ let rec toString = (expression: t) => let toStringResult = codeResult => switch codeResult { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + | Error(m) => `Error(${Reducer_ErrorValue.errorValueToString(m)})` } let toStringResultOkless = codeResult => switch codeResult { | Ok(a) => toString(a) - | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + | Error(m) => `Error(${Reducer_ErrorValue.errorValueToString(m)})` } let inspect = (expr: t): t => { diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy index 4d8cc135..0cd28a86 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy @@ -12,21 +12,21 @@ zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda outerBlock = statements:array_statements finalExpression: (statementSeparator @expression)? { if (finalExpression) statements.push(finalExpression) - return h.nodeProgram(statements) } + return h.nodeProgram(statements, location()) } / finalExpression: expression - { return h.nodeProgram([finalExpression]) } + { return h.nodeProgram([finalExpression], location()) } innerBlockOrExpression = quotedInnerBlock / finalExpression: expression - { return h.nodeBlock([finalExpression])} + { return h.nodeBlock([finalExpression], location())} quotedInnerBlock = '{' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' { if (finalExpression) statements.push(finalExpression) - return h.nodeBlock(statements) } + return h.nodeBlock(statements, location()) } / '{' _nl finalExpression: expression _nl '}' - { return h.nodeBlock([finalExpression]) } + { return h.nodeBlock([finalExpression], location()) } array_statements = head:statement tail:(statementSeparator @array_statements ) @@ -42,16 +42,16 @@ statement voidStatement = "call" _nl value:zeroOMoreArgumentsBlockOrExpression { var variable = h.nodeIdentifier("_", location()); - return h.nodeLetStatement(variable, value); } + return h.nodeLetStatement(variable, value, location()); } letStatement = variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression - { return h.nodeLetStatement(variable, value) } + { return h.nodeLetStatement(variable, value, location()) } defunStatement = variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression - { var value = h.nodeLambda(args, body) - return h.nodeLetStatement(variable, value) } + { var value = h.nodeLambda(args, body, location()) + return h.nodeLetStatement(variable, value, location()) } assignmentOp "assignment" = '=' @@ -67,16 +67,16 @@ ifthenelse = 'if' __nl condition:logicalAdditive __nl 'then' __nl trueExpression:innerBlockOrExpression __nl 'else' __nl falseExpression:(ifthenelse/innerBlockOrExpression) - { return h.nodeTernary(condition, trueExpression, falseExpression) } + { return h.nodeTernary(condition, trueExpression, falseExpression, location()) } ternary = condition:logicalAdditive _ '?' _nl trueExpression:logicalAdditive _ ':' _nl falseExpression:(ternary/logicalAdditive) - { return h.nodeTernary(condition, trueExpression, falseExpression) } + { return h.nodeTernary(condition, trueExpression, falseExpression, location()) } logicalAdditive = head:logicalMultiplicative tail:(_ operator:logicalAdditiveOp _nl arg:logicalMultiplicative {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} logicalAdditiveOp "operator" = '||' @@ -85,21 +85,21 @@ logicalAdditive logicalMultiplicative = head:equality tail:(_ operator:logicalMultiplicativeOp _nl arg:equality {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} logicalMultiplicativeOp "operator" = '&&' equality = left:relational _ operator:equalityOp _nl right:relational - { return h.makeFunctionCall(h.toFunction[operator], [left, right])} + { return h.makeFunctionCall(h.toFunction[operator], [left, right], location())} / relational equalityOp "operator" = '=='/'!=' relational = left:additive _ operator:relationalOp _nl right:additive - { return h.makeFunctionCall(h.toFunction[operator], [left, right])} + { return h.makeFunctionCall(h.toFunction[operator], [left, right], location())} / additive relationalOp "operator" = '<='/'<'/'>='/'>' @@ -107,7 +107,7 @@ relational additive = head:multiplicative tail:(_ operator:additiveOp _nl arg:multiplicative {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} additiveOp "operator" = '+' / '-' / '.+' / '.-' @@ -115,7 +115,7 @@ additive multiplicative = head:power tail:(_ operator:multiplicativeOp _nl arg:power {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} multiplicativeOp "operator" = '*' / '/' / '.*' / './' @@ -123,7 +123,7 @@ multiplicative power = head:credibleInterval tail:(_ operator:powerOp _nl arg:credibleInterval {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} powerOp "operator" = '^' / '.^' @@ -131,7 +131,7 @@ power credibleInterval = head:chainFunctionCall tail:(__ operator:credibleIntervalOp __nl arg:chainFunctionCall {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} credibleIntervalOp "operator" = 'to' @@ -139,7 +139,7 @@ credibleInterval chainFunctionCall = head:unary tail:(_ ('->'/'|>') _nl chained:chainedFunction {return chained})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(element.fnName, [result, ...element.args]) + return h.makeFunctionCall(element.fnName, [result, ...element.args], location()) }, head)} chainedFunction @@ -154,7 +154,7 @@ chainFunctionCall unary = unaryOperator:unaryOperator _nl right:(unary/postOperator) - { return h.makeFunctionCall(h.unaryToFunction[unaryOperator], [right])} + { return h.makeFunctionCall(h.unaryToFunction[unaryOperator], [right], location())} / postOperator unaryOperator "unary operator" @@ -169,17 +169,17 @@ collectionElement tail:( _ '[' _nl arg:expression _nl ']' {return {fn: h.postOperatorToFunction['[]'], args: [arg]}} / _ '(' _nl args:array_functionArguments _nl ')' {return {fn: h.postOperatorToFunction['()'], args: args}} - / '.' arg:$dollarIdentifier {return {fn: h.postOperatorToFunction['[]'], args: [h.nodeString(arg)]}} + / '.' arg:$dollarIdentifier {return {fn: h.postOperatorToFunction['[]'], args: [h.nodeString(arg, location())]}} )* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(element.fn, [result, ...element.args]) + return h.makeFunctionCall(element.fn, [result, ...element.args], location()) }, head)} array_functionArguments = head:expression tail:(_ ',' _nl @expression)* { return [head, ...tail]; } / "" - {return [h.nodeVoid()];} + {return [h.nodeVoid(location())];} atom = '(' _nl expression:expression _nl ')' {return expression} @@ -195,7 +195,7 @@ basicLiteral / voidLiteral voidLiteral 'void' - = "()" {return h.nodeVoid();} + = "()" {return h.nodeVoid(location());} variable = dollarIdentifierWithModule / dollarIdentifier @@ -221,36 +221,36 @@ dollarIdentifier '$identifier' = ([\$_a-z]+[\$_a-z0-9]i*) {return h.nodeIdentifier(text(), location())} moduleIdentifier 'identifier' - = ([A-Z]+[_a-z0-9]i*) {return h.nodeModuleIdentifier(text())} + = ([A-Z]+[_a-z0-9]i*) {return h.nodeModuleIdentifier(text(), location())} string 'string' - = characters:("'" @([^'])* "'") {return h.nodeString(characters.join(''))} - / characters:('"' @([^"])* '"') {return h.nodeString(characters.join(''))} + = characters:("'" @([^'])* "'") {return h.nodeString(characters.join(''), location())} + / characters:('"' @([^"])* '"') {return h.nodeString(characters.join(''), location())} number = number:(float / integer) unit:unitIdentifier? { if (unit === null) { return number } else - { return h.makeFunctionCall('fromUnit_'+unit.value, [number]) + { return h.makeFunctionCall('fromUnit_'+unit.value, [number], location()) } } integer 'integer' = d+ !"\." ![e]i - { return h.nodeInteger(parseInt(text()))} + { return h.nodeInteger(parseInt(text()), location())} float 'float' = $(((d+ "\." d*) / ("\." d+)) floatExponent? / d+ floatExponent) - { return h.nodeFloat(parseFloat(text()))} + { return h.nodeFloat(parseFloat(text()), location())} floatExponent = [e]i '-'? d+ d = [0-9] boolean 'boolean' = ('true'/'false') ! [a-z]i ! [_$] - { return h.nodeBoolean(text() === 'true')} + { return h.nodeBoolean(text() === 'true', location())} valueConstructor = recordConstructor @@ -261,15 +261,15 @@ valueConstructor lambda = '{' _nl '|' _nl args:array_parameters _nl '|' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' { statements.push(finalExpression) - return h.nodeLambda(args, h.nodeBlock(statements)) } + return h.nodeLambda(args, h.nodeBlock(statements, location()), location()) } / '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}' - { return h.nodeLambda(args, finalExpression) } + { return h.nodeLambda(args, finalExpression, location()) } arrayConstructor 'array' = '[' _nl ']' - { return h.constructArray([]); } + { return h.constructArray([], location()); } / '[' _nl args:array_elements _nl ']' - { return h.constructArray(args); } + { return h.constructArray(args, location()); } array_elements = head:expression tail:(_ ',' _nl @expression)* @@ -277,7 +277,7 @@ arrayConstructor 'array' recordConstructor 'record' = '{' _nl args:array_recordArguments _nl end_of_record - { return h.constructRecord(args); } + { return h.constructRecord(args, location()); } end_of_record = '}' @@ -289,7 +289,7 @@ recordConstructor 'record' keyValuePair = key:expression _ ':' _nl value:expression - { return h.nodeKeyValue(key, value)} + { return h.nodeKeyValue(key, value, location())} // Separators diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res index 9c59c504..c6e21c9e 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res @@ -1,19 +1,20 @@ module Extra = Reducer_Extra open Reducer_ErrorValue -type node = {"type": string} +type node = {"type": string, "location": Reducer_ErrorValue.location} -@module("./Reducer_Peggy_GeneratedParser.js") external parse__: string => node = "parse" +@module("./Reducer_Peggy_GeneratedParser.js") +external parse__: (string, {"grammarSource": string}) => node = "parse" -type withLocation = {"location": Reducer_ErrorValue.syntaxErrorLocation} +type withLocation = {"location": Reducer_ErrorValue.location} external castWithLocation: Js.Exn.t => withLocation = "%identity" -let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.syntaxErrorLocation => +let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.location => castWithLocation(error)["location"] -let parse = (expr: string): result => +let parse = (expr: string, source: string): result => try { - Ok(parse__(expr)) + Ok(parse__(expr, {"grammarSource": source})) } catch { | Js.Exn.Error(obj) => RESyntaxError(Belt.Option.getExn(Js.Exn.message(obj)), syntaxErrorToLocation(obj)->Some)->Error @@ -34,27 +35,30 @@ type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node} type nodeModuleIdentifier = {...node, "value": string} type nodeString = {...node, "value": string} type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node} -// type nodeTypeIdentifier = {...node, "value": string} type nodeVoid = node -type peggyNode = - | PgNodeBlock(nodeBlock) - | PgNodeProgram(nodeProgram) - | PgNodeArray(nodeArray) - | PgNodeRecord(nodeRecord) - | PgNodeBoolean(nodeBoolean) - | PgNodeFloat(nodeFloat) - | PgNodeCall(nodeCall) - | PgNodeIdentifier(nodeIdentifier) - | PgNodeInteger(nodeInteger) - | PgNodeKeyValue(nodeKeyValue) - | PgNodeLambda(nodeLambda) - | PgNodeLetStatement(nodeLetStatement) - | PgNodeModuleIdentifier(nodeModuleIdentifier) - | PgNodeString(nodeString) - | PgNodeTernary(nodeTernary) - // | PgNodeTypeIdentifier(nodeTypeIdentifier) - | PgNodeVoid(nodeVoid) +type astContent = + | ASTBlock(nodeBlock) + | ASTProgram(nodeProgram) + | ASTArray(nodeArray) + | ASTRecord(nodeRecord) + | ASTBoolean(nodeBoolean) + | ASTFloat(nodeFloat) + | ASTCall(nodeCall) + | ASTIdentifier(nodeIdentifier) + | ASTInteger(nodeInteger) + | ASTKeyValue(nodeKeyValue) + | ASTLambda(nodeLambda) + | ASTLetStatement(nodeLetStatement) + | ASTModuleIdentifier(nodeModuleIdentifier) + | ASTString(nodeString) + | ASTTernary(nodeTernary) + | ASTVoid(nodeVoid) + +type ast = { + location: location, + content: astContent, +} external castNodeBlock: node => nodeBlock = "%identity" external castNodeProgram: node => nodeProgram = "%identity" @@ -71,80 +75,87 @@ external castNodeLetStatement: node => nodeLetStatement = "%identity" external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity" external castNodeString: node => nodeString = "%identity" external castNodeTernary: node => nodeTernary = "%identity" -// external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity" external castNodeVoid: node => nodeVoid = "%identity" exception UnsupportedPeggyNodeType(string) // This should never happen; programming error -let castNodeType = (node: node) => - switch node["type"] { - | "Block" => node->castNodeBlock->PgNodeBlock - | "Program" => node->castNodeBlock->PgNodeProgram - | "Array" => node->castNodeArray->PgNodeArray - | "Record" => node->castNodeRecord->PgNodeRecord - | "Boolean" => node->castNodeBoolean->PgNodeBoolean - | "Call" => node->castNodeCall->PgNodeCall - | "Float" => node->castNodeFloat->PgNodeFloat - | "Identifier" => node->castNodeIdentifier->PgNodeIdentifier - | "Integer" => node->castNodeInteger->PgNodeInteger - | "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue - | "Lambda" => node->castNodeLambda->PgNodeLambda - | "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement - | "ModuleIdentifier" => node->castNodeModuleIdentifier->PgNodeModuleIdentifier - | "String" => node->castNodeString->PgNodeString - | "Ternary" => node->castNodeTernary->PgNodeTernary - // | "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier - | "Void" => node->castNodeVoid->PgNodeVoid +let nodeToAST = (node: node) => { + let content = switch node["type"] { + | "Block" => node->castNodeBlock->ASTBlock + | "Program" => node->castNodeBlock->ASTProgram + | "Array" => node->castNodeArray->ASTArray + | "Record" => node->castNodeRecord->ASTRecord + | "Boolean" => node->castNodeBoolean->ASTBoolean + | "Call" => node->castNodeCall->ASTCall + | "Float" => node->castNodeFloat->ASTFloat + | "Identifier" => node->castNodeIdentifier->ASTIdentifier + | "Integer" => node->castNodeInteger->ASTInteger + | "KeyValue" => node->castNodeKeyValue->ASTKeyValue + | "Lambda" => node->castNodeLambda->ASTLambda + | "LetStatement" => node->castNodeLetStatement->ASTLetStatement + | "ModuleIdentifier" => node->castNodeModuleIdentifier->ASTModuleIdentifier + | "String" => node->castNodeString->ASTString + | "Ternary" => node->castNodeTernary->ASTTernary + | "Void" => node->castNodeVoid->ASTVoid | _ => raise(UnsupportedPeggyNodeType(node["type"])) } -let rec pgToString = (peggyNode: peggyNode): string => { + {location: node["location"], content: content} +} + +let nodeIdentifierToAST = (node: nodeIdentifier) => { + {location: node["location"], content: node->ASTIdentifier} +} + +let nodeKeyValueToAST = (node: nodeKeyValue) => { + {location: node["location"], content: node->ASTKeyValue} +} + +let rec pgToString = (ast: ast): string => { let argsToString = (args: array): string => - args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString + args->Belt.Array.map(arg => arg->nodeIdentifierToAST->pgToString)->Js.Array2.toString let nodesToStringUsingSeparator = (nodes: array, separator: string): string => - nodes->Js.Array2.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") + nodes->Belt.Array.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") - let pgNodesToStringUsingSeparator = (nodes: array, separator: string): string => - nodes->Js.Array2.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") + let pgNodesToStringUsingSeparator = (nodes: array, separator: string): string => + nodes->Belt.Array.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") - switch peggyNode { - | PgNodeBlock(node) - | PgNodeProgram(node) => + switch ast.content { + | ASTBlock(node) + | ASTProgram(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}" - | PgNodeArray(node) => "[" ++ node["elements"]->nodesToStringUsingSeparator("; ") ++ "]" - | PgNodeRecord(node) => + | ASTArray(node) => "[" ++ node["elements"]->nodesToStringUsingSeparator("; ") ++ "]" + | ASTRecord(node) => "{" ++ node["elements"] - ->Js.Array2.map(element => PgNodeKeyValue(element)) + ->Belt.Array.map(element => element->nodeKeyValueToAST) ->pgNodesToStringUsingSeparator(", ") ++ "}" - | PgNodeBoolean(node) => node["value"]->Js.String.make - | PgNodeCall(node) => + | ASTBoolean(node) => node["value"]->Js.String.make + | ASTCall(node) => "(" ++ node["fn"]->toString ++ " " ++ node["args"]->nodesToStringUsingSeparator(" ") ++ ")" - | PgNodeFloat(node) => node["value"]->Js.String.make - | PgNodeIdentifier(node) => `:${node["value"]}` - | PgNodeInteger(node) => node["value"]->Js.String.make - | PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"]) - | PgNodeLambda(node) => - "{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}" - | PgNodeLetStatement(node) => - pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"]) - | PgNodeModuleIdentifier(node) => `@${node["value"]}` - | PgNodeString(node) => `'${node["value"]->Js.String.make}'` - | PgNodeTernary(node) => + | ASTFloat(node) => node["value"]->Js.String.make + | ASTIdentifier(node) => `:${node["value"]}` + | ASTInteger(node) => node["value"]->Js.String.make + | ASTKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"]) + | ASTLambda(node) => "{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}" + | ASTLetStatement(node) => + pgToString(node["variable"]->nodeIdentifierToAST) ++ " = " ++ toString(node["value"]) + | ASTModuleIdentifier(node) => `@${node["value"]}` + | ASTString(node) => `'${node["value"]->Js.String.make}'` + | ASTTernary(node) => "(::$$_ternary_$$ " ++ toString(node["condition"]) ++ " " ++ toString(node["trueExpression"]) ++ " " ++ toString(node["falseExpression"]) ++ ")" - // | PgNodeTypeIdentifier(node) => `#${node["value"]}` - | PgNodeVoid(_node) => "()" + | ASTVoid(_node) => "()" } } -and toString = (node: node): string => node->castNodeType->pgToString +and toString = (node: node): string => node->nodeToAST->pgToString let toStringResult = (rNode: result): string => switch rNode { | Ok(node) => toString(node) - | Error(error) => `Error(${errorToString(error)})` + | Error(error) => `Error(${errorValueToString(error)})` } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res index ef8ed75b..6c187476 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res @@ -3,62 +3,72 @@ module ExpressionT = Reducer_Expression_T module Parse = Reducer_Peggy_Parse type expression = Reducer_T.expression +type expressionContent = Reducer_T.expressionContent let rec fromNode = (node: Parse.node): expression => { - let caseBlock = nodeBlock => - ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)) + let ast = Parse.nodeToAST(node) - let caseProgram = nodeProgram => - ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode)) + let content: expressionContent = { + let caseBlock = nodeBlock => + ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)) - let caseLambda = (nodeLambda: Parse.nodeLambda): expression => { - let args = - nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) - let body = nodeLambda["body"]->fromNode + let caseProgram = nodeProgram => + ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode)) - ExpressionBuilder.eLambda(args, body) + let caseLambda = (nodeLambda: Parse.nodeLambda): expressionContent => { + let args = + nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) + let body = nodeLambda["body"]->fromNode + + ExpressionBuilder.eLambda(args, body) + } + + let caseRecord = (nodeRecord): expressionContent => { + nodeRecord["elements"] + ->Js.Array2.map(keyValueNode => ( + keyValueNode["key"]->fromNode, + keyValueNode["value"]->fromNode, + )) + ->ExpressionBuilder.eRecord + } + + switch ast.content { + | ASTBlock(nodeBlock) => caseBlock(nodeBlock) + | ASTProgram(nodeProgram) => caseProgram(nodeProgram) + | ASTArray(nodeArray) => + ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode)) + | ASTRecord(nodeRecord) => caseRecord(nodeRecord) + | ASTBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"]) + | ASTCall(nodeCall) => + ExpressionBuilder.eCall(fromNode(nodeCall["fn"]), nodeCall["args"]->Js.Array2.map(fromNode)) + | ASTFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"]) + | ASTIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"]) + | ASTInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"])) + | ASTKeyValue(nodeKeyValue) => + ExpressionBuilder.eArray([fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])]) + | ASTLambda(nodeLambda) => caseLambda(nodeLambda) + | ASTLetStatement(nodeLetStatement) => + ExpressionBuilder.eLetStatement( + nodeLetStatement["variable"]["value"], + fromNode(nodeLetStatement["value"]), + ) + | ASTModuleIdentifier(nodeModuleIdentifier) => + ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"]) + | ASTString(nodeString) => ExpressionBuilder.eString(nodeString["value"]) + | ASTTernary(nodeTernary) => + ExpressionBuilder.eTernary( + fromNode(nodeTernary["condition"]), + fromNode(nodeTernary["trueExpression"]), + fromNode(nodeTernary["falseExpression"]), + ) + // | PgNodeTypeIdentifier(nodeTypeIdentifier) => + // ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"]) + | ASTVoid(_) => ExpressionBuilder.eVoid + } } - let caseRecord = (nodeRecord): expression => { - nodeRecord["elements"] - ->Js.Array2.map(keyValueNode => ( - keyValueNode["key"]->fromNode, - keyValueNode["value"]->fromNode, - )) - ->ExpressionBuilder.eRecord - } - - switch Parse.castNodeType(node) { - | PgNodeBlock(nodeBlock) => caseBlock(nodeBlock) - | PgNodeProgram(nodeProgram) => caseProgram(nodeProgram) - | PgNodeArray(nodeArray) => - ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode)) - | PgNodeRecord(nodeRecord) => caseRecord(nodeRecord) - | PgNodeBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"]) - | PgNodeCall(nodeCall) => - ExpressionBuilder.eCall(fromNode(nodeCall["fn"]), nodeCall["args"]->Js.Array2.map(fromNode)) - | PgNodeFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"]) - | PgNodeIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"]) - | PgNodeInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"])) - | PgNodeKeyValue(nodeKeyValue) => - ExpressionBuilder.eArray([fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])]) - | PgNodeLambda(nodeLambda) => caseLambda(nodeLambda) - | PgNodeLetStatement(nodeLetStatement) => - ExpressionBuilder.eLetStatement( - nodeLetStatement["variable"]["value"], - fromNode(nodeLetStatement["value"]), - ) - | PgNodeModuleIdentifier(nodeModuleIdentifier) => - ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"]) - | PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"]) - | PgNodeTernary(nodeTernary) => - ExpressionBuilder.eTernary( - fromNode(nodeTernary["condition"]), - fromNode(nodeTernary["trueExpression"]), - fromNode(nodeTernary["falseExpression"]), - ) - // | PgNodeTypeIdentifier(nodeTypeIdentifier) => - // ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"]) - | PgNodeVoid(_) => ExpressionBuilder.eVoid + { + ast: ast, + content: content, } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts index f24fd819..6e9fe80c 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts @@ -34,83 +34,91 @@ export const postOperatorToFunction = { "[]": "$_atIndex_$", }; -type NodeBlock = { +type Node = { + location: LocationRange; +}; + +type NodeBlock = Node & { type: "Block"; statements: AnyPeggyNode[]; }; -type NodeProgram = { +type NodeProgram = Node & { type: "Program"; statements: AnyPeggyNode[]; }; -type NodeArray = { +type NodeArray = Node & { type: "Array"; elements: AnyPeggyNode[]; }; -type NodeRecord = { +type NodeRecord = Node & { type: "Record"; elements: NodeKeyValue[]; }; -type NodeCall = { +type NodeCall = Node & { type: "Call"; fn: AnyPeggyNode; args: AnyPeggyNode[]; }; -type NodeFloat = { +type NodeFloat = Node & { type: "Float"; value: number; }; -type NodeInteger = { +type NodeInteger = Node & { type: "Integer"; value: number; }; -type NodeIdentifier = { +type NodeIdentifier = Node & { type: "Identifier"; value: string; }; -type NodeLetStatement = { +type NodeLetStatement = Node & { type: "LetStatement"; variable: NodeIdentifier; value: AnyPeggyNode; }; -type NodeLambda = { +type NodeLambda = Node & { type: "Lambda"; args: AnyPeggyNode[]; body: AnyPeggyNode; }; -type NodeTernary = { +type NodeTernary = Node & { type: "Ternary"; condition: AnyPeggyNode; trueExpression: AnyPeggyNode; falseExpression: AnyPeggyNode; }; -type NodeKeyValue = { +type NodeKeyValue = Node & { type: "KeyValue"; key: AnyPeggyNode; value: AnyPeggyNode; }; -type NodeString = { +type NodeString = Node & { type: "String"; value: string; location?: LocationRange; }; -type NodeBoolean = { +type NodeBoolean = Node & { type: "Boolean"; value: boolean; }; +type NodeVoid = Node & { + type: "Void"; +}; + export type AnyPeggyNode = | NodeArray | NodeRecord @@ -125,47 +133,78 @@ export type AnyPeggyNode = | NodeTernary | NodeKeyValue | NodeString - | NodeBoolean; + | NodeBoolean + | NodeVoid; -export function makeFunctionCall(fn: string, args: AnyPeggyNode[]) { +export function makeFunctionCall( + fn: string, + args: AnyPeggyNode[], + location: LocationRange +) { if (fn === "$$_applyAll_$$") { - return nodeCall(args[0], args.splice(1)); + return nodeCall(args[0], args.splice(1), location); } else { - return nodeCall(nodeIdentifier(fn), args); + return nodeCall(nodeIdentifier(fn, location), args, location); } } -export function constructArray(elements: AnyPeggyNode[]) { - return { type: "Array", elements }; +export function constructArray( + elements: AnyPeggyNode[], + location: LocationRange +): NodeArray { + return { type: "Array", elements, location }; } -export function constructRecord(elements: AnyPeggyNode[]) { - return { type: "Record", elements }; +export function constructRecord( + elements: NodeKeyValue[], + location: LocationRange +): NodeRecord { + return { type: "Record", elements, location }; } -export function nodeBlock(statements: AnyPeggyNode[]): NodeBlock { - return { type: "Block", statements }; +export function nodeBlock( + statements: AnyPeggyNode[], + location: LocationRange +): NodeBlock { + return { type: "Block", statements, location }; } -export function nodeProgram(statements: AnyPeggyNode[]): NodeProgram { - return { type: "Program", statements }; +export function nodeProgram( + statements: AnyPeggyNode[], + location: LocationRange +): NodeProgram { + return { type: "Program", statements, location }; } -export function nodeBoolean(value: boolean): NodeBoolean { - return { type: "Boolean", value }; +export function nodeBoolean( + value: boolean, + location: LocationRange +): NodeBoolean { + return { type: "Boolean", value, location }; } -export function nodeCall(fn: AnyPeggyNode, args: AnyPeggyNode[]): NodeCall { - return { type: "Call", fn, args }; +export function nodeCall( + fn: AnyPeggyNode, + args: AnyPeggyNode[], + location: LocationRange +): NodeCall { + return { type: "Call", fn, args, location }; } -export function nodeFloat(value: number): NodeFloat { - return { type: "Float", value }; +export function nodeFloat(value: number, location: LocationRange): NodeFloat { + return { type: "Float", value, location }; } -export function nodeIdentifier(value: string): NodeIdentifier { - return { type: "Identifier", value }; +export function nodeIdentifier( + value: string, + location: LocationRange +): NodeIdentifier { + return { type: "Identifier", value, location }; } -export function nodeInteger(value: number): NodeInteger { - return { type: "Integer", value }; +export function nodeInteger( + value: number, + location: LocationRange +): NodeInteger { + return { type: "Integer", value, location }; } export function nodeKeyValue( key: AnyPeggyNode, - value: AnyPeggyNode + value: AnyPeggyNode, + location: LocationRange ): NodeKeyValue { if (key.type === "Identifier") { key = { @@ -173,43 +212,43 @@ export function nodeKeyValue( type: "String", }; } - return { type: "KeyValue", key, value }; + return { type: "KeyValue", key, value, location }; } export function nodeLambda( args: AnyPeggyNode[], - body: AnyPeggyNode + body: AnyPeggyNode, + location: LocationRange ): NodeLambda { - return { type: "Lambda", args, body }; + return { type: "Lambda", args, body, location }; } export function nodeLetStatement( variable: NodeIdentifier, - value: AnyPeggyNode + value: AnyPeggyNode, + location: LocationRange ): NodeLetStatement { - return { type: "LetStatement", variable, value }; + return { type: "LetStatement", variable, value, location }; } -export function nodeModuleIdentifier(value: string) { - return { type: "ModuleIdentifier", value }; +export function nodeModuleIdentifier(value: string, location: LocationRange) { + return { type: "ModuleIdentifier", value, location }; } -export function nodeString(value: string): NodeString { - return { type: "String", value }; +export function nodeString(value: string, location: LocationRange): NodeString { + return { type: "String", value, location }; } export function nodeTernary( condition: AnyPeggyNode, trueExpression: AnyPeggyNode, - falseExpression: AnyPeggyNode + falseExpression: AnyPeggyNode, + location: LocationRange ): NodeTernary { return { type: "Ternary", condition, trueExpression, falseExpression, + location, }; } -export function nodeTypeIdentifier(typeValue: string) { - return { type: "TypeIdentifier", value: typeValue }; -} - -export function nodeVoid() { - return { type: "Void" }; +export function nodeVoid(location: LocationRange): NodeVoid { + return { type: "Void", location }; } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res index 85546905..2a704b23 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -22,7 +22,7 @@ and lambdaValue = { body: lambdaBody, } @genType.opaque and lambdaDeclaration = Declaration.declaration -and expression = +and expressionContent = | EBlock(array) // programs are similar to blocks, but don't create an inner scope. there can be only one program at the top level of the expression. | EProgram(array) @@ -35,6 +35,11 @@ and expression = | ELambda(array, expression) | EValue(value) +and expression = { + ast: Reducer_Peggy_Parse.ast, + content: expressionContent, +} + and namespace = Belt.Map.String.t and bindings = { namespace: namespace, diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res index cd1eefef..f7681ee7 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res @@ -79,24 +79,12 @@ let toStringResult = x => | Error(m) => `Error(${ErrorValue.errorToString(m)})` } -let toStringOptionResult = x => - switch x { - | Some(a) => toStringResult(a) - | None => "None" - } - -let toStringResultOkless = (codeResult: result): string => +let toStringResultOkless = (codeResult: result): string => switch codeResult { | Ok(a) => toString(a) | Error(m) => `Error(${ErrorValue.errorToString(m)})` } -let toStringResultRecord = x => - switch x { - | Ok(a) => `Ok(${toStringMap(a)})` - | Error(m) => `Error(${ErrorValue.errorToString(m)})` - } - type internalExpressionValueType = | EvtArray | EvtBool diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res index 1942a136..082b620c 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res @@ -117,7 +117,7 @@ let getResultOption = (project: t, sourceId: string): ProjectItem.T.resultType = let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType => switch getResultOption(project, sourceId) { - | None => RENeedToRun->Error + | None => RENeedToRun->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue->Error | Some(result) => result } @@ -171,7 +171,7 @@ let linkDependencies = (project: t, sourceId: string): Reducer_T.namespace => { "__result__", switch project->getResult(id) { | Ok(result) => result - | Error(error) => error->Reducer_ErrorValue.ErrorException->raise + | Error(error) => error->Reducer_ErrorValue.ExceptionWithStackTrace->raise }, ), ]) diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 93d434a3..4313f6e1 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res @@ -4,8 +4,9 @@ module T = ReducerProject_ProjectItem_T type projectItem = T.projectItem type t = T.t -let emptyItem: projectItem = { +let emptyItem = (sourceId: string): projectItem => { source: "", + sourceId: sourceId, rawParse: None, expression: None, continuation: Reducer_Namespace.make(), @@ -18,6 +19,7 @@ let emptyItem: projectItem = { // source -> rawParse -> includes -> expression -> continuation -> result let getSource = (r: t): T.sourceType => r.source +let getSourceId = (r: t): T.sourceType => r.sourceId let getRawParse = (r: t): T.rawParseType => r.rawParse let getExpression = (r: t): T.expressionType => r.expression let getContinuation = (r: t): T.continuationArgumentType => r.continuation @@ -29,7 +31,7 @@ let getDirectIncludes = (r: t): array => r.directIncludes let getIncludesAsVariables = (r: t): T.importAsVariablesType => r.includeAsVariables let touchSource = (this: t): t => { - let r = emptyItem + let r = emptyItem(this->getSourceId) { ...r, source: getSource(this), @@ -41,8 +43,9 @@ let touchSource = (this: t): t => { } let touchRawParse = (this: t): t => { + let r = emptyItem(this->getSourceId) { - ...emptyItem, + ...r, source: getSource(this), continues: getContinues(this), includes: getIncludes(this), @@ -148,7 +151,8 @@ let parseIncludes = (this: t): t => { } } } -let doRawParse = (this: t): T.rawParseArgumentType => this->getSource->Reducer_Peggy_Parse.parse +let doRawParse = (this: t): T.rawParseArgumentType => + this->getSource->Reducer_Peggy_Parse.parse(this.sourceId) let rawParse = (this: t): t => this->getRawParse->E.O2.defaultFn(() => doRawParse(this))->setRawParse(this, _) @@ -167,7 +171,7 @@ let buildExpression = (this: t): t => { } } -let failRun = (this: t, e: Reducer_ErrorValue.errorValue): t => +let failRun = (this: t, e: Reducer_ErrorValue.error): t => this->setResult(e->Error)->setContinuation(Reducer_Namespace.make()) let doRun = (this: t, context: Reducer_T.context): t => @@ -181,12 +185,24 @@ let doRun = (this: t, context: Reducer_T.context): t => ->setResult(result->Ok) ->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals) } catch { - | Reducer_ErrorValue.ErrorException(e) => this->failRun(e) - | _ => this->failRun(RETodo("unhandled rescript exception")) + | Reducer_ErrorValue.ErrorException(e) => + this->failRun(e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + | Reducer_ErrorValue.ExceptionWithStackTrace(e) => this->failRun(e) + | _ => + this->failRun( + RETodo( + "unhandled rescript exception", + )->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue, + ) } - | Error(e) => this->failRun(e) + | Error(e) => this->failRun(e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) } - | None => this->failRun(RETodo("attempt to run without expression")) + | None => + this->failRun( + RETodo( + "attempt to run without expression", + )->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue, + ) } let run = (this: t, context: Reducer_T.context): t => { diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res index bf1cbfcb..5d32a161 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -11,7 +11,7 @@ type expressionType = option type continuationArgumentType = Reducer_T.namespace type continuationType = option type continuationResultType = option> -type resultArgumentType = result +type resultArgumentType = result type resultType = option type continuesArgumentType = array type continuesType = array @@ -21,6 +21,7 @@ type importAsVariablesType = array<(string, string)> type projectItem = { source: sourceType, + sourceId: string, rawParse: rawParseType, expression: expressionType, continuation: continuationArgumentType, diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res index 0eac623a..bb0ae2ee 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res @@ -13,4 +13,4 @@ type t = project let getSourceIds = (project: t): array => Belt.MutableMap.String.keysToArray(project.items) let getItem = (project: t, sourceId: string) => - Belt.MutableMap.String.getWithDefault(project.items, sourceId, ProjectItem.emptyItem) + Belt.MutableMap.String.getWithDefault(project.items, sourceId, ProjectItem.emptyItem(sourceId)) From 111dd5535c1b8a07c6d802727ddeed4cda1d55dc Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Mon, 26 Sep 2022 03:42:34 +0400 Subject: [PATCH 02/18] move location to Reducer_Peggy_Parse, separate parse errors --- .../Reducer_Peggy_TestHelpers.res | 12 +++--- .../ForTS/ForTS_Reducer_ErrorValue.res | 2 +- .../rescript/Reducer/Reducer_ErrorValue.res | 18 +++----- .../Reducer_Expression/Reducer_Expression.res | 6 ++- .../Reducer_Expression_T.res | 18 +------- .../Reducer_Peggy/Reducer_Peggy_Parse.res | 41 ++++++++++++++----- .../ReducerProject_ProjectItem.res | 5 ++- 7 files changed, 54 insertions(+), 48 deletions(-) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res index 4b9073dd..65d87565 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res @@ -13,7 +13,7 @@ let expectParseToBe = (expr, answer) => let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) -let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { +let expectExpressionToBe = (expr, answer, ~v="_", ()) => { let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode) let a1 = rExpr->ExpressionT.toStringResultOkless @@ -22,7 +22,9 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { } else { let a2 = rExpr - ->E.R2.errMap(e => e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + ->E.R2.errMap(e => + e->Reducer_ErrorValue.fromParseError->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue + ) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) @@ -30,16 +32,16 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { } let testToExpression = (expr, answer, ~v="_", ()) => - test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) + test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) module MyOnly = { let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) let testToExpression = (expr, answer, ~v="_", ()) => - Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) + Only.test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) } module MySkip = { let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) let testToExpression = (expr, answer, ~v="_", ()) => - Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) + Skip.test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) } diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res index 4edc4ed3..1392639b 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res @@ -1,6 +1,6 @@ @genType type reducerError = Reducer_ErrorValue.error //alias @genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias -@genType type location = Reducer_ErrorValue.location //alias +@genType type location = Reducer_Peggy_Parse.location //alias @genType let toString = (e: reducerError): string => Reducer_ErrorValue.errorToString(e) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index 4a3d863f..e2c1d495 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -1,16 +1,4 @@ -// Do not gentype this, use LocationRange from peggy types instead -// TODO - rename locationPoint -> location, location -> locationRange to match peggy -@genType -type locationPoint = { - line: int, - column: int, -} -@genType -type location = { - source: string, - start: locationPoint, - end: locationPoint, -} +type location = Reducer_Peggy_Parse.location @genType.opaque type errorValue = @@ -51,6 +39,10 @@ type error = { exception ExceptionWithStackTrace(error) +let fromParseError = ( + SyntaxError(message, location): Reducer_Peggy_Parse.parseError, +) => RESyntaxError(message, location->Some) + let errorValueToString = (err: errorValue) => switch err { | REArityError(_oFnName, arity, usedArity) => 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 5ab213a0..714daffe 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 @@ -125,7 +125,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { module BackCompatible = { // Those methods are used to support the existing tests // If they are used outside limited testing context, error location reporting will fail - let parse = (peggyCode: string): result => + let parse = (peggyCode: string): result => peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) let evaluate = (expression: T.expression): result => { @@ -144,6 +144,8 @@ module BackCompatible = { let evaluateString = (peggyCode: string): result => parse(peggyCode) - ->E.R2.errMap(e => e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + ->E.R2.errMap(e => + e->Reducer_ErrorValue.fromParseError->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue + ) ->Result.flatMap(evaluate) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index 2aca6cdb..6b2982f6 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -31,30 +31,16 @@ let rec toString = (expression: t) => let toStringResult = codeResult => switch codeResult { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${Reducer_ErrorValue.errorValueToString(m)})` + | Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})` } let toStringResultOkless = codeResult => switch codeResult { | Ok(a) => toString(a) - | Error(m) => `Error(${Reducer_ErrorValue.errorValueToString(m)})` + | Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})` } let inspect = (expr: t): t => { Js.log(toString(expr)) expr } - -let inspectResult = (r: result): result< - t, - Reducer_ErrorValue.errorValue, -> => { - Js.log(toStringResult(r)) - r -} - -let resultToValue = (rExpression: result): t => - switch rExpression { - | Ok(expression) => expression - | Error(errorValue) => Reducer_ErrorValue.toException(errorValue) - } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res index c6e21c9e..4a0a77da 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res @@ -1,23 +1,39 @@ module Extra = Reducer_Extra -open Reducer_ErrorValue -type node = {"type": string, "location": Reducer_ErrorValue.location} +// Do not gentype this, use LocationRange from peggy types instead +// TODO - rename locationPoint -> location, location -> locationRange to match peggy +@genType +type locationPoint = { + line: int, + column: int, +} +@genType +type location = { + source: string, + start: locationPoint, + end: locationPoint, +} + +type node = {"type": string, "location": location} + +type parseError = SyntaxError(string, location) + +type parseResult = result @module("./Reducer_Peggy_GeneratedParser.js") external parse__: (string, {"grammarSource": string}) => node = "parse" -type withLocation = {"location": Reducer_ErrorValue.location} +type withLocation = {"location": location} external castWithLocation: Js.Exn.t => withLocation = "%identity" -let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.location => - castWithLocation(error)["location"] +let syntaxErrorToLocation = (error: Js.Exn.t): location => castWithLocation(error)["location"] -let parse = (expr: string, source: string): result => +let parse = (expr: string, source: string): parseResult => try { Ok(parse__(expr, {"grammarSource": source})) } catch { | Js.Exn.Error(obj) => - RESyntaxError(Belt.Option.getExn(Js.Exn.message(obj)), syntaxErrorToLocation(obj)->Some)->Error + SyntaxError(Belt.Option.getExn(Js.Exn.message(obj)), syntaxErrorToLocation(obj))->Error } type nodeBlock = {...node, "statements": array} @@ -154,8 +170,13 @@ let rec pgToString = (ast: ast): string => { } and toString = (node: node): string => node->nodeToAST->pgToString -let toStringResult = (rNode: result): string => +let toStringError = (error: parseError): string => { + let SyntaxError(message, _) = error + `Syntax Error: ${message}}` +} + +let toStringResult = (rNode: parseResult): string => switch rNode { - | Ok(node) => toString(node) - | Error(error) => `Error(${errorValueToString(error)})` + | Ok(node) => node->toString + | Error(error) => `Error(${error->toStringError})` } diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 4313f6e1..567ff29b 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res @@ -152,7 +152,10 @@ let parseIncludes = (this: t): t => { } } let doRawParse = (this: t): T.rawParseArgumentType => - this->getSource->Reducer_Peggy_Parse.parse(this.sourceId) + this + ->getSource + ->Reducer_Peggy_Parse.parse(this.sourceId) + ->E.R2.errMap(Reducer_ErrorValue.fromParseError) let rawParse = (this: t): t => this->getRawParse->E.O2.defaultFn(() => doRawParse(this))->setRawParse(this, _) From 69b32d0b93cf33e78e7d75055defff7fb869d32a Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Mon, 26 Sep 2022 04:44:08 +0400 Subject: [PATCH 03/18] refactor into SqError --- .../Reducer_Peggy_TestHelpers.res | 4 +- .../__tests__/Reducer/Reducer_TestHelpers.res | 3 +- .../__tests__/Reducer/Reducer_test.res | 2 +- .../ReducerProject_includes_test.res | 6 +- ...educerProject_tutorial_3_includes_test.res | 6 +- packages/squiggle-lang/src/js/SqError.ts | 8 +- packages/squiggle-lang/src/js/SqProject.ts | 12 +- .../src/rescript/FR/FR_Builtin.res | 4 +- .../src/rescript/FR/FR_Danger.res | 34 ++-- .../squiggle-lang/src/rescript/FR/FR_Date.res | 4 +- .../squiggle-lang/src/rescript/FR/FR_Dict.res | 21 +-- .../squiggle-lang/src/rescript/FR/FR_Dist.res | 14 +- .../src/rescript/FR/FR_GenericDist.res | 10 +- .../src/rescript/FR/FR_Pointset.res | 37 ++--- .../src/rescript/FR/FR_Sampleset.res | 13 +- .../src/rescript/FR/FR_Scoring.res | 18 ++- .../rescript/ForTS/ForTS_ReducerProject.res | 10 +- .../ForTS/ForTS_Reducer_ErrorValue.res | 21 --- .../src/rescript/ForTS/ForTS_SqError.res | 19 +++ .../ForTS_SquiggleValue.res | 4 +- .../src/rescript/ForTS/ForTS__Types.res | 3 +- .../FunctionRegistry_Core.res | 21 ++- .../FunctionRegistry_Helpers.res | 28 ++-- .../Reducer_Dispatch_ChainPiece.res | 27 ---- .../Reducer_Dispatch/Reducer_Dispatch_T.res | 21 --- .../rescript/Reducer/Reducer_ErrorValue.res | 131 ---------------- .../Reducer_Expression/Reducer_Expression.res | 32 ++-- .../Reducer_Expression_ExpressionBuilder.res | 2 - .../Reducer_Expression_Lambda.res | 4 +- .../src/rescript/Reducer/Reducer_Value.res | 14 +- .../ReducerProject/ReducerProject.res | 5 +- .../ReducerProject_ParseIncludes.res | 5 +- .../ReducerProject_ProjectItem.res | 23 +-- .../ReducerProject_ProjectItem_T.res | 11 +- .../squiggle-lang/src/rescript/SqError.res | 145 ++++++++++++++++++ .../SquiggleLibrary_StdLib.res | 11 +- 36 files changed, 329 insertions(+), 404 deletions(-) delete mode 100644 packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res create mode 100644 packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res delete mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res delete mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res delete mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res create mode 100644 packages/squiggle-lang/src/rescript/SqError.res diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res index 65d87565..e30d3274 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res @@ -22,9 +22,7 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => { } else { let a2 = rExpr - ->E.R2.errMap(e => - e->Reducer_ErrorValue.fromParseError->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue - ) + ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.Error.fromMessage) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res index 43de902e..efff179e 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res @@ -1,4 +1,3 @@ -module ErrorValue = Reducer_ErrorValue module Expression = Reducer_Expression module ExpressionT = Reducer_Expression_T @@ -9,7 +8,7 @@ let unwrapRecord = rValue => rValue->Belt.Result.flatMap(value => switch value { | Reducer_T.IEvRecord(aRecord) => Ok(aRecord) - | _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error + | _ => SqError.Message.RETodo("TODO: Internal bindings must be returned")->Error } ) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res index 49180135..0697d239 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res @@ -70,7 +70,7 @@ describe("test exceptions", () => { testDescriptionEvalToBe( "javascript exception", "javascriptraise('div by 0')", - "Error(Error: 'div by 0')", + "Error(JS Exception: Error: 'div by 0')", ) // testDescriptionEvalToBe( // "rescript exception", diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res index a6b35ec4..6c6d46fe 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res @@ -25,7 +25,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common"] - | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) + | Error(error) => fail(error->SqError.Message.toString) } }) test("past chain", () => { @@ -60,7 +60,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "myModule"] - | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) + | Error(error) => fail(error->SqError.Message.toString) } }) @@ -99,7 +99,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "common2", "myModule"] - | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) + | Error(error) => fail(error->SqError.Message.toString) } }) test("direct past chain", () => { diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res index 995ce4d3..bd06c481 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res @@ -36,7 +36,7 @@ Here we will finally proceed to a real life scenario. */ /* Parse includes has set the includes */ switch project->Project.getIncludes("main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->Reducer_ErrorValue.errorValueToString->fail + | Error(err) => err->SqError.Message.toString->fail } /* If the includes cannot be parsed then you get a syntax error. Otherwise you get the includes. @@ -85,7 +85,7 @@ Here we will finally proceed to a real life scenario. */ let rIncludes = project->Project.getIncludes(sourceName) switch rIncludes { /* Maybe there is an include syntax error */ - | Error(err) => err->Reducer_ErrorValue.errorValueToString->Js.Exn.raiseError + | Error(err) => err->SqError.Message.toString->Js.Exn.raiseError | Ok(includes) => includes->Belt.Array.forEach(newIncludeName => { @@ -169,7 +169,7 @@ Here we will finally proceed to a real life scenario. */ test("getIncludes", () => { switch Project.getIncludes(project, "main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->Reducer_ErrorValue.errorValueToString->fail + | Error(err) => err->SqError.Message.toString->fail } }) }) diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index c0c509b4..ea21dc11 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -1,13 +1,13 @@ -import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; +import * as RSError from "../rescript/ForTS/ForTS_SqError.gen"; export class SqError { - constructor(private _value: RSErrorValue.reducerError) {} + constructor(private _value: RSError.error) {} toString() { - return RSErrorValue.toString(this._value); + return RSError.toString(this._value); } static createOtherError(v: string) { - return new SqError(RSErrorValue.createOtherError(v)); + return new SqError(RSError.createOtherError(v)); } } diff --git a/packages/squiggle-lang/src/js/SqProject.ts b/packages/squiggle-lang/src/js/SqProject.ts index 22f2f78a..30d8b245 100644 --- a/packages/squiggle-lang/src/js/SqProject.ts +++ b/packages/squiggle-lang/src/js/SqProject.ts @@ -1,15 +1,12 @@ import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; -import { - reducerError, - reducerErrorValue, - attachEmptyStackTraceToErrorValue, -} from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; +import * as RSError from "../rescript/ForTS/ForTS_SqError.gen"; import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen"; import { SqError } from "./SqError"; import { SqRecord } from "./SqRecord"; import { wrapValue } from "./SqValue"; import { resultMap2 } from "./types"; import { SqValueLocation } from "./SqValueLocation"; +import { errorFromMessage } from "../rescript/ForTS/ForTS_SqError.gen"; export class SqProject { constructor(private _value: RSProject.reducerProject) {} @@ -54,8 +51,7 @@ export class SqProject { return resultMap2( RSProject.getIncludes(this._value, sourceId), (a) => a, - (v: reducerErrorValue) => - new SqError(attachEmptyStackTraceToErrorValue(v)) + (v: RSError.errorMessage) => new SqError(errorFromMessage(v)) ); } @@ -109,7 +105,7 @@ export class SqProject { items: [], }) ), - (v: reducerError) => new SqError(v) + (v: RSError.error) => new SqError(v) ); } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res b/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res index 84e6a50f..3e7a9e8b 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res @@ -5,7 +5,7 @@ let nameSpace = "" // no namespaced versions type simpleDefinition = { inputs: array, - fn: array => result, + fn: array => result, } let makeFnMany = (name: string, definitions: array) => @@ -22,7 +22,7 @@ let makeFnMany = (name: string, definitions: array) => let makeFn = ( name: string, inputs: array, - fn: array => result, + fn: array => result, ) => makeFnMany(name, [{inputs: inputs, fn: fn}]) let library = [ diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res index 7767f8c1..0474c1f4 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res @@ -77,7 +77,7 @@ module Integration = { | Reducer_T.IEvNumber(x) => Ok(x) | _ => Error( - "Error 1 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->Reducer_ErrorValue.REOther, + "Error 1 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->SqError.Message.REOther, ) } result @@ -143,8 +143,8 @@ module Integration = { | Error(b) => ("Integration error 2 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead." ++ "Original error: " ++ - b->Reducer_ErrorValue.errorValueToString) - ->Reducer_ErrorValue.REOther + b->SqError.Message.toString) + ->SqError.Message.REOther ->Error } result @@ -169,7 +169,7 @@ module Integration = { let result = switch inputs { | [_, _, _, IEvNumber(0.0)] => "Integration error 4 in Danger.integrate: Increment can't be 0." - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error | [ IEvLambda(aLambda), @@ -187,7 +187,7 @@ module Integration = { ) | _ => Error( - Reducer_ErrorValue.REOther( + SqError.Message.REOther( "Integration error 5 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))", ), ) @@ -213,7 +213,7 @@ module Integration = { let result = switch inputs { | [_, _, _, IEvNumber(0.0)] => "Integration error in Danger.integrate: Increment can't be 0." - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error | [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] => Helpers.integrateFunctionBetweenWithNumIntegrationPoints( @@ -225,11 +225,11 @@ module Integration = { reducer, )->E.R2.errMap(b => ("Integration error 7 in Danger.integrate. Something went wrong along the way: " ++ - b->Reducer_ErrorValue.errorValueToString)->Reducer_ErrorValue.REOther + b->SqError.Message.toString)->SqError.Message.REOther ) | _ => "Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))" - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error } result @@ -246,7 +246,7 @@ module DiminishingReturns = { module Helpers = { type diminishingReturnsAccumulatorInner = { optimalAllocations: array, - currentMarginalReturns: result, errorValue>, + currentMarginalReturns: result, errorMessage>, } let findBiggestElementIndex = (xs: array) => E.A.reducei(xs, 0, (acc, newElement, index) => { @@ -255,7 +255,7 @@ module DiminishingReturns = { | false => acc } }) - type diminishingReturnsAccumulator = result + type diminishingReturnsAccumulator = result // TODO: This is so complicated, it probably should be its own file. It might also make sense to have it work in Rescript directly, taking in a function rather than a reducer; then something else can wrap that function in the reducer/lambdas/environment. /* The key idea for this function is that @@ -290,19 +290,19 @@ module DiminishingReturns = { ) { | (false, _, _, _) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->SqError.Message.REOther, ) | (_, false, _, _) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->SqError.Message.REOther, ) | (_, _, false, _) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->SqError.Message.REOther, ) | (_, _, _, false) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->SqError.Message.REOther, ) | (true, true, true, true) => { let applyFunctionAtPoint = (lambda, point: float) => { @@ -318,7 +318,7 @@ module DiminishingReturns = { | Reducer_T.IEvNumber(x) => Ok(x) | _ => Error( - "Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->Reducer_ErrorValue.REOther, + "Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->SqError.Message.REOther, ) } } @@ -411,7 +411,7 @@ module DiminishingReturns = { | Reducer_T.IEvLambda(lambda) => Ok(lambda) | _ => "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. A member of the array wasn't a function" - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error } }, innerlambdas) @@ -433,7 +433,7 @@ module DiminishingReturns = { } | _ => "Error in Danger.diminishingMarginalReturnsForTwoFunctions" - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error }, (), diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Date.res b/packages/squiggle-lang/src/rescript/FR/FR_Date.res index c9732980..0e4b828d 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Date.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Date.res @@ -4,7 +4,7 @@ open FunctionRegistry_Helpers let makeFn = ( name: string, inputs: array, - fn: array => result, + fn: array => result, ) => Function.make( ~name, @@ -66,7 +66,7 @@ let library = [ | [IEvNumber(year)] => switch DateTime.Date.makeFromYear(year) { | Ok(t) => IEvDate(t)->Ok - | Error(e) => Reducer_ErrorValue.RETodo(e)->Error + | Error(e) => SqError.Message.RETodo(e)->Error } | _ => Error(impossibleError) } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Dict.res b/packages/squiggle-lang/src/rescript/FR/FR_Dict.res index 13bcfe33..f844ceac 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Dict.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Dict.res @@ -17,7 +17,7 @@ module Internals = { ->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value])) ->Wrappers.evArray - let fromList = (items: array): result => + let fromList = (items: array): result => items ->E.A2.fmap(item => { switch (item: Reducer_T.value) { @@ -59,7 +59,6 @@ let library = [ ], (), ), - Function.make( ~name="mergeMany", ~nameSpace, @@ -72,13 +71,17 @@ let library = [ ~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))], ~run=(inputs, _, _) => { switch inputs { - | [IEvArray(dicts)] => { - dicts->Belt.Array.map(dictValue => switch dictValue { - | IEvRecord(dict) => dict - | _ => impossibleError->Reducer_ErrorValue.toException - })->Internals.mergeMany->Ok - } - | _ => impossibleError->Error + | [IEvArray(dicts)] => + dicts + ->Belt.Array.map(dictValue => + switch dictValue { + | IEvRecord(dict) => dict + | _ => impossibleError->SqError.Message.toException + } + ) + ->Internals.mergeMany + ->Ok + | _ => impossibleError->Error } }, (), diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Dist.res b/packages/squiggle-lang/src/rescript/FR/FR_Dist.res index 3a7504f2..270e2b4a 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Dist.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Dist.res @@ -16,14 +16,13 @@ module DistributionCreation = { r ->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env)) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REOther(e)) + ->E.R2.errMap(e => SqError.Message.REOther(e)) let make = (name, fn) => { FnDefinition.make( ~name, ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], - ~run=(inputs, env, _) => - inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env), + ~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env), (), ) } @@ -43,7 +42,9 @@ module DistributionCreation = { ~name, ~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])], ~run=(inputs, env, _) => - inputs->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev"))->process(~fn, ~env), + inputs + ->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev")) + ->process(~fn, ~env), (), ) } @@ -54,14 +55,13 @@ module DistributionCreation = { r ->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env)) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REOther(e)) + ->E.R2.errMap(e => SqError.Message.REOther(e)) let make = (name, fn) => FnDefinition.make( ~name, ~inputs=[FRTypeDistOrNumber], - ~run=(inputs, env, _) => - inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env), + ~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env), (), ) } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res b/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res index 6f98d132..72249d19 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res @@ -296,7 +296,7 @@ module Old = { let genericOutputToReducerValue = (o: DistributionOperation.outputType): result< Reducer_T.value, - Reducer_ErrorValue.errorValue, + SqError.Message.t, > => switch o { | Dist(d) => Ok(Reducer_T.IEvDistribution(d)) @@ -311,9 +311,9 @@ module Old = { switch dispatchToGenericOutput(call, environment) { | Some(o) => genericOutputToReducerValue(o) | None => - Reducer_ErrorValue.REOther("Internal error in FR_GenericDist implementation") - ->Reducer_ErrorValue.ErrorException - ->raise + SqError.Message.REOther( + "Internal error in FR_GenericDist implementation", + )->SqError.Message.toException } } @@ -405,6 +405,6 @@ let library = E.A.concatMany([ let mxLambda = Reducer_Expression_Lambda.makeFFILambda((inputs, env, _) => { switch Old.dispatch(("mx", inputs), env) { | Ok(value) => value - | Error(e) => e->Reducer_ErrorValue.ErrorException->raise + | Error(e) => e->SqError.Message.toException } }) diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res b/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res index 4babe231..28684f34 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res @@ -7,31 +7,32 @@ let requiresNamespace = true let inputsToDist = (inputs: array, xyShapeToPointSetDist) => { // TODO - rewrite in more functional/functor-based style switch inputs { - | [IEvArray(items)] => { - items->Belt.Array.map( - item => - switch item { - | IEvRecord(map) => { - let xValue = map->Belt.Map.String.get("x") - let yValue = map->Belt.Map.String.get("y") - switch (xValue, yValue) { - | (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y) - | _ => impossibleError->Reducer_ErrorValue.toException - } + | [IEvArray(items)] => + items + ->Belt.Array.map(item => + switch item { + | IEvRecord(map) => { + let xValue = map->Belt.Map.String.get("x") + let yValue = map->Belt.Map.String.get("y") + switch (xValue, yValue) { + | (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y) + | _ => impossibleError->SqError.Message.toException } - | _ => impossibleError->Reducer_ErrorValue.toException } - )->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->Reducer_ErrorValue.toException + | _ => impossibleError->SqError.Message.toException + } + ) + ->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 } } module Internal = { type t = PointSetDist.t - let toType = (r): result => + let toType = (r): result => switch r { | Ok(r) => Ok(Wrappers.evDistribution(PointSet(r))) | Error(err) => Error(REOperationError(err)) @@ -71,7 +72,7 @@ let library = [ ) ->E.R2.fmap(Wrappers.pointSet) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) + ->E.R2.errMap(e => SqError.Message.REDistributionError(e)) | _ => Error(impossibleError) }, (), diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res b/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res index 5bbfb506..7f11ba7a 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res @@ -18,7 +18,7 @@ module Internal = { | _ => Error(Operation.SampleMapNeedsNtoNFunction) } - let toType = (r): result => + let toType = (r): result => switch r { | Ok(r) => Ok(Wrappers.evDistribution(SampleSet(r))) | Error(r) => Error(REDistributionError(SampleSetError(r))) @@ -95,7 +95,7 @@ let libaryBase = [ GenericDist.toSampleSetDist(dist, environment.sampleCount) ->E.R2.fmap(Wrappers.sampleSet) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) + ->E.R2.errMap(e => SqError.Message.REDistributionError(e)) | _ => Error(impossibleError) }, (), @@ -115,9 +115,8 @@ let libaryBase = [ ~inputs=[FRTypeArray(FRTypeNumber)], ~run=(inputs, _, _) => { let sampleSet = - inputs->Prepare.ToTypedArray.numbers |> E.R2.bind(r => - SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??") - ) + inputs->Prepare.ToTypedArray.numbers + |> E.R2.bind(r => SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??")) sampleSet ->E.R2.fmap(Wrappers.sampleSet) ->E.R2.fmap(Wrappers.evDistribution) @@ -164,7 +163,7 @@ let libaryBase = [ | [IEvLambda(lambda)] => switch Internal.fromFn(lambda, environment, reducer) { | Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution) - | Error(e) => e->Reducer_ErrorValue.REOperationError->Error + | Error(e) => e->SqError.Message.REOperationError->Error } | _ => Error(impossibleError) }, @@ -291,7 +290,7 @@ module Comparison = { r ->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution) ->E.R2.errMap(e => - e->DistributionTypes.Error.sampleErrorToDistErr->Reducer_ErrorValue.REDistributionError + e->DistributionTypes.Error.sampleErrorToDistErr->SqError.Message.REDistributionError ) let mkBig = (name, withDist, withFloat) => diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res b/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res index 63589e83..60cb82e5 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res @@ -6,7 +6,7 @@ let requiresNamespace = true let runScoring = (estimate, answer, prior, env) => { GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env) ->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber) - ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) + ->E.R2.errMap(e => SqError.Message.REDistributionError(e)) } let library = [ @@ -31,14 +31,13 @@ let library = [ ]), ], ~run=(inputs, environment, _) => { - switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs, ("estimate", "answer", "prior")) { + 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) - | Ok([ - IEvDistribution(estimate), - IEvNumber(d), - IEvDistribution(prior), - ]) => + | Ok([IEvDistribution(estimate), IEvNumber(d), IEvDistribution(prior)]) => runScoring(estimate, Score_Scalar(d), Some(prior), environment) | Error(e) => Error(e->FunctionRegistry_Helpers.wrapError) | _ => Error(FunctionRegistry_Helpers.impossibleError) @@ -50,7 +49,10 @@ let library = [ ~name="logScore", ~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])], ~run=(inputs, environment, _) => { - switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs, ("estimate", "answer")) { + switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs( + inputs, + ("estimate", "answer"), + ) { | Ok([IEvDistribution(estimate), IEvDistribution(d)]) => runScoring(estimate, Score_Dist(d), None, environment) | Ok([IEvDistribution(estimate), IEvNumber(d)]) => diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res index 857044d7..93581b15 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res @@ -1,7 +1,7 @@ @genType type reducerProject = ReducerProject_T.project //re-export -type reducerError = ForTS_Reducer_ErrorValue.reducerError //use -type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use +type error = ForTS_SqError.error //use +type errorMessage = ForTS_SqError.errorMessage //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use @@ -99,7 +99,7 @@ To set the includes one first has to call "parseIncludes". The parsed includes o @genType let getIncludes = (project: reducerProject, sourceId: string): result< array, - reducerErrorValue, + errorMessage, > => project->Private.getIncludes(sourceId) /* Other sources contributing to the global namespace of this source. */ @@ -192,7 +192,7 @@ let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Rec Get the result after running this source file or the project */ @genType -let getResult = (project: reducerProject, sourceId: string): result => +let getResult = (project: reducerProject, sourceId: string): result => project->Private.getResult(sourceId) /* @@ -201,7 +201,7 @@ However, without a project, you cannot handle include directives. The source has to be include free */ @genType -let evaluate = (sourceCode: string): (result, squiggleValue_Record) => +let evaluate = (sourceCode: string): (result, squiggleValue_Record) => Private.evaluate(sourceCode) @genType diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res deleted file mode 100644 index 1392639b..00000000 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res +++ /dev/null @@ -1,21 +0,0 @@ -@genType type reducerError = Reducer_ErrorValue.error //alias -@genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias -@genType type location = Reducer_Peggy_Parse.location //alias - -@genType -let toString = (e: reducerError): string => Reducer_ErrorValue.errorToString(e) - -@genType -let getLocation = (e: reducerError): option => - switch e.stackTrace { - | Some(stack) => Some(stack.location) - | None => None - } - -@genType -let createOtherError = (v: string): reducerError => - Reducer_ErrorValue.REOther(v)->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue - -@genType -let attachEmptyStackTraceToErrorValue = (v: reducerErrorValue): reducerError => - Reducer_ErrorValue.attachEmptyStackTraceToErrorValue(v) diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res new file mode 100644 index 00000000..c6a4b131 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res @@ -0,0 +1,19 @@ +@genType type error = SqError.Error.t //alias +@genType type errorMessage = SqError.Message.t //alias +@genType type location = Reducer_Peggy_Parse.location //alias + +@genType +let toString = (e: error): string => SqError.Error.toString(e) + +@genType +let getLocation = (e: error): option => + switch e.stackTrace { + | Some(stack) => Some(stack.location) + | None => None + } + +@genType +let createOtherError = (v: string): error => SqError.Message.REOther(v)->SqError.Error.fromMessage + +@genType +let errorFromMessage = (v: errorMessage): error => v->SqError.Error.fromMessage diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res index ff029c95..2a8e8e1c 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res @@ -1,5 +1,5 @@ @genType type squiggleValue = Reducer_T.value //re-export -type reducerError = ForTS_Reducer_ErrorValue.reducerError //use +type error = ForTS_SqError.error //use @genType type squiggleValue_Array = Reducer_T.arrayValue //re-export recursive type @genType type squiggleValue_Record = Reducer_T.map //re-export recursive type @@ -69,7 +69,7 @@ let toString = (variant: squiggleValue) => Reducer_Value.toString(variant) // This is a useful method for unit tests. // Convert the result along with the error message to a string. @genType -let toStringResult = (variantResult: result) => +let toStringResult = (variantResult: result) => Reducer_Value.toStringResult(variantResult) @genType diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res index 949801c7..cd6562b9 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res @@ -1,5 +1,4 @@ -@genType type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //re-export -@genType type location = ForTS_Reducer_ErrorValue.location //re-export +@genType type location = ForTS_SqError.location //re-export @genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res index e6929761..f39eecc4 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res @@ -1,5 +1,5 @@ type internalExpressionValueType = Reducer_Value.internalExpressionValueType -type errorValue = Reducer_ErrorValue.errorValue +type errorMessage = SqError.Message.t /* Function Registry "Type". A type, without any other information. @@ -32,7 +32,7 @@ type fnDefinition = { array, Reducer_T.environment, Reducer_T.reducerFn, - ) => result, + ) => result, } type function = { @@ -83,19 +83,17 @@ module FRType = { | (FRTypeNumeric, IEvNumber(_)) => true | (FRTypeNumeric, IEvDistribution(Symbolic(#Float(_)))) => true | (FRTypeLambda, IEvLambda(_)) => true - | (FRTypeArray(intendedType), IEvArray(elements)) => { - elements->Belt.Array.every(v => matchWithValue(intendedType, v)) - } + | (FRTypeArray(intendedType), IEvArray(elements)) => + elements->Belt.Array.every(v => matchWithValue(intendedType, v)) | (FRTypeDict(r), IEvRecord(map)) => map->Belt.Map.String.valuesToArray->Belt.Array.every(v => matchWithValue(r, v)) - | (FRTypeRecord(recordParams), IEvRecord(map)) => { + | (FRTypeRecord(recordParams), IEvRecord(map)) => recordParams->Belt.Array.every(((name, input)) => { switch map->Belt.Map.String.get(name) { - | Some(v) => matchWithValue(input, v) - | None => false + | Some(v) => matchWithValue(input, v) + | None => false } }) - } | _ => false } @@ -104,8 +102,7 @@ module FRType = { if !isSameLength { false } else { - E.A.zip(inputs, args) - ->Belt.Array.every(((input, arg)) => matchWithValue(input, arg)) + E.A.zip(inputs, args)->Belt.Array.every(((input, arg)) => matchWithValue(input, arg)) } } } @@ -230,7 +227,7 @@ module Registry = { args: array, env: Reducer_T.environment, reducer: Reducer_T.reducerFn, - ): result => { + ): result => { switch Belt.Map.String.get(registry.fnNameDict, fnName) { | Some(definitions) => { let showNameMatchDefinitions = () => { diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res index a5a0f523..9ace7e20 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res @@ -2,8 +2,8 @@ open FunctionRegistry_Core open Reducer_T let impossibleErrorString = "Wrong inputs / Logically impossible" -let impossibleError: errorValue = impossibleErrorString->Reducer_ErrorValue.REOther -let wrapError = e => Reducer_ErrorValue.REOther(e) +let impossibleError: errorMessage = impossibleErrorString->SqError.Message.REOther +let wrapError = e => SqError.Message.REOther(e) module Wrappers = { let symbolic = r => DistributionTypes.Symbolic(r) @@ -30,21 +30,21 @@ module Prepare = { let twoArgs = (inputs: ts, (arg1: string, arg2: string)): result => switch inputs { | [IEvRecord(map)] => { - let n1 = map->Belt.Map.String.getExn(arg1) - let n2 = map->Belt.Map.String.getExn(arg2) - Ok([n1, n2]) - } + let n1 = map->Belt.Map.String.getExn(arg1) + let n2 = map->Belt.Map.String.getExn(arg2) + Ok([n1, n2]) + } | _ => Error(impossibleErrorString) } let threeArgs = (inputs: ts, (arg1: string, arg2: string, arg3: string)): result => switch inputs { | [IEvRecord(map)] => { - let n1 = map->Belt.Map.String.getExn(arg1) - let n2 = map->Belt.Map.String.getExn(arg2) - let n3 = map->Belt.Map.String.getExn(arg3) - Ok([n1, n2, n3]) - } + let n1 = map->Belt.Map.String.getExn(arg1) + let n2 = map->Belt.Map.String.getExn(arg2) + let n3 = map->Belt.Map.String.getExn(arg3) + Ok([n1, n2, n3]) + } | _ => Error(impossibleErrorString) } } @@ -108,8 +108,10 @@ module Prepare = { } module Record = { - let twoDistOrNumber = (values: ts, labels: (string, string)): result<(frValueDistOrNumber, frValueDistOrNumber), err> => - values->ToValueArray.Record.twoArgs(labels)->E.R.bind(twoDistOrNumber) + let twoDistOrNumber = (values: ts, labels: (string, string)): result< + (frValueDistOrNumber, frValueDistOrNumber), + err, + > => values->ToValueArray.Record.twoArgs(labels)->E.R.bind(twoDistOrNumber) let twoDist = (values: ts, labels: (string, string)): result< (DistributionTypes.genericDist, DistributionTypes.genericDist), diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res deleted file mode 100644 index e545bb2d..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res +++ /dev/null @@ -1,27 +0,0 @@ -// types are disabled until review and rewrite for 0.5 interpreter compatibility -/* -module ProjectAccessorsT = ReducerProject_ProjectAccessors_T -module T = Reducer_Dispatch_T -module TypeChecker = Reducer_Type_TypeChecker -open Reducer_Value - -type errorValue = Reducer_ErrorValue.errorValue - -let makeFromTypes = jumpTable => { - let dispatchChainPiece: T.dispatchChainPiece = ( - (fnName, fnArgs): functionCall, - accessors: ProjectAccessorsT.t, - ) => { - let jumpTableEntry = jumpTable->Js.Array2.find(elem => { - let (candidName, candidType, _) = elem - candidName == fnName && TypeChecker.checkITypeArgumentsBool(candidType, fnArgs) - }) - switch jumpTableEntry { - | Some((_, _, bridgeFn)) => bridgeFn(fnArgs, accessors)->Some - | _ => None - } - } - dispatchChainPiece -} - -*/ diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res deleted file mode 100644 index 0d852783..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res +++ /dev/null @@ -1,21 +0,0 @@ -// module ExpressionT = Reducer_Expression_T -// module ProjectAccessorsT = ReducerProject_ProjectAccessors_T - -// // Each piece of the dispatch chain computes the result or returns None so that the chain can continue -// type dispatchChainPiece = ( -// Reducer_Value.functionCall, -// ProjectAccessorsT.t, -// ) => option> - -// type dispatchChainPieceWithReducer = ( -// Reducer_Value.functionCall, -// ProjectAccessorsT.t, -// Reducer_T.reducerFn, -// ) => option> - -// // This is a switch statement case implementation: get the arguments and compute the result -// type genericIEvFunction = ( -// array, -// ProjectAccessorsT.t, -// ) => result - diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res deleted file mode 100644 index e2c1d495..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ /dev/null @@ -1,131 +0,0 @@ -type location = Reducer_Peggy_Parse.location - -@genType.opaque -type errorValue = - | REArityError(option, int, int) - | REArrayIndexNotFound(string, int) - | REAssignmentExpected - | REDistributionError(DistributionTypes.error) - | REExpectedType(string, string) - | REExpressionExpected - | REFunctionExpected(string) - | REFunctionNotFound(string) - | REJavaScriptExn(option, option) // Javascript Exception - | REMacroNotFound(string) - | RENotAFunction(string) - | REOperationError(Operation.operationError) - | RERecordPropertyNotFound(string, string) - | RESymbolNotFound(string) - | RESyntaxError(string, option) - | RETodo(string) // To do - | REUnitNotFound(string) - | RENeedToRun - | REOther(string) - -type t = errorValue - -exception ErrorException(errorValue) - -type rec stackTrace = { - location: location, - parent: option, -} - -@genType.opaque -type error = { - error: t, - stackTrace: option, -} - -exception ExceptionWithStackTrace(error) - -let fromParseError = ( - SyntaxError(message, location): Reducer_Peggy_Parse.parseError, -) => RESyntaxError(message, location->Some) - -let errorValueToString = (err: errorValue) => - switch err { - | REArityError(_oFnName, arity, usedArity) => - `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( - usedArity, - )} argument(s) were passed.` - | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` - | REAssignmentExpected => "Assignment expected" - | REExpressionExpected => "Expression expected" - | REFunctionExpected(msg) => `Function expected: ${msg}` - | REFunctionNotFound(msg) => `Function not found: ${msg}` - | REDistributionError(err) => `Distribution Math Error: ${DistributionTypes.Error.toString(err)}` - | REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}` - | REJavaScriptExn(omsg, oname) => { - let answer = "JS Exception:" - let answer = switch oname { - | Some(name) => `${answer} ${name}` - | _ => answer - } - let answer = switch omsg { - | Some(msg) => `${answer}: ${msg}` - | _ => answer - } - answer - } - | REMacroNotFound(macro) => `Macro not found: ${macro}` - | RENotAFunction(valueString) => `${valueString} is not a function` - | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` - | RESymbolNotFound(symbolName) => `${symbolName} is not defined` - | RESyntaxError(desc, _) => `Syntax Error: ${desc}` - | RETodo(msg) => `TODO: ${msg}` - | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}` - | REUnitNotFound(unitName) => `Unit not found: ${unitName}` - | RENeedToRun => "Need to run" - | REOther(msg) => `Error: ${msg}` - } - -let fromException = exn => - switch exn { - | ErrorException(e) => e - | Js.Exn.Error(e) => - switch Js.Exn.message(e) { - | Some(message) => REOther(message) - | None => - switch Js.Exn.name(e) { - | Some(name) => REOther(name) - | None => REOther("Unknown error") - } - } - | _e => REOther("Unknown error") - } - -let rec stackTraceToString = ({location, parent}: stackTrace) => { - ` Line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}, source ${location.source}\n` ++ - switch parent { - | Some(parent) => stackTraceToString(parent) - | None => "" - } -} - -let errorToString = (err: error) => - switch err.stackTrace { - | Some(stack) => "Traceback:\n" ++ stack->stackTraceToString - | None => "" - } ++ - err.error->errorValueToString - -let toException = (errorValue: t) => errorValue->ErrorException->raise - -let attachEmptyStackTraceToErrorValue = (errorValue: t) => { - error: errorValue, - stackTrace: None, -} - -let attachLocationToErrorValue = (errorValue: t, location: location): error => { - error: errorValue, - stackTrace: Some({location: location, parent: None}), -} - -let raiseNewExceptionWithStackTrace = (errorValue: t, location: location) => - errorValue->attachLocationToErrorValue(location)->ExceptionWithStackTrace->raise - -let raiseExtendedExceptionWithStackTrace = ({error, stackTrace}: error, location: location) => - {error: error, stackTrace: Some({location: location, parent: stackTrace})} - ->ExceptionWithStackTrace - ->raise 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 714daffe..d0060982 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 @@ -3,14 +3,12 @@ module Lambda = Reducer_Expression_Lambda module Result = Belt.Result module T = Reducer_T -type errorValue = Reducer_ErrorValue.errorValue - -let toLocation = (expression: T.expression): Reducer_ErrorValue.location => { +let toLocation = (expression: T.expression): SqError.location => { expression.ast.location } -let throwFrom = (error: errorValue, expression: T.expression) => - error->Reducer_ErrorValue.raiseNewExceptionWithStackTrace(expression->toLocation) +let throwFrom = (error: SqError.Message.t, expression: T.expression) => + error->SqError.Error.fromMessageWithLocation(expression->toLocation)->SqError.Error.throw /* Recursively evaluate the expression @@ -110,11 +108,11 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { let result = Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate) (result, context) } catch { - | Reducer_ErrorValue.ErrorException(e) => e->throwFrom(expression) // function implementation returned an error without location - | Reducer_ErrorValue.ExceptionWithStackTrace(e) => - Reducer_ErrorValue.raiseExtendedExceptionWithStackTrace(e, expression->toLocation) // function implementation probably called a lambda that threw an exception - | Js.Exn.Error(obj) => - REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwFrom(expression) + | exn => + exn + ->SqError.Error.fromException + ->SqError.Error.extend(expression->toLocation) + ->SqError.Error.throw } | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression) } @@ -128,24 +126,18 @@ module BackCompatible = { let parse = (peggyCode: string): result => peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) - let evaluate = (expression: T.expression): result => { + let evaluate = (expression: T.expression): result => { let context = Reducer_Context.createDefaultContext() try { let (value, _) = expression->evaluate(context) value->Ok } catch { - | exn => - exn - ->Reducer_ErrorValue.fromException - ->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue - ->Error // TODO - this could be done better (see ReducerProject) + | exn => exn->SqError.Error.fromException->Error } } - let evaluateString = (peggyCode: string): result => + let evaluateString = (peggyCode: string): result => parse(peggyCode) - ->E.R2.errMap(e => - e->Reducer_ErrorValue.fromParseError->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue - ) + ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.Error.fromMessage) ->Result.flatMap(evaluate) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res index bd0a044a..965fc499 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res @@ -1,7 +1,5 @@ -module BErrorValue = Reducer_ErrorValue module T = Reducer_T -type errorValue = BErrorValue.errorValue type expression = Reducer_T.expression type expressionContent = Reducer_T.expressionContent 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 index e9a85a1a..36e43b25 100644 --- 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 @@ -1,5 +1,3 @@ -module ErrorValue = Reducer_ErrorValue - let doLambdaCall = ( lambdaValue: Reducer_T.lambdaValue, args, @@ -28,7 +26,7 @@ let makeLambda = ( let argsLength = arguments->Js.Array2.length let parametersLength = parameters->Js.Array2.length if argsLength !== parametersLength { - ErrorValue.REArityError(None, parametersLength, argsLength)->ErrorValue.ErrorException->raise + SqError.Message.REArityError(None, parametersLength, argsLength)->SqError.Message.toException } let localBindings = bindings->Reducer_Bindings.extend diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res index f7681ee7..ea0931f2 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res @@ -1,7 +1,3 @@ -// deprecated, use Reducer_T instead -// (value methods should be moved to Reducer_Value.res) - -module ErrorValue = Reducer_ErrorValue type environment = GenericDist.env module T = Reducer_T @@ -76,13 +72,13 @@ let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)}) let toStringResult = x => switch x { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${ErrorValue.errorToString(m)})` + | Error(m) => `Error(${SqError.Error.toString(m)})` } -let toStringResultOkless = (codeResult: result): string => +let toStringResultOkless = (codeResult: result): string => switch codeResult { | Ok(a) => toString(a) - | Error(m) => `Error(${ErrorValue.errorToString(m)})` + | Error(m) => `Error(${SqError.Error.toString(m)})` } type internalExpressionValueType = @@ -144,10 +140,10 @@ let functionCallSignatureToString = (functionCallSignature: functionCallSignatur let arrayToValueArray = (arr: array): array => arr -let resultToValue = (rExpression: result): t => +let resultToValue = (rExpression: result): t => switch rExpression { | Ok(expression) => expression - | Error(errorValue) => Reducer_ErrorValue.toException(errorValue) + | Error(errorValue) => SqError.Message.toException(errorValue) } let recordToKeyValuePairs = (record: T.map): array<(string, t)> => record->Belt.Map.String.toArray diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res index 082b620c..8ead6e32 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res @@ -1,7 +1,6 @@ // TODO: Auto clean project based on topology module Bindings = Reducer_Bindings -module ErrorValue = Reducer_ErrorValue module ProjectItem = ReducerProject_ProjectItem module T = ReducerProject_T module Topology = ReducerProject_Topology @@ -117,7 +116,7 @@ let getResultOption = (project: t, sourceId: string): ProjectItem.T.resultType = let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType => switch getResultOption(project, sourceId) { - | None => RENeedToRun->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue->Error + | None => RENeedToRun->SqError.Error.fromMessage->Error | Some(result) => result } @@ -171,7 +170,7 @@ let linkDependencies = (project: t, sourceId: string): Reducer_T.namespace => { "__result__", switch project->getResult(id) { | Ok(result) => result - | Error(error) => error->Reducer_ErrorValue.ExceptionWithStackTrace->raise + | Error(error) => error->SqError.Error.throw }, ), ]) diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res index 38370488..c0612be8 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res @@ -1,10 +1,7 @@ @module("./ReducerProject_IncludeParser.js") external parse__: string => array> = "parse" -let parseIncludes = (expr: string): result< - array<(string, string)>, - Reducer_ErrorValue.errorValue, -> => +let parseIncludes = (expr: string): result, SqError.Message.t> => try { let answer = parse__(expr) // let logEntry = answer->Js.Array2.joinWith(",") diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 567ff29b..07ecac88 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res @@ -155,7 +155,7 @@ let doRawParse = (this: t): T.rawParseArgumentType => this ->getSource ->Reducer_Peggy_Parse.parse(this.sourceId) - ->E.R2.errMap(Reducer_ErrorValue.fromParseError) + ->E.R2.errMap(SqError.Message.fromParseError) let rawParse = (this: t): t => this->getRawParse->E.O2.defaultFn(() => doRawParse(this))->setRawParse(this, _) @@ -174,7 +174,7 @@ let buildExpression = (this: t): t => { } } -let failRun = (this: t, e: Reducer_ErrorValue.error): t => +let failRun = (this: t, e: SqError.Error.t): t => this->setResult(e->Error)->setContinuation(Reducer_Namespace.make()) let doRun = (this: t, context: Reducer_T.context): t => @@ -188,24 +188,11 @@ let doRun = (this: t, context: Reducer_T.context): t => ->setResult(result->Ok) ->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals) } catch { - | Reducer_ErrorValue.ErrorException(e) => - this->failRun(e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) - | Reducer_ErrorValue.ExceptionWithStackTrace(e) => this->failRun(e) - | _ => - this->failRun( - RETodo( - "unhandled rescript exception", - )->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue, - ) + | e => this->failRun(e->SqError.Error.fromException) } - | Error(e) => this->failRun(e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + | Error(e) => this->failRun(e->SqError.Error.fromMessage) } - | None => - this->failRun( - RETodo( - "attempt to run without expression", - )->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue, - ) + | None => this->failRun(RETodo("attempt to run without expression")->SqError.Error.fromMessage) } let run = (this: t, context: Reducer_T.context): t => { diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res index 5d32a161..8ea3a814 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -1,22 +1,21 @@ module Parse = Reducer_Peggy_Parse module ExpressionT = Reducer_Expression_T -open Reducer_ErrorValue type sourceArgumentType = string type sourceType = string -type rawParseArgumentType = result +type rawParseArgumentType = result type rawParseType = option -type expressionArgumentType = result +type expressionArgumentType = result type expressionType = option type continuationArgumentType = Reducer_T.namespace type continuationType = option -type continuationResultType = option> -type resultArgumentType = result +type continuationResultType = option> +type resultArgumentType = result type resultType = option type continuesArgumentType = array type continuesType = array type includesArgumentType = string -type includesType = result, errorValue> +type includesType = result, SqError.Message.t> type importAsVariablesType = array<(string, string)> type projectItem = { diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res new file mode 100644 index 00000000..15f72d54 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -0,0 +1,145 @@ +type location = Reducer_Peggy_Parse.location + +module Message = { + @genType.opaque + type t = + | REArityError(option, int, int) + | REArrayIndexNotFound(string, int) + | REAssignmentExpected + | REDistributionError(DistributionTypes.error) + | REExpectedType(string, string) + | REExpressionExpected + | REFunctionExpected(string) + | REFunctionNotFound(string) + | REJavaScriptExn(option, option) // Javascript Exception + | REMacroNotFound(string) + | RENotAFunction(string) + | REOperationError(Operation.operationError) + | RERecordPropertyNotFound(string, string) + | RESymbolNotFound(string) + | RESyntaxError(string, option) + | RETodo(string) // To do + | REUnitNotFound(string) + | RENeedToRun + | REOther(string) + + exception MessageException(t) + + let fromParseError = ( + SyntaxError(message, location): Reducer_Peggy_Parse.parseError, + ) => RESyntaxError(message, location->Some) + + let toString = (err: t) => + switch err { + | REArityError(_oFnName, arity, usedArity) => + `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( + usedArity, + )} argument(s) were passed.` + | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` + | REAssignmentExpected => "Assignment expected" + | REExpressionExpected => "Expression expected" + | REFunctionExpected(msg) => `Function expected: ${msg}` + | REFunctionNotFound(msg) => `Function not found: ${msg}` + | REDistributionError(err) => + `Distribution Math Error: ${DistributionTypes.Error.toString(err)}` + | REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}` + | REJavaScriptExn(omsg, oname) => { + let answer = "JS Exception:" + let answer = switch oname { + | Some(name) => `${answer} ${name}` + | _ => answer + } + let answer = switch omsg { + | Some(msg) => `${answer}: ${msg}` + | _ => answer + } + answer + } + | REMacroNotFound(macro) => `Macro not found: ${macro}` + | RENotAFunction(valueString) => `${valueString} is not a function` + | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` + | RESymbolNotFound(symbolName) => `${symbolName} is not defined` + | RESyntaxError(desc, _) => `Syntax Error: ${desc}` + | RETodo(msg) => `TODO: ${msg}` + | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}` + | REUnitNotFound(unitName) => `Unit not found: ${unitName}` + | RENeedToRun => "Need to run" + | REOther(msg) => `Error: ${msg}` + } + + let fromException = exn => + switch exn { + | MessageException(e) => e + | Js.Exn.Error(e) => + switch Js.Exn.message(e) { + | Some(message) => REOther(message) + | None => + switch Js.Exn.name(e) { + | Some(name) => REOther(name) + | None => REOther("Unknown error") + } + } + | _e => REOther("Unknown error") + } + + let toException = (errorValue: t) => errorValue->MessageException->raise +} + +module StackTrace = { + 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 => "" + } + } +} + +module Error = { + @genType.opaque + type t = { + message: Message.t, + stackTrace: option, + } + + exception SqException(t) + + let toString = (err: t) => err.message->Message.toString + + let toStringWithStackTrace = (err: t) => + switch err.stackTrace { + | Some(stack) => "Traceback:\n" ++ stack->StackTrace.toString + | None => "" + } ++ + err->toString + + let fromMessage = (errorMessage: Message.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) => { + message: message, + stackTrace: Some({location: location, parent: stackTrace}), + } + + let throw = (t: t) => t->SqException->raise + + let fromException = exn => + switch exn { + | SqException(e) => e + | Message.MessageException(e) => e->fromMessage + | Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage + | _ => REOther("Unknown exception")->fromMessage + } +} diff --git a/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res b/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res index 3be93851..57311827 100644 --- a/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res +++ b/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res @@ -1,4 +1,4 @@ -exception ErrorException = Reducer_ErrorValue.ErrorException +let throwMessage = SqError.Message.toException let stdLib: Reducer_T.namespace = { // constants @@ -17,16 +17,15 @@ let stdLib: Reducer_T.namespace = { switch Belt.Array.get(aValueArray, index) { | Some(value) => value - | None => REArrayIndexNotFound("Array index not found", index)->ErrorException->raise + | None => REArrayIndexNotFound("Array index not found", index)->throwMessage } } | [IEvRecord(dict), IEvString(sIndex)] => switch Belt.Map.String.get(dict, sIndex) { | Some(value) => value - | None => - RERecordPropertyNotFound("Record property not found", sIndex)->ErrorException->raise + | None => RERecordPropertyNotFound("Record property not found", sIndex)->throwMessage } - | _ => REOther("Trying to access key on wrong value")->ErrorException->raise + | _ => REOther("Trying to access key on wrong value")->throwMessage } })->Reducer_T.IEvLambda, ) @@ -49,7 +48,7 @@ let stdLib: Reducer_T.namespace = { Reducer_Expression_Lambda.makeFFILambda((arguments, environment, reducer) => { switch FunctionRegistry_Library.call(name, arguments, environment, reducer) { | Ok(value) => value - | Error(error) => error->Reducer_ErrorValue.ErrorException->raise + | Error(error) => error->SqError.Message.toException } })->Reducer_T.IEvLambda, ) From 845d38e375a2b1440e1f6a7025e121c2bc512086 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Mon, 26 Sep 2022 16:27:45 +0400 Subject: [PATCH 04/18] top-level SqError; expose stacktrace in TS API --- .../src/components/SquiggleErrorAlert.tsx | 28 ++++- .../Reducer_Peggy_TestHelpers.res | 2 +- packages/squiggle-lang/scripts/lib.mjs | 2 +- packages/squiggle-lang/src/js/SqError.ts | 16 ++- packages/squiggle-lang/src/js/SqProject.ts | 7 +- packages/squiggle-lang/src/js/index.ts | 2 +- .../rescript/ForTS/ForTS_ReducerProject.res | 4 +- .../src/rescript/ForTS/ForTS_SqError.res | 19 ---- .../ForTS_SquiggleValue.res | 2 +- .../src/rescript/ForTS/ForTS__Types.res | 2 - .../Reducer_Expression/Reducer_Expression.res | 16 +-- .../Reducer_Peggy/Reducer_Peggy_Parse.res | 2 - .../src/rescript/Reducer/Reducer_Value.res | 6 +- .../ReducerProject/ReducerProject.res | 4 +- .../ReducerProject_ProjectItem.res | 8 +- .../ReducerProject_ProjectItem_T.res | 2 +- .../squiggle-lang/src/rescript/SqError.res | 105 +++++++++++------- 17 files changed, 128 insertions(+), 99 deletions(-) delete mode 100644 packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index d03e237f..e9dd4245 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -1,4 +1,4 @@ -import { SqError } from "@quri/squiggle-lang"; +import { SqError, SqLocation } from "@quri/squiggle-lang"; import React from "react"; import { ErrorAlert } from "./Alert"; @@ -6,10 +6,34 @@ type Props = { error: SqError; }; +const StackTraceLocation: React.FC<{ location: SqLocation }> = ({ + location, +}) => { + return ( +
+ Line {location.start.line}, column {location.start.column} +
+ ); +}; + +const StackTrace: React.FC = ({ error }) => { + return ( +
+ {error.toLocationArray().map((location, i) => ( + + ))} +
+ ); +}; + export const SquiggleErrorAlert: React.FC = ({ error }) => { return ( -
{error.toString()}
+
{error.toString()}
+
Traceback:
+
+ +
); }; diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res index e30d3274..3e5abceb 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res @@ -22,7 +22,7 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => { } else { let a2 = rExpr - ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.Error.fromMessage) + ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.fromMessage) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) diff --git a/packages/squiggle-lang/scripts/lib.mjs b/packages/squiggle-lang/scripts/lib.mjs index 6f778769..5fc91873 100644 --- a/packages/squiggle-lang/scripts/lib.mjs +++ b/packages/squiggle-lang/scripts/lib.mjs @@ -36,6 +36,6 @@ export const run = (src, { output, sampleCount } = {}) => { "Time:", String(time), result.tag === "Error" ? red(result.tag) : green(result.tag), - result.tag === "Error" ? result.value.toString() : "" + result.tag === "Error" ? result.value.toStringWithStackTrace() : "" ); }; diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index ea21dc11..5025e671 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -1,13 +1,25 @@ -import * as RSError from "../rescript/ForTS/ForTS_SqError.gen"; +import * as RSError from "../rescript/SqError.gen"; + +export type SqLocation = RSError.location; export class SqError { - constructor(private _value: RSError.error) {} + constructor(private _value: RSError.t) {} toString() { return RSError.toString(this._value); } + toStringWithStackTrace() { + return RSError.toStringWithStackTrace(this._value); + } + static createOtherError(v: string) { return new SqError(RSError.createOtherError(v)); } + + toLocationArray() { + const stackTrace = RSError.getStackTrace(this._value); + + return stackTrace ? RSError.StackTrace.toLocationArray(stackTrace) : []; + } } diff --git a/packages/squiggle-lang/src/js/SqProject.ts b/packages/squiggle-lang/src/js/SqProject.ts index 30d8b245..b00b8927 100644 --- a/packages/squiggle-lang/src/js/SqProject.ts +++ b/packages/squiggle-lang/src/js/SqProject.ts @@ -1,12 +1,11 @@ import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; -import * as RSError from "../rescript/ForTS/ForTS_SqError.gen"; +import * as RSError from "../rescript/SqError.gen"; import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen"; import { SqError } from "./SqError"; import { SqRecord } from "./SqRecord"; import { wrapValue } from "./SqValue"; import { resultMap2 } from "./types"; import { SqValueLocation } from "./SqValueLocation"; -import { errorFromMessage } from "../rescript/ForTS/ForTS_SqError.gen"; export class SqProject { constructor(private _value: RSProject.reducerProject) {} @@ -51,7 +50,7 @@ export class SqProject { return resultMap2( RSProject.getIncludes(this._value, sourceId), (a) => a, - (v: RSError.errorMessage) => new SqError(errorFromMessage(v)) + (v: RSError.Message_t) => new SqError(RSError.fromMessage(v)) ); } @@ -105,7 +104,7 @@ export class SqProject { items: [], }) ), - (v: RSError.error) => new SqError(v) + (v: RSError.t) => new SqError(v) ); } diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index aac26bd5..82e12103 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 } from "./SqError"; +export { SqError, SqLocation } from "./SqError"; export { SqShape } from "./SqPointSetDist"; export { resultMap } from "./types"; diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res index 93581b15..ff2ea2ce 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res @@ -1,7 +1,7 @@ @genType type reducerProject = ReducerProject_T.project //re-export -type error = ForTS_SqError.error //use -type errorMessage = ForTS_SqError.errorMessage //use +type error = SqError.t //use +type errorMessage = SqError.Message.t //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res deleted file mode 100644 index c6a4b131..00000000 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SqError.res +++ /dev/null @@ -1,19 +0,0 @@ -@genType type error = SqError.Error.t //alias -@genType type errorMessage = SqError.Message.t //alias -@genType type location = Reducer_Peggy_Parse.location //alias - -@genType -let toString = (e: error): string => SqError.Error.toString(e) - -@genType -let getLocation = (e: error): option => - switch e.stackTrace { - | Some(stack) => Some(stack.location) - | None => None - } - -@genType -let createOtherError = (v: string): error => SqError.Message.REOther(v)->SqError.Error.fromMessage - -@genType -let errorFromMessage = (v: errorMessage): error => v->SqError.Error.fromMessage diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res index 2a8e8e1c..d116b74b 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res @@ -1,5 +1,5 @@ @genType type squiggleValue = Reducer_T.value //re-export -type error = ForTS_SqError.error //use +type error = SqError.t //use @genType type squiggleValue_Array = Reducer_T.arrayValue //re-export recursive type @genType type squiggleValue_Record = Reducer_T.map //re-export recursive type diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res index cd6562b9..b2bb9d98 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res @@ -1,5 +1,3 @@ -@genType type location = ForTS_SqError.location //re-export - @genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export @genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export 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 d0060982..d33d7ba3 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 @@ -8,7 +8,7 @@ let toLocation = (expression: T.expression): SqError.location => { } let throwFrom = (error: SqError.Message.t, expression: T.expression) => - error->SqError.Error.fromMessageWithLocation(expression->toLocation)->SqError.Error.throw + error->SqError.fromMessageWithLocation(expression->toLocation)->SqError.throw /* Recursively evaluate the expression @@ -108,11 +108,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { let result = Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate) (result, context) } catch { - | exn => - exn - ->SqError.Error.fromException - ->SqError.Error.extend(expression->toLocation) - ->SqError.Error.throw + | exn => exn->SqError.fromException->SqError.extend(expression->toLocation)->SqError.throw } | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression) } @@ -126,18 +122,18 @@ module BackCompatible = { let parse = (peggyCode: string): result => peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) - let evaluate = (expression: T.expression): result => { + let evaluate = (expression: T.expression): result => { let context = Reducer_Context.createDefaultContext() try { let (value, _) = expression->evaluate(context) value->Ok } catch { - | exn => exn->SqError.Error.fromException->Error + | exn => exn->SqError.fromException->Error } } - let evaluateString = (peggyCode: string): result => + let evaluateString = (peggyCode: string): result => parse(peggyCode) - ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.Error.fromMessage) + ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.fromMessage) ->Result.flatMap(evaluate) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res index 4a0a77da..b2b57bed 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res @@ -1,7 +1,5 @@ module Extra = Reducer_Extra -// Do not gentype this, use LocationRange from peggy types instead -// TODO - rename locationPoint -> location, location -> locationRange to match peggy @genType type locationPoint = { line: int, diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res index ea0931f2..7abecb92 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res @@ -72,13 +72,13 @@ let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)}) let toStringResult = x => switch x { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${SqError.Error.toString(m)})` + | Error(m) => `Error(${SqError.toString(m)})` } -let toStringResultOkless = (codeResult: result): string => +let toStringResultOkless = (codeResult: result): string => switch codeResult { | Ok(a) => toString(a) - | Error(m) => `Error(${SqError.Error.toString(m)})` + | Error(m) => `Error(${SqError.toString(m)})` } type internalExpressionValueType = diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res index 8ead6e32..6655f062 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res @@ -116,7 +116,7 @@ let getResultOption = (project: t, sourceId: string): ProjectItem.T.resultType = let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType => switch getResultOption(project, sourceId) { - | None => RENeedToRun->SqError.Error.fromMessage->Error + | None => RENeedToRun->SqError.fromMessage->Error | Some(result) => result } @@ -170,7 +170,7 @@ let linkDependencies = (project: t, sourceId: string): Reducer_T.namespace => { "__result__", switch project->getResult(id) { | Ok(result) => result - | Error(error) => error->SqError.Error.throw + | Error(error) => error->SqError.throw }, ), ]) diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 07ecac88..2211f004 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res @@ -174,7 +174,7 @@ let buildExpression = (this: t): t => { } } -let failRun = (this: t, e: SqError.Error.t): t => +let failRun = (this: t, e: SqError.t): t => this->setResult(e->Error)->setContinuation(Reducer_Namespace.make()) let doRun = (this: t, context: Reducer_T.context): t => @@ -188,11 +188,11 @@ let doRun = (this: t, context: Reducer_T.context): t => ->setResult(result->Ok) ->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals) } catch { - | e => this->failRun(e->SqError.Error.fromException) + | e => this->failRun(e->SqError.fromException) } - | Error(e) => this->failRun(e->SqError.Error.fromMessage) + | Error(e) => this->failRun(e->SqError.fromMessage) } - | None => this->failRun(RETodo("attempt to run without expression")->SqError.Error.fromMessage) + | None => this->failRun(RETodo("attempt to run without expression")->SqError.fromMessage) } let run = (this: t, context: Reducer_T.context): t => { diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res index 8ea3a814..9ef83536 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -10,7 +10,7 @@ type expressionType = option type continuationArgumentType = Reducer_T.namespace type continuationType = option type continuationResultType = option> -type resultArgumentType = result +type resultArgumentType = result type resultType = option type continuesArgumentType = array type continuesType = array diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res index 15f72d54..2d04fcaf 100644 --- a/packages/squiggle-lang/src/rescript/SqError.res +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -86,6 +86,7 @@ module Message = { } module StackTrace = { + @genType.opaque type rec t = { location: location, parent: option, @@ -98,48 +99,68 @@ module StackTrace = { | None => "" } } -} -module Error = { - @genType.opaque - type t = { - message: Message.t, - stackTrace: option, - } - - exception SqException(t) - - let toString = (err: t) => err.message->Message.toString - - let toStringWithStackTrace = (err: t) => - switch err.stackTrace { - | Some(stack) => "Traceback:\n" ++ stack->StackTrace.toString - | None => "" - } ++ - err->toString - - let fromMessage = (errorMessage: Message.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) => { - message: message, - stackTrace: Some({location: location, parent: stackTrace}), - } - - let throw = (t: t) => t->SqException->raise - - let fromException = exn => - switch exn { - | SqException(e) => e - | Message.MessageException(e) => e->fromMessage - | Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage - | _ => REOther("Unknown exception")->fromMessage + 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 } + +@genType.opaque +type t = { + message: Message.t, + stackTrace: option, +} + +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) => { + message: message, + stackTrace: Some({location: location, parent: stackTrace}), +} + +@genType +let getLocation = (t: t): option => t.stackTrace->E.O2.fmap(stack => stack.location) + +@genType +let getStackTrace = (t: t): option => t.stackTrace + +@genType +let toString = (t: t): string => t.message->Message.toString + +@genType +let createOtherError = (v: string): t => Message.REOther(v)->fromMessage + +@genType +let toStringWithStackTrace = (t: t) => + switch t.stackTrace { + | Some(stack) => "Traceback:\n" ++ stack->StackTrace.toString + | None => "" + } ++ + t->toString + +let throw = (t: t) => t->SqException->raise + +let fromException = exn => + switch exn { + | SqException(e) => e + | Message.MessageException(e) => e->fromMessage + | Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage + | _ => REOther("Unknown exception")->fromMessage + } From 4c56b2fd07a7451cb80811ecf082999ba5a12875 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Tue, 27 Sep 2022 02:29:00 +0400 Subject: [PATCH 05/18] implement error markers in editor --- .../components/src/components/CodeEditor.tsx | 17 +++- .../src/components/SquiggleChart.tsx | 81 +++++++++++-------- .../src/components/SquiggleEditor.tsx | 59 ++++++++++++-- .../src/components/SquiggleErrorAlert.tsx | 19 +++-- .../src/components/SquigglePlayground.tsx | 49 ++++++++--- .../components/src/lib/hooks/useSquiggle.ts | 16 +++- packages/components/src/lib/utility.ts | 18 ++++- packages/components/src/styles/main.css | 5 ++ packages/squiggle-lang/src/js/SqError.ts | 4 + .../Reducer_Expression/Reducer_Expression.res | 2 +- 10 files changed, 207 insertions(+), 63 deletions(-) diff --git a/packages/components/src/components/CodeEditor.tsx b/packages/components/src/components/CodeEditor.tsx index 15802131..ec2fdee7 100644 --- a/packages/components/src/components/CodeEditor.tsx +++ b/packages/components/src/components/CodeEditor.tsx @@ -5,6 +5,8 @@ import AceEditor from "react-ace"; import "ace-builds/src-noconflict/mode-golang"; import "ace-builds/src-noconflict/theme-github"; +import { SqLocation } from "@quri/squiggle-lang"; + interface CodeEditorProps { value: string; onChange: (value: string) => void; @@ -13,15 +15,17 @@ interface CodeEditorProps { width?: number; height: number; showGutter?: boolean; + errorLocations?: SqLocation[]; } export const CodeEditor: FC = ({ value, onChange, onSubmit, + height, oneLine = false, showGutter = false, - height, + errorLocations = [], }) => { const lineCount = value.split("\n").length; const id = useMemo(() => _.uniqueId(), []); @@ -30,8 +34,11 @@ export const CodeEditor: FC = ({ const onSubmitRef = useRef(null); onSubmitRef.current = onSubmit; + const editorEl = useRef(null); + return ( = ({ exec: () => onSubmitRef.current?.(), }, ]} + markers={errorLocations?.map((location) => ({ + startRow: location.start.line - 1, + startCol: location.start.column - 1, + endRow: location.end.line - 1, + endCol: location.end.column - 1, + className: "ace-error-marker", + type: "text", + }))} /> ); }; diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 4dd57a1f..2e126d4c 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -9,6 +9,7 @@ import { import { useSquiggle } from "../lib/hooks"; import { SquiggleViewer } from "./SquiggleViewer"; import { JsImports } from "../lib/jsImports"; +import { getValueToRender } from "../lib/utility"; export interface SquiggleChartProps { /** The input string for squiggle */ @@ -58,16 +59,9 @@ export interface SquiggleChartProps { const defaultOnChange = () => {}; const defaultImports: JsImports = {}; -export const SquiggleChart: React.FC = React.memo( - ({ - code = "", - executionId = 0, - environment, - onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here - height = 200, - jsImports = defaultImports, +export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { + const { showSummary = false, - width, logX = false, expY = false, diagramStart = 0, @@ -80,9 +74,47 @@ export const SquiggleChart: React.FC = React.memo( title, xAxisType = "number", distributionChartActions, - enableLocalSettings = false, - }) => { - const { result, bindings } = useSquiggle({ + } = props; + + const distributionPlotSettings = { + showSummary, + logX, + expY, + format: tickFormat, + minX, + maxX, + color, + title, + xAxisType, + actions: distributionChartActions, + }; + + const chartSettings = { + start: diagramStart, + stop: diagramStop, + count: diagramCount, + }; + + return { distributionPlotSettings, chartSettings }; +}; + +export const SquiggleChart: React.FC = React.memo( + (props) => { + const { distributionPlotSettings, chartSettings } = + splitSquiggleChartSettings(props); + + const { + code = "", + environment, + jsImports = defaultImports, + onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here + executionId = 0, + width, + height = 200, + enableLocalSettings = false, + } = props; + + const resultAndBindings = useSquiggle({ code, environment, jsImports, @@ -90,32 +122,11 @@ export const SquiggleChart: React.FC = React.memo( executionId, }); - const distributionPlotSettings = { - showSummary, - logX, - expY, - format: tickFormat, - minX, - maxX, - color, - title, - xAxisType, - actions: distributionChartActions, - }; - - const chartSettings = { - start: diagramStart, - stop: diagramStop, - count: diagramCount, - }; - - const resultToRender = resultMap(result, (value) => - value.tag === SqValueTag.Void ? bindings.asValue() : value - ); + const valueToRender = getValueToRender(resultAndBindings); return ( void; -}> = ({ code, setCode }) => ( + errorLocations?: SqLocation[]; +}> = ({ code, setCode, errorLocations }) => (
); @@ -24,6 +33,9 @@ export type SquiggleEditorProps = SquiggleChartProps & { onCodeChange?: (code: string) => void; }; +const defaultOnChange = () => {}; +const defaultImports: JsImports = {}; + export const SquiggleEditor: React.FC = (props) => { const [code, setCode] = useMaybeControlledValue({ value: props.code, @@ -31,11 +43,46 @@ export const SquiggleEditor: React.FC = (props) => { onChange: props.onCodeChange, }); - let chartProps = { ...props, code }; + const { distributionPlotSettings, chartSettings } = + splitSquiggleChartSettings(props); + + const { + environment, + jsImports = defaultImports, + onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here + executionId = 0, + width, + height = 200, + enableLocalSettings = false, + } = props; + + const resultAndBindings = useSquiggle({ + code, + environment, + jsImports, + onChange, + executionId, + }); + + const valueToRender = getValueToRender(resultAndBindings); + const errorLocations = getErrorLocations(resultAndBindings.result); + return ( - - + + ); }; diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index e9dd4245..fb4721eb 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -17,21 +17,24 @@ const StackTraceLocation: React.FC<{ location: SqLocation }> = ({ }; const StackTrace: React.FC = ({ error }) => { - return ( + const locations = error.toLocationArray(); + return locations.length ? (
- {error.toLocationArray().map((location, i) => ( - - ))} +
Traceback:
+
+ {locations.map((location, i) => ( + + ))} +
- ); + ) : null; }; export const SquiggleErrorAlert: React.FC = ({ error }) => { return ( -
{error.toString()}
-
Traceback:
-
+
+
{error.toString()}
diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index 8039bbe0..de467918 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -8,7 +8,11 @@ import React, { } from "react"; import { useForm, UseFormRegister, useWatch } from "react-hook-form"; import * as yup from "yup"; -import { useMaybeControlledValue, useRunnerState } from "../lib/hooks"; +import { + useMaybeControlledValue, + useRunnerState, + useSquiggle, +} from "../lib/hooks"; import { yupResolver } from "@hookform/resolvers/yup"; import { ChartSquareBarIcon, @@ -26,7 +30,7 @@ import clsx from "clsx"; import { environment } from "@quri/squiggle-lang"; -import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; +import { SquiggleChartProps } from "./SquiggleChart"; import { CodeEditor } from "./CodeEditor"; import { JsonEditor } from "./JsonEditor"; import { ErrorAlert, SuccessAlert } from "./Alert"; @@ -40,6 +44,8 @@ import { HeadedSection } from "./ui/HeadedSection"; import { defaultTickFormat } from "../lib/distributionSpecBuilder"; import { Button } from "./ui/Button"; import { JsImports } from "../lib/jsImports"; +import { getErrorLocations, getValueToRender } from "../lib/utility"; +import { SquiggleViewer } from "./SquiggleViewer"; type PlaygroundProps = SquiggleChartProps & { /** The initial squiggle string to put in the playground */ @@ -282,7 +288,7 @@ export const SquigglePlayground: FC = ({ onSettingsChange?.(vars); }, [vars, onSettingsChange]); - const env: environment = useMemo( + const environment: environment = useMemo( () => ({ sampleCount: Number(vars.sampleCount), xyPointLength: Number(vars.xyPointLength), @@ -299,26 +305,51 @@ export const SquigglePlayground: FC = ({ executionId, } = useRunnerState(code); + const resultAndBindings = useSquiggle({ + code, + environment, + jsImports: imports, + executionId, + }); + + const valueToRender = getValueToRender(resultAndBindings); + const squiggleChart = renderedCode === "" ? null : (
{isRunning ? (
) : null} -
); + const errorLocations = getErrorLocations(resultAndBindings.result); + const firstTab = vars.showEditor ? (
void; }; -export const useSquiggle = (args: SquiggleArgs) => { +export type ResultAndBindings = { + result: result; + bindings: SqRecord; +}; + +export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => { const result = useMemo( () => { const project = SqProject.create(); diff --git a/packages/components/src/lib/utility.ts b/packages/components/src/lib/utility.ts index d4d3661b..c3ce08a4 100644 --- a/packages/components/src/lib/utility.ts +++ b/packages/components/src/lib/utility.ts @@ -1,4 +1,5 @@ -import { result } from "@quri/squiggle-lang"; +import { result, resultMap, SqValueTag } from "@quri/squiggle-lang"; +import { ResultAndBindings } from "./hooks/useSquiggle"; export function flattenResult(x: result[]): result { if (x.length === 0) { @@ -35,3 +36,18 @@ export function all(arr: boolean[]): boolean { export function some(arr: boolean[]): boolean { return arr.reduce((x, y) => x || y, false); } + +export function getValueToRender({ result, bindings }: ResultAndBindings) { + return resultMap(result, (value) => + value.tag === SqValueTag.Void ? bindings.asValue() : value + ); +} + +export function getErrorLocations(result: ResultAndBindings["result"]) { + if (result.tag === "Error") { + const location = result.value.toLocation(); + return location ? [location] : []; + } else { + return []; + } +} diff --git a/packages/components/src/styles/main.css b/packages/components/src/styles/main.css index 987c3714..257c5712 100644 --- a/packages/components/src/styles/main.css +++ b/packages/components/src/styles/main.css @@ -22,3 +22,8 @@ but this line is still necessary for proper initialization of `--tw-*` variables .ace_cursor { border-left: 2px solid !important; } + +.ace-error-marker { + position: absolute; + border-bottom: 1px solid red; +} diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index 5025e671..5e599e51 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -22,4 +22,8 @@ export class SqError { return stackTrace ? RSError.StackTrace.toLocationArray(stackTrace) : []; } + + toLocation() { + return RSError.getLocation(this._value); + } } 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 d33d7ba3..10e026a3 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 @@ -98,7 +98,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { | T.ECall(fn, args) => { let (lambda, _) = fn->evaluate(context) - let argValues = Js.Array2.map(args, arg => { + let argValues = Belt.Array.map(args, arg => { let (argValue, _) = arg->evaluate(context) argValue }) From 184584c9f3032f3ba83ac84ba5c1803f33e7bd71 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Thu, 29 Sep 2022 19:48:31 +0400 Subject: [PATCH 06/18] 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, ) From a764f3075c65115428f3de1efe67d8dcfce54e15 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Sun, 2 Oct 2022 14:04:45 +0400 Subject: [PATCH 07/18] WIP (broken) --- .../rescript/Reducer/Reducer_CallStack.res | 36 ---------- .../Reducer_Expression/Reducer_Expression.res | 15 ++-- .../rescript/Reducer/Reducer_FrameStack.res | 40 +++++++++++ .../src/rescript/Reducer/Reducer_Lambda.res | 14 +--- .../src/rescript/Reducer/Reducer_T.res | 10 ++- .../squiggle-lang/src/rescript/SqError.res | 71 +++++++------------ 6 files changed, 85 insertions(+), 101 deletions(-) delete mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res create mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res deleted file mode 100644 index 520ce6eb..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_CallStack.res +++ /dev/null @@ -1,36 +0,0 @@ -@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_Expression/Reducer_Expression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression.res index 030ca71d..f334335a 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 @@ -6,8 +6,11 @@ let toLocation = (expression: T.expression): SqError.location => { expression.ast.location } -let throwFrom = (error: SqError.Message.t, context: T.context) => - error->SqError.throwMessage(context) +let throwFrom = (error: SqError.Message.t, expression: T.expression, context: T.context) => + error->SqError.throwMessage( + context.callStack, + location: Some(expression->toLocation) + ) /* Recursively evaluate the expression @@ -53,7 +56,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(context) + | _ => REOther("Record keys must be strings")->throwFrom(expression, context) } let (value, _) = eValue->evaluate(context) (keyString, value) @@ -77,7 +80,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(context) + | None => RESymbolNotFound(name)->throwFrom(expression, context) } | T.EValue(value) => (value, context) @@ -86,7 +89,7 @@ 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(context) + | _ => REExpectedType("Boolean", "")->throwFrom(expression, context) } } @@ -112,7 +115,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { let result = Reducer_Lambda.doLambdaCall(lambda, argValues, context, evaluate) (result, context) } - | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(context) + | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression, context) } } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res new file mode 100644 index 00000000..217993f0 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -0,0 +1,40 @@ +type t = Reducer_T.frameStack + +module Frame = { + let toString = ({lambda, location}: Reducer_T.frame) => + `${fromFrame} at ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` +} + +let make = (): t => list{} + +let topFrameName = (t: t) => + switch t->getTopFrame { + | Some({lambda}) => + switch lambda { + | FnLambda({name}) => name + | FnBuiltin({name}) => name + } + | None => "
" + } + +let extend = (t: t, lambda: Reducer_T.lambdaValue, location: option) => + t->Belt.List.add({ + lambda: lambda, + fromLocation: location, + fromFrame: t->topFrameName, + }) + +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_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res index 81e30cfb..04adeea0 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res @@ -57,14 +57,6 @@ let makeFFILambda = (name: string, body: Reducer_T.lambdaBody): t => FnBuiltin({ 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 { @@ -76,13 +68,13 @@ let parameters = (t: t): array => { let doLambdaCall = (t: t, args, context: Reducer_Context.t, reducer) => { let newContext = { ...context, - callStack: t->extendCallStack(context.callStack), + callStack: t->Reducer_CallStack.extend(t), } - SqError.contextualizeAndRethrow(() => { + SqError.rethrowWithStacktrace(() => { switch t { | FnLambda({body}) => body(args, newContext, reducer) | FnBuiltin({body}) => body(args, newContext, reducer) } - }, newContext) + }, newContext.callStack) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res index cb0edfd2..eab7bd55 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -50,10 +50,18 @@ and bindings = { parent: option, } +and frame = { + name: string, + location: option, // can be empty for calls from builtin functions +} + +and frameStack = list + and context = { bindings: bindings, environment: environment, - callStack: Reducer_CallStack.t, + inFunction: option, // used to build the next frame in frameStack + callStack: frameStack, } and reducerFn = (expression, context) => (value, context) diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res index 3cf3dfe2..461e1feb 100644 --- a/packages/squiggle-lang/src/rescript/SqError.res +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -90,91 +90,68 @@ module Message = { @genType.opaque type t = { message: Message.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, - stackTrace: Reducer_CallStack.t, + frameStack: Reducer_FrameStack.t, } exception SqException(t) // `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 => { +let fromMessage = (message: Message.t, frameStack: Reducer_FrameStack.t): t => { message: message, - location: location, - stackTrace: switch context { - | Some(context) => context.callStack - | None => Reducer_CallStack.make() - }, + stackTrace: stackTrace, } +let fromMessageWithoutFrameStack = (message: Message.t) => + fromMessage(message, Reducer_FrameStack.make()) + @genType let getTopFrame = (t: t): option => - t.stackTrace->Reducer_CallStack.getTopFrame + t.stackTrace->Reducer_FrameStack.getTopFrame @genType -let getStackTrace = (t: t): Reducer_CallStack.t => t.stackTrace +let getFrameStack = (t: t): Reducer_FrameStack.t => t.frameStack @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)->fromMessageWithoutFrameStack @genType let getFrameArray = (t: t): array => t.stackTrace->Reducer_CallStack.toFrameArray @genType -let toStringWithStackTrace = (t: t) => - if t.stackTrace->Reducer_CallStack.isEmpty { - "Traceback:\n" ++ t.stackTrace->Reducer_CallStack.toString - } else { - "" - } ++ - t->toString - +let toStringWithStacktrace = (t: t) => + t->toString ++ if t.frameStack->Reducer_FrameStack.isEmpty { + "Traceback:\n" ++ t.frameStack->Reducer_FrameStack.toString + } let throw = (t: t) => t->SqException->raise -let throwMessage = (message: Message.t, context: Reducer_T.context, location: location) => - fromMessage(message, context, location)->throw +let throwMessage = (message: Message.t, frameStack: Reducer_FrameStack.t) => + fromMessage(message, frameStack)->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 - | Message.MessageException(e) => e->fromMessage - | Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage - | _ => REOther("Unknown exception")->fromMessage + | Message.MessageException(e) => e->fromMessage(Reducer_CallStack.make()) + | Js.Exn.Error(obj) => + REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessageWithoutStacktrace + | _ => REOther("Unknown exception")->fromMessageWithoutStacktrace } // converts raw exceptions into exceptions with stacktrace attached // already converted exceptions won't be affected -let contextualizeAndRethrow = (fn: unit => 'a, context: Reducer_T.context) => { +let rethrowWithStacktrace = (fn: unit => 'a, stackTrace: Reducer_CallStack.t) => { try { fn() } catch { - | SqException(e) => e->throw - | Message.MessageException(e) => e->throwMessage(context) + | SqException(e) => e->throw // exception already has a stacktrace + | Message.MessageException(e) => e->throwMessage(stackTrace) // probably comes from FunctionRegistry, adding stacktrace | Js.Exn.Error(obj) => - REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessage(context) - | _ => REOther("Unknown exception")->throwMessage(context) + REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessage(stackTrace) + | _ => REOther("Unknown exception")->throwMessage(stackTrace) } } From 26dbd29ec8eecf495f2576e6f6266713d2059279 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 04:51:23 +0400 Subject: [PATCH 08/18] framestack reimplemented --- .../src/components/SquiggleErrorAlert.tsx | 20 ++++---- packages/components/src/lib/utility.ts | 2 +- packages/squiggle-lang/src/js/SqError.ts | 33 +++++++++---- .../squiggle-lang/src/js/SqValueLocation.ts | 1 - packages/squiggle-lang/src/js/index.ts | 2 +- .../src/rescript/Reducer/Reducer_Context.res | 7 ++- .../Reducer_Expression/Reducer_Expression.res | 22 ++++++--- .../Reducer_Expression_ExpressionBuilder.res | 9 ++-- .../Reducer_Expression_T.res | 2 +- .../rescript/Reducer/Reducer_FrameStack.res | 42 +++++++++-------- .../src/rescript/Reducer/Reducer_Lambda.res | 25 ++++++++-- .../src/rescript/Reducer/Reducer_Lambda_T.res | 8 ++++ .../Reducer_Peggy_GeneratedParser.peggy | 6 +-- .../Reducer_Peggy/Reducer_Peggy_Parse.res | 2 +- .../Reducer_Peggy_ToExpression.res | 2 +- .../rescript/Reducer/Reducer_Peggy/helpers.ts | 6 ++- .../src/rescript/Reducer/Reducer_T.res | 10 ++-- .../squiggle-lang/src/rescript/SqError.res | 46 +++++++++---------- 18 files changed, 153 insertions(+), 92 deletions(-) create mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index fb4721eb..c4b14f17 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -1,4 +1,4 @@ -import { SqError, SqLocation } from "@quri/squiggle-lang"; +import { SqError, SqFrame } from "@quri/squiggle-lang"; import React from "react"; import { ErrorAlert } from "./Alert"; @@ -6,24 +6,26 @@ type Props = { error: SqError; }; -const StackTraceLocation: React.FC<{ location: SqLocation }> = ({ - location, -}) => { +const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => { + const location = frame.location(); return (
- Line {location.start.line}, column {location.start.column} + {frame.name()} + {location + ? ` at line ${location.start.line}, column ${location.start.column}` + : ""}
); }; const StackTrace: React.FC = ({ error }) => { - const locations = error.toLocationArray(); - return locations.length ? ( + const frames = error.getFrameArray(); + return frames.length ? (
Traceback:
- {locations.map((location, i) => ( - + {frames.map((frame, i) => ( + ))}
diff --git a/packages/components/src/lib/utility.ts b/packages/components/src/lib/utility.ts index c3ce08a4..cb002954 100644 --- a/packages/components/src/lib/utility.ts +++ b/packages/components/src/lib/utility.ts @@ -45,7 +45,7 @@ export function getValueToRender({ result, bindings }: ResultAndBindings) { export function getErrorLocations(result: ResultAndBindings["result"]) { if (result.tag === "Error") { - const location = result.value.toLocation(); + const location = result.value.location(); return location ? [location] : []; } else { return []; diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index 61d09096..59036830 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -1,8 +1,9 @@ import * as RSError from "../rescript/SqError.gen"; +import * as RSReducerT from "../rescript/Reducer/Reducer_T.gen"; -import * as RSCallStack from "../rescript/Reducer/Reducer_CallStack.gen"; +import * as RSFrameStack from "../rescript/Reducer/Reducer_FrameStack.gen"; -export type SqFrame = RSCallStack.frame; +export { location as SqLocation } from "../rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.gen"; export class SqError { constructor(private _value: RSError.t) {} @@ -19,17 +20,31 @@ export class SqError { return new SqError(RSError.createOtherError(v)); } - getTopFrame(): SqCallFrame | undefined { - const frame = RSCallStack.getTopFrame(RSError.getStackTrace(this._value)); - return frame ? new SqCallFrame(frame) : undefined; + getTopFrame(): SqFrame | undefined { + const frame = RSFrameStack.getTopFrame(RSError.getFrameStack(this._value)); + return frame ? new SqFrame(frame) : undefined; } - getFrameArray(): SqCallFrame[] { + getFrameArray(): SqFrame[] { const frames = RSError.getFrameArray(this._value); - return frames.map((frame) => new SqCallFrame(frame)); + return frames.map((frame) => new SqFrame(frame)); + } + + location() { + return this.getTopFrame()?.location(); } } -export class SqCallFrame { - constructor(private _value: SqFrame) {} +export class SqFrame { + constructor(private _value: RSReducerT.frame) {} + + name(): string { + return RSFrameStack.Frame.toName(this._value); + } + + location() { + console.log(RSFrameStack); + console.log(RSFrameStack.Frame); + return RSFrameStack.Frame.toLocation(this._value); + } } diff --git a/packages/squiggle-lang/src/js/SqValueLocation.ts b/packages/squiggle-lang/src/js/SqValueLocation.ts index 33c7060b..ad2e7e02 100644 --- a/packages/squiggle-lang/src/js/SqValueLocation.ts +++ b/packages/squiggle-lang/src/js/SqValueLocation.ts @@ -1,4 +1,3 @@ -import { isParenthesisNode } from "mathjs"; import { SqProject } from "./SqProject"; type PathItem = string | number; diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index aac26bd5..886c830a 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 } from "./SqError"; +export { SqError, SqFrame, SqLocation } from "./SqError"; export { SqShape } from "./SqPointSetDist"; export { resultMap } from "./types"; diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res index af596dc2..a2d98f92 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res @@ -4,8 +4,13 @@ let defaultEnvironment: Reducer_T.environment = DistributionOperation.defaultEnv let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environment): t => { { - callStack: list{}, + frameStack: list{}, bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend, environment: environment, + inFunction: None, } } + +let currentFunctionName = (t: t): string => { + t.inFunction->E.O2.fmap(Reducer_Lambda_T.name)->E.O2.default("") +} 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 f334335a..0036816b 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 @@ -2,14 +2,16 @@ module Bindings = Reducer_Bindings module Result = Belt.Result module T = Reducer_T -let toLocation = (expression: T.expression): SqError.location => { +let toLocation = (expression: T.expression): Reducer_Peggy_Parse.location => { expression.ast.location } let throwFrom = (error: SqError.Message.t, expression: T.expression, context: T.context) => - error->SqError.throwMessage( - context.callStack, - location: Some(expression->toLocation) + error->SqError.throwMessageWithFrameStack( + context.frameStack->Reducer_FrameStack.extend( + context->Reducer_Context.currentFunctionName, + Some(expression->toLocation), + ), ) /* @@ -93,9 +95,9 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { } } - | T.ELambda(parameters, body) => ( + | T.ELambda(parameters, body, name) => ( Reducer_Lambda.makeLambda( - None, // TODO - pass function name from parser + name, parameters, context.bindings, body, @@ -112,7 +114,13 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { }) switch lambda { | T.IEvLambda(lambda) => { - let result = Reducer_Lambda.doLambdaCall(lambda, argValues, context, evaluate) + let result = Reducer_Lambda.doLambdaCallFrom( + lambda, + argValues, + context, + evaluate, + Some(expression->toLocation), // we have to pass the location of a current expression here, to put it on frameStack + ) (result, context) } | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression, context) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res index 965fc499..527331fb 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res @@ -9,10 +9,11 @@ let eBool = aBool => aBool->T.IEvBool->T.EValue let eCall = (fn: expression, args: array): expressionContent => T.ECall(fn, args) -let eLambda = (parameters: array, expr: expression): expressionContent => T.ELambda( - parameters, - expr, -) +let eLambda = ( + parameters: array, + expr: expression, + name: option, +): expressionContent => T.ELambda(parameters, expr, name) let eNumber = aNumber => aNumber->T.IEvNumber->T.EValue diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index 6b2982f6..c7804c7a 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -24,7 +24,7 @@ let rec toString = (expression: t) => `${predicate->toString} ? (${trueCase->toString}) : (${falseCase->toString})` | EAssign(name, value) => `${name} = ${value->toString}` | ECall(fn, args) => `(${fn->toString})(${args->Js.Array2.map(toString)->commaJoin})` - | ELambda(parameters, body) => `{|${parameters->commaJoin}| ${body->toString}}` + | ELambda(parameters, body, _) => `{|${parameters->commaJoin}| ${body->toString}}` | EValue(aValue) => Reducer_Value.toString(aValue) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res index 217993f0..e61efd5d 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -1,37 +1,43 @@ type t = Reducer_T.frameStack module Frame = { - let toString = ({lambda, location}: Reducer_T.frame) => - `${fromFrame} at ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` + let toString = ({name, location}: Reducer_T.frame) => + name ++ + switch location { + | Some(location) => + ` at line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` + | None => "" + } + + @genType + let toLocation = (t: Reducer_T.frame): option => t.location + + @genType + let toName = (t: Reducer_T.frame): string => t.name } let make = (): t => list{} -let topFrameName = (t: t) => - switch t->getTopFrame { - | Some({lambda}) => - switch lambda { - | FnLambda({name}) => name - | FnBuiltin({name}) => name - } - | None => "
" - } +@genType +let getTopFrame = (t: t): option => Belt.List.head(t) -let extend = (t: t, lambda: Reducer_T.lambdaValue, location: option) => +let extend = (t: t, name: string, location: option) => t->Belt.List.add({ - lambda: lambda, - fromLocation: location, - fromFrame: t->topFrameName, + name: name, + location: location, }) let toString = (t: t) => - t->Belt.List.map(s => " " ++ s->toStringFrame ++ "\n")->Belt.List.toArray->Js.Array2.joinWith("") + t + ->Belt.List.map(s => " " ++ s->Frame.toString ++ "\n") + ->Belt.List.toArray + ->Js.Array2.joinWith("") @genType -let toFrameArray = (t: t): array => t->Belt.List.toArray +let toFrameArray = (t: t): array => t->Belt.List.toArray @genType -let getTopFrame = (t: t): option => t->Belt.List.head +let getTopFrame = (t: t): option => t->Belt.List.head let isEmpty = (t: t): bool => switch t->Belt.List.head { diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res index 04adeea0..98a272db 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res @@ -33,7 +33,8 @@ let makeLambda = ( 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 + frameStack: context.frameStack, // already extended in `doLambdaCall` + inFunction: context.inFunction, // already updated in `doLambdaCall` } let (value, _) = reducer(body, lambdaContext) @@ -49,7 +50,7 @@ let makeLambda = ( }) } -// stdlib lambdas (everything in FunctionRegistry) is built by this method. Body is generated in SquiggleLibrary_StdLib.res +// stdlib functions (everything in FunctionRegistry) are 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. @@ -65,10 +66,20 @@ let parameters = (t: t): array => { } } -let doLambdaCall = (t: t, args, context: Reducer_Context.t, reducer) => { +let doLambdaCallFrom = ( + t: t, + args: array, + context: Reducer_T.context, + reducer, + location: option, +) => { let newContext = { ...context, - callStack: t->Reducer_CallStack.extend(t), + frameStack: context.frameStack->Reducer_FrameStack.extend( + context->Reducer_Context.currentFunctionName, + location, + ), + inFunction: Some(t), } SqError.rethrowWithStacktrace(() => { @@ -76,5 +87,9 @@ let doLambdaCall = (t: t, args, context: Reducer_Context.t, reducer) => { | FnLambda({body}) => body(args, newContext, reducer) | FnBuiltin({body}) => body(args, newContext, reducer) } - }, newContext.callStack) + }, newContext.frameStack) +} + +let doLambdaCall = (t: t, args, context, reducer) => { + doLambdaCallFrom(t, args, context, reducer, None) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res new file mode 100644 index 00000000..ab0724b4 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res @@ -0,0 +1,8 @@ +type t = Reducer_T.lambdaValue + +let name = (t: t): string => { + switch t { + | FnLambda({name}) => name->E.O2.default("") + | FnBuiltin({name}) => name + } +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy index 0cd28a86..cbe1f9da 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy @@ -50,7 +50,7 @@ letStatement defunStatement = variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression - { var value = h.nodeLambda(args, body, location()) + { var value = h.nodeLambda(args, body, location(), variable) return h.nodeLetStatement(variable, value, location()) } assignmentOp "assignment" = '=' @@ -261,9 +261,9 @@ valueConstructor lambda = '{' _nl '|' _nl args:array_parameters _nl '|' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' { statements.push(finalExpression) - return h.nodeLambda(args, h.nodeBlock(statements, location()), location()) } + return h.nodeLambda(args, h.nodeBlock(statements, location()), location(), undefined) } / '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}' - { return h.nodeLambda(args, finalExpression, location()) } + { return h.nodeLambda(args, finalExpression, location(), undefined) } arrayConstructor 'array' = '[' _nl ']' diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res index b2b57bed..001f3e5b 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res @@ -44,7 +44,7 @@ type nodeIdentifier = {...node, "value": string} type nodeInteger = {...node, "value": int} type nodeKeyValue = {...node, "key": node, "value": node} type nodeRecord = {...node, "elements": array} -type nodeLambda = {...node, "args": array, "body": node} +type nodeLambda = {...node, "args": array, "body": node, "name": option} type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node} type nodeModuleIdentifier = {...node, "value": string} type nodeString = {...node, "value": string} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res index 6c187476..2e858c55 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res @@ -20,7 +20,7 @@ let rec fromNode = (node: Parse.node): expression => { nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) let body = nodeLambda["body"]->fromNode - ExpressionBuilder.eLambda(args, body) + ExpressionBuilder.eLambda(args, body, nodeLambda["name"]) } let caseRecord = (nodeRecord): expressionContent => { diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts index 6e9fe80c..6253b875 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts @@ -89,6 +89,7 @@ type NodeLambda = Node & { type: "Lambda"; args: AnyPeggyNode[]; body: AnyPeggyNode; + name?: string; }; type NodeTernary = Node & { @@ -217,9 +218,10 @@ export function nodeKeyValue( export function nodeLambda( args: AnyPeggyNode[], body: AnyPeggyNode, - location: LocationRange + location: LocationRange, + name?: NodeIdentifier ): NodeLambda { - return { type: "Lambda", args, body, location }; + return { type: "Lambda", args, body, location, name: name?.value }; } export function nodeLetStatement( variable: NodeIdentifier, diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res index eab7bd55..dcc9e4bf 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -36,7 +36,7 @@ and expressionContent = | ETernary(expression, expression, expression) | EAssign(string, expression) | ECall(expression, array) - | ELambda(array, expression) + | ELambda(array, expression, option) | EValue(value) and expression = { @@ -50,18 +50,18 @@ and bindings = { parent: option, } +@genType.opaque and frame = { name: string, location: option, // can be empty for calls from builtin functions } - -and frameStack = list +@genType.opaque and frameStack = list and context = { bindings: bindings, environment: environment, - inFunction: option, // used to build the next frame in frameStack - callStack: frameStack, + frameStack: frameStack, + inFunction: option, } and reducerFn = (expression, context) => (value, context) diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res index 461e1feb..29d98517 100644 --- a/packages/squiggle-lang/src/rescript/SqError.res +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -95,19 +95,19 @@ type t = { exception SqException(t) -// `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, frameStack: Reducer_FrameStack.t): t => { +let fromMessageWithFrameStack = (message: Message.t, frameStack: Reducer_FrameStack.t): t => { message: message, - stackTrace: stackTrace, + frameStack: frameStack, } -let fromMessageWithoutFrameStack = (message: Message.t) => - fromMessage(message, Reducer_FrameStack.make()) +// this shouldn't be used much, since frame stack will be empty +// but it's useful for global errors, e.g. in ReducerProject or somethere in the frontend +@genType +let fromMessage = (message: Message.t) => + fromMessageWithFrameStack(message, Reducer_FrameStack.make()) @genType -let getTopFrame = (t: t): option => - t.stackTrace->Reducer_FrameStack.getTopFrame +let getTopFrame = (t: t): option => t.frameStack->Reducer_FrameStack.getTopFrame @genType let getFrameStack = (t: t): Reducer_FrameStack.t => t.frameStack @@ -116,42 +116,42 @@ let getFrameStack = (t: t): Reducer_FrameStack.t => t.frameStack let toString = (t: t): string => t.message->Message.toString @genType -let createOtherError = (v: string): t => Message.REOther(v)->fromMessageWithoutFrameStack +let createOtherError = (v: string): t => Message.REOther(v)->fromMessage @genType -let getFrameArray = (t: t): array => - t.stackTrace->Reducer_CallStack.toFrameArray +let getFrameArray = (t: t): array => t.frameStack->Reducer_FrameStack.toFrameArray @genType -let toStringWithStacktrace = (t: t) => +let toStringWithStackTrace = (t: t) => t->toString ++ if t.frameStack->Reducer_FrameStack.isEmpty { - "Traceback:\n" ++ t.frameStack->Reducer_FrameStack.toString + "\nTraceback:\n" ++ t.frameStack->Reducer_FrameStack.toString + } else { + "" } let throw = (t: t) => t->SqException->raise -let throwMessage = (message: Message.t, frameStack: Reducer_FrameStack.t) => - fromMessage(message, frameStack)->throw +let throwMessageWithFrameStack = (message: Message.t, frameStack: Reducer_FrameStack.t) => + fromMessageWithFrameStack(message, frameStack)->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 - | Message.MessageException(e) => e->fromMessage(Reducer_CallStack.make()) - | Js.Exn.Error(obj) => - REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessageWithoutStacktrace - | _ => REOther("Unknown exception")->fromMessageWithoutStacktrace + | Message.MessageException(e) => e->fromMessage + | 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 rethrowWithStacktrace = (fn: unit => 'a, stackTrace: Reducer_CallStack.t) => { +let rethrowWithStacktrace = (fn: unit => 'a, frameStack: Reducer_FrameStack.t) => { try { fn() } catch { | SqException(e) => e->throw // exception already has a stacktrace - | Message.MessageException(e) => e->throwMessage(stackTrace) // probably comes from FunctionRegistry, adding stacktrace + | Message.MessageException(e) => e->throwMessageWithFrameStack(frameStack) // probably comes from FunctionRegistry, adding stacktrace | Js.Exn.Error(obj) => - REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessage(stackTrace) - | _ => REOther("Unknown exception")->throwMessage(stackTrace) + REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessageWithFrameStack(frameStack) + | _ => REOther("Unknown exception")->throwMessageWithFrameStack(frameStack) } } From 234ebe21031fbca07d26e6a57b30f76e749c297f Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 05:17:30 +0400 Subject: [PATCH 09/18] locations for syntax errors --- .../Reducer_Peggy/Reducer_Peggy_TestHelpers.res | 2 +- .../ReducerProject/ReducerProject_includes_test.res | 6 +++--- .../ReducerProject_tutorial_3_includes_test.res | 6 +++--- .../src/rescript/ForTS/ForTS_ReducerProject.res | 6 ++---- .../src/rescript/Reducer/Reducer_Context.res | 2 +- .../Reducer_Expression/Reducer_Expression.res | 4 +--- .../src/rescript/Reducer/Reducer_FrameStack.res | 3 +++ .../src/rescript/Reducer/Reducer_T.res | 2 ++ .../ReducerProject/ReducerProject_ParseIncludes.res | 11 ++++++----- .../ReducerProject/ReducerProject_ProjectItem.res | 7 ++----- .../ReducerProject/ReducerProject_ProjectItem_T.res | 6 +++--- packages/squiggle-lang/src/rescript/SqError.res | 13 +++++++------ 12 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res index 3e5abceb..1731168f 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res @@ -22,7 +22,7 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => { } else { let a2 = rExpr - ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.fromMessage) + ->E.R2.errMap(e => e->SqError.fromParseError) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res index 6c6d46fe..9c21ff1d 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res @@ -25,7 +25,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common"] - | Error(error) => fail(error->SqError.Message.toString) + | Error(error) => fail(error->SqError.toString) } }) test("past chain", () => { @@ -60,7 +60,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "myModule"] - | Error(error) => fail(error->SqError.Message.toString) + | Error(error) => fail(error->SqError.toString) } }) @@ -99,7 +99,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "common2", "myModule"] - | Error(error) => fail(error->SqError.Message.toString) + | Error(error) => fail(error->SqError.toString) } }) test("direct past chain", () => { diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res index bd06c481..5692d93c 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res @@ -36,7 +36,7 @@ Here we will finally proceed to a real life scenario. */ /* Parse includes has set the includes */ switch project->Project.getIncludes("main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->SqError.Message.toString->fail + | Error(err) => err->SqError.toString->fail } /* If the includes cannot be parsed then you get a syntax error. Otherwise you get the includes. @@ -85,7 +85,7 @@ Here we will finally proceed to a real life scenario. */ let rIncludes = project->Project.getIncludes(sourceName) switch rIncludes { /* Maybe there is an include syntax error */ - | Error(err) => err->SqError.Message.toString->Js.Exn.raiseError + | Error(err) => err->SqError.toString->Js.Exn.raiseError | Ok(includes) => includes->Belt.Array.forEach(newIncludeName => { @@ -169,7 +169,7 @@ Here we will finally proceed to a real life scenario. */ test("getIncludes", () => { switch Project.getIncludes(project, "main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->SqError.Message.toString->fail + | Error(err) => err->SqError.toString->fail } }) }) diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res index daa4e7a6..c8904e00 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res @@ -104,10 +104,8 @@ let cleanAllResults = (project: reducerProject): unit => project->Private.cleanA To set the includes one first has to call "parseIncludes". The parsed includes or the parser error is returned. */ @genType -let getIncludes = (project: reducerProject, sourceId: string): result< - array, - errorMessage, -> => project->Private.getIncludes(sourceId) +let getIncludes = (project: reducerProject, sourceId: string): result, error> => + project->Private.getIncludes(sourceId) /* Other sources contributing to the global namespace of this source. */ @genType diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res index a2d98f92..809ac514 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res @@ -12,5 +12,5 @@ let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environ } let currentFunctionName = (t: t): string => { - t.inFunction->E.O2.fmap(Reducer_Lambda_T.name)->E.O2.default("") + t.inFunction->E.O2.fmap(Reducer_Lambda_T.name)->E.O2.default(Reducer_T.topFrameName) } 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 0036816b..4906dd54 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 @@ -149,7 +149,5 @@ module BackCompatible = { } let evaluateString = (peggyCode: string): result => - parse(peggyCode) - ->E.R2.errMap(e => e->SqError.Message.fromParseError->SqError.fromMessage) - ->Result.flatMap(evaluate) + parse(peggyCode)->E.R2.errMap(e => e->SqError.fromParseError)->Result.flatMap(evaluate) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res index e61efd5d..46640439 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -27,6 +27,9 @@ let extend = (t: t, name: string, location: option location: location, }) +let makeSingleFrameStack = (location: Reducer_Peggy_Parse.location): t => + make()->extend(Reducer_T.topFrameName, Some(location)) + let toString = (t: t) => t ->Belt.List.map(s => " " ++ s->Frame.toString ++ "\n") diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res index dcc9e4bf..36025e40 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -65,3 +65,5 @@ and context = { } and reducerFn = (expression, context) => (value, context) + +let topFrameName = "" diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res index c0612be8..bab3fa88 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res @@ -1,7 +1,7 @@ @module("./ReducerProject_IncludeParser.js") external parse__: string => array> = "parse" -let parseIncludes = (expr: string): result, SqError.Message.t> => +let parseIncludes = (expr: string): result, SqError.t> => try { let answer = parse__(expr) // let logEntry = answer->Js.Array2.joinWith(",") @@ -9,8 +9,9 @@ let parseIncludes = (expr: string): result, SqError.Mess Belt.Array.map(answer, item => (item[0], item[1]))->Ok } catch { | Js.Exn.Error(obj) => - RESyntaxError( - Belt.Option.getExn(Js.Exn.message(obj)), - Reducer_Peggy_Parse.syntaxErrorToLocation(obj)->Some, - )->Error + RESyntaxError(Belt.Option.getExn(Js.Exn.message(obj))) + ->SqError.fromMessageWithFrameStack( + Reducer_FrameStack.makeSingleFrameStack(Reducer_Peggy_Parse.syntaxErrorToLocation(obj)), + ) + ->Error } diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 2211f004..3612c492 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res @@ -152,10 +152,7 @@ let parseIncludes = (this: t): t => { } } let doRawParse = (this: t): T.rawParseArgumentType => - this - ->getSource - ->Reducer_Peggy_Parse.parse(this.sourceId) - ->E.R2.errMap(SqError.Message.fromParseError) + this->getSource->Reducer_Peggy_Parse.parse(this.sourceId)->E.R2.errMap(SqError.fromParseError) let rawParse = (this: t): t => this->getRawParse->E.O2.defaultFn(() => doRawParse(this))->setRawParse(this, _) @@ -190,7 +187,7 @@ let doRun = (this: t, context: Reducer_T.context): t => } catch { | e => this->failRun(e->SqError.fromException) } - | Error(e) => this->failRun(e->SqError.fromMessage) + | Error(e) => this->failRun(e) } | None => this->failRun(RETodo("attempt to run without expression")->SqError.fromMessage) } diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res index 9ef83536..bcdbfa02 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -3,9 +3,9 @@ module ExpressionT = Reducer_Expression_T type sourceArgumentType = string type sourceType = string -type rawParseArgumentType = result +type rawParseArgumentType = result type rawParseType = option -type expressionArgumentType = result +type expressionArgumentType = result type expressionType = option type continuationArgumentType = Reducer_T.namespace type continuationType = option @@ -15,7 +15,7 @@ type resultType = option type continuesArgumentType = array type continuesType = array type includesArgumentType = string -type includesType = result, SqError.Message.t> +type includesType = result, SqError.t> type importAsVariablesType = array<(string, string)> type projectItem = { diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res index 29d98517..a9950020 100644 --- a/packages/squiggle-lang/src/rescript/SqError.res +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -19,7 +19,7 @@ module Message = { | REOperationError(Operation.operationError) | RERecordPropertyNotFound(string, string) | RESymbolNotFound(string) - | RESyntaxError(string, option) + | RESyntaxError(string) | RETodo(string) // To do | REUnitNotFound(string) | RENeedToRun @@ -27,10 +27,6 @@ module Message = { exception MessageException(t) - let fromParseError = ( - SyntaxError(message, location): Reducer_Peggy_Parse.parseError, - ) => RESyntaxError(message, location->Some) - let toString = (err: t) => switch err { | REArityError(_oFnName, arity, usedArity) => @@ -61,7 +57,7 @@ module Message = { | RENotAFunction(valueString) => `${valueString} is not a function` | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` | RESymbolNotFound(symbolName) => `${symbolName} is not defined` - | RESyntaxError(desc, _) => `Syntax Error: ${desc}` + | RESyntaxError(desc) => `Syntax Error: ${desc}` | RETodo(msg) => `TODO: ${msg}` | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}` | REUnitNotFound(unitName) => `Unit not found: ${unitName}` @@ -106,6 +102,11 @@ let fromMessageWithFrameStack = (message: Message.t, frameStack: Reducer_FrameSt let fromMessage = (message: Message.t) => fromMessageWithFrameStack(message, Reducer_FrameStack.make()) +let fromParseError = (SyntaxError(message, location): Reducer_Peggy_Parse.parseError) => + RESyntaxError(message)->fromMessageWithFrameStack( + Reducer_FrameStack.makeSingleFrameStack(location), + ) + @genType let getTopFrame = (t: t): option => t.frameStack->Reducer_FrameStack.getTopFrame From 9f1c5affc450055b2da8c4d885076e69e0138aa6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 05:21:39 +0400 Subject: [PATCH 10/18] ts fix --- packages/squiggle-lang/src/js/SqProject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/squiggle-lang/src/js/SqProject.ts b/packages/squiggle-lang/src/js/SqProject.ts index d1c8b3ef..ca65c7ec 100644 --- a/packages/squiggle-lang/src/js/SqProject.ts +++ b/packages/squiggle-lang/src/js/SqProject.ts @@ -54,7 +54,7 @@ export class SqProject { return resultMap2( RSProject.getIncludes(this._value, sourceId), (a) => a, - (v: RSError.Message_t) => new SqError(RSError.fromMessage(v)) + (v: RSError.t) => new SqError(v) ); } From a45d6c6c57e70de0624db96b902be1287a6aeeb6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 05:22:00 +0400 Subject: [PATCH 11/18] format --- .../Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res index be6444fd..55611d3a 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res @@ -182,7 +182,7 @@ describe("Peggy parse", () => { "a.p1 to a.p2", "{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}", ) // lower than post - testParse("1 to 2 + 3", "{(:credibleIntervalToDistribution 1 (:add 2 3))}") + testParse("1 to 2 + 3", "{(:credibleIntervalToDistribution 1 (:add 2 3))}") testParse( "1->add(2) to 3->add(4) -> add(4)", "{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}", From 6463c4db5a9c5529b55ecd07c6d68d16eb82ff7c Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 15:49:16 +0400 Subject: [PATCH 12/18] minor cleanups --- .../src/components/SquiggleErrorAlert.tsx | 2 +- packages/squiggle-lang/scripts/lib.mjs | 2 +- .../src/rescript/Reducer/Reducer_Exception.res | 3 --- .../src/rescript/Reducer/Reducer_FrameStack.res | 6 +++--- .../src/rescript/Reducer/Reducer_Lambda.res | 2 +- packages/squiggle-lang/src/rescript/SqError.res | 17 +++++++++-------- 6 files changed, 15 insertions(+), 17 deletions(-) delete mode 100644 packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index c4b14f17..fee0fca5 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -22,7 +22,7 @@ const StackTrace: React.FC = ({ error }) => { const frames = error.getFrameArray(); return frames.length ? (
-
Traceback:
+
Stack trace:
{frames.map((frame, i) => ( diff --git a/packages/squiggle-lang/scripts/lib.mjs b/packages/squiggle-lang/scripts/lib.mjs index 5fc91873..b51bcc7c 100644 --- a/packages/squiggle-lang/scripts/lib.mjs +++ b/packages/squiggle-lang/scripts/lib.mjs @@ -36,6 +36,6 @@ export const run = (src, { output, sampleCount } = {}) => { "Time:", String(time), result.tag === "Error" ? red(result.tag) : green(result.tag), - result.tag === "Error" ? result.value.toStringWithStackTrace() : "" + result.tag === "Error" ? result.value.toStringWithFrameStack() : "" ); }; diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res deleted file mode 100644 index 14db0843..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res +++ /dev/null @@ -1,3 +0,0 @@ -// There are switch statement cases in the code which are impossible to reach by design. -// ImpossibleException is a sign of programming error. -exception ImpossibleException(string) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res index 46640439..02bb1cad 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -1,3 +1,6 @@ +// This is called "frameStack" and not "callStack", because the last frame in errors is often not a function call. +// A "frame" is a pair of a scope (function or top-level scope, currently stored as a string) and a location inside it. +// See this comment to deconfuse about what a frame is: https://github.com/quantified-uncertainty/squiggle/pull/1172#issuecomment-1264115038 type t = Reducer_T.frameStack module Frame = { @@ -18,9 +21,6 @@ module Frame = { let make = (): t => list{} -@genType -let getTopFrame = (t: t): option => Belt.List.head(t) - let extend = (t: t, name: string, location: option) => t->Belt.List.add({ name: name, diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res index 98a272db..b814ea45 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res @@ -82,7 +82,7 @@ let doLambdaCallFrom = ( inFunction: Some(t), } - SqError.rethrowWithStacktrace(() => { + SqError.rethrowWithFrameStack(() => { switch t { | FnLambda({body}) => body(args, newContext, reducer) | FnBuiltin({body}) => body(args, newContext, reducer) diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res index a9950020..dd28fa4b 100644 --- a/packages/squiggle-lang/src/rescript/SqError.res +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -1,7 +1,8 @@ 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 +// 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 = @@ -125,16 +126,16 @@ let getFrameArray = (t: t): array => t.frameStack->Reducer_Fram @genType let toStringWithStackTrace = (t: t) => t->toString ++ if t.frameStack->Reducer_FrameStack.isEmpty { - "\nTraceback:\n" ++ t.frameStack->Reducer_FrameStack.toString + "\nStack trace:\n" ++ t.frameStack->Reducer_FrameStack.toString } else { "" } let throw = (t: t) => t->SqException->raise let throwMessageWithFrameStack = (message: Message.t, frameStack: Reducer_FrameStack.t) => - fromMessageWithFrameStack(message, frameStack)->throw + message->fromMessageWithFrameStack(frameStack)->throw -// this shouldn't be used for most runtime errors - the resulting error would have an empty stacktrace +// this shouldn't be used for most runtime errors - the resulting error would have an empty framestack let fromException = exn => switch exn { | SqException(e) => e @@ -143,14 +144,14 @@ let fromException = exn => | _ => REOther("Unknown exception")->fromMessage } -// converts raw exceptions into exceptions with stacktrace attached +// converts raw exceptions into exceptions with framestack attached // already converted exceptions won't be affected -let rethrowWithStacktrace = (fn: unit => 'a, frameStack: Reducer_FrameStack.t) => { +let rethrowWithFrameStack = (fn: unit => 'a, frameStack: Reducer_FrameStack.t) => { try { fn() } catch { - | SqException(e) => e->throw // exception already has a stacktrace - | Message.MessageException(e) => e->throwMessageWithFrameStack(frameStack) // probably comes from FunctionRegistry, adding stacktrace + | SqException(e) => e->throw // exception already has a framestack + | Message.MessageException(e) => e->throwMessageWithFrameStack(frameStack) // probably comes from FunctionRegistry, adding framestack | Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessageWithFrameStack(frameStack) | _ => REOther("Unknown exception")->throwMessageWithFrameStack(frameStack) From 6055320aa20ff4b88948b9de87978baaeaa5befc Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 15:56:13 +0400 Subject: [PATCH 13/18] more cleanups --- packages/squiggle-lang/src/js/SqError.ts | 4 ++-- .../squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res | 4 ++-- .../rescript/ReducerProject/ReducerProject_ProjectItem_T.res | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index 59036830..a2bd1154 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -39,12 +39,12 @@ export class SqFrame { constructor(private _value: RSReducerT.frame) {} name(): string { - return RSFrameStack.Frame.toName(this._value); + return RSFrameStack.Frame.getName(this._value); } location() { console.log(RSFrameStack); console.log(RSFrameStack.Frame); - return RSFrameStack.Frame.toLocation(this._value); + return RSFrameStack.Frame.getLocation(this._value); } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res index 02bb1cad..a1dfd691 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -13,10 +13,10 @@ module Frame = { } @genType - let toLocation = (t: Reducer_T.frame): option => t.location + let getLocation = (t: Reducer_T.frame): option => t.location @genType - let toName = (t: Reducer_T.frame): string => t.name + let getName = (t: Reducer_T.frame): string => t.name } let make = (): t => list{} diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res index bcdbfa02..24e690eb 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -8,8 +8,6 @@ type rawParseType = option type expressionArgumentType = result type expressionType = option type continuationArgumentType = Reducer_T.namespace -type continuationType = option -type continuationResultType = option> type resultArgumentType = result type resultType = option type continuesArgumentType = array From 0137b446891d40dd6963109625b0d07f1ac46164 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 16:22:29 +0400 Subject: [PATCH 14/18] more cleanups, some tests --- .../squiggle-lang/__tests__/SqError_test.res | 41 +++++++++++++++++++ packages/squiggle-lang/src/js/SqError.ts | 2 - .../rescript/Reducer/Reducer_FrameStack.res | 4 +- 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 packages/squiggle-lang/__tests__/SqError_test.res diff --git a/packages/squiggle-lang/__tests__/SqError_test.res b/packages/squiggle-lang/__tests__/SqError_test.res new file mode 100644 index 00000000..55109dde --- /dev/null +++ b/packages/squiggle-lang/__tests__/SqError_test.res @@ -0,0 +1,41 @@ +open Jest +open Expect + +describe("SqError.Message", () => { + test("toString", () => + expect(SqError.Message.REOther("test error")->SqError.Message.toString)->toBe( + "Error: test error", + ) + ) +}) + +describe("SqError", () => { + test("fromMessage", () => + expect(SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toString)->toBe( + "Error: test error", + ) + ) + + test("toStringWithStackTrace with empty stacktrace", () => + expect( + SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toStringWithStackTrace, + )->toBe("Error: test error") + ) + + test("toStringWithStackTrace", () => { + let frameStack = + Reducer_FrameStack.make() + ->Reducer_FrameStack.extend("frame1", None) + ->Reducer_FrameStack.extend("frame2", None) + + expect( + SqError.Message.REOther("test error") + ->SqError.fromMessageWithFrameStack(frameStack) + ->SqError.toStringWithStackTrace, + )->toBe(`Error: test error +Stack trace: + frame2 + frame1 +`) + }) +}) diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index a2bd1154..5c63d907 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -43,8 +43,6 @@ export class SqFrame { } location() { - console.log(RSFrameStack); - console.log(RSFrameStack.Frame); return RSFrameStack.Frame.getLocation(this._value); } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res index a1dfd691..b7370e00 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -8,7 +8,7 @@ module Frame = { name ++ switch location { | Some(location) => - ` at line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` + ` at line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` // TODO - source id? | None => "" } @@ -27,9 +27,11 @@ let extend = (t: t, name: string, location: option location: location, }) +// this is useful for SyntaxErrors let makeSingleFrameStack = (location: Reducer_Peggy_Parse.location): t => make()->extend(Reducer_T.topFrameName, Some(location)) +// this includes the left offset because it's mostly used in SqError.toStringWithStackTrace let toString = (t: t) => t ->Belt.List.map(s => " " ++ s->Frame.toString ++ "\n") From 2d0e6432cdb48109776d8298d3ebe6e77e333c70 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 17:10:53 +0400 Subject: [PATCH 15/18] fix #1199 --- packages/components/src/components/Alert.tsx | 2 +- .../src/components/FunctionChart.tsx | 36 +++++++++++++++---- .../components/SquiggleViewer/VariableBox.tsx | 4 +-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/components/src/components/Alert.tsx b/packages/components/src/components/Alert.tsx index bc2e2f92..cb579536 100644 --- a/packages/components/src/components/Alert.tsx +++ b/packages/components/src/components/Alert.tsx @@ -30,7 +30,7 @@ export const Alert: React.FC<{ className={clsx("h-5 w-5 flex-shrink-0", iconColor)} aria-hidden="true" /> -
+
{heading}
diff --git a/packages/components/src/components/FunctionChart.tsx b/packages/components/src/components/FunctionChart.tsx index 2da5d367..fda699e9 100644 --- a/packages/components/src/components/FunctionChart.tsx +++ b/packages/components/src/components/FunctionChart.tsx @@ -1,9 +1,15 @@ import * as React from "react"; -import { SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; +import { + SqLambda, + environment, + SqValueTag, + SqError, +} from "@quri/squiggle-lang"; import { FunctionChart1Dist } from "./FunctionChart1Dist"; import { FunctionChart1Number } from "./FunctionChart1Number"; import { DistributionPlottingSettings } from "./DistributionChart"; -import { ErrorAlert, MessageAlert } from "./Alert"; +import { MessageAlert } from "./Alert"; +import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; export type FunctionChartSettings = { start: number; @@ -19,6 +25,25 @@ interface FunctionChartProps { height: number; } +const FunctionCallErrorAlert = ({ error }: { error: SqError }) => { + const [expanded, setExpanded] = React.useState(false); + if (expanded) { + } + return ( + +
+ setExpanded(!expanded)} + > + {expanded ? "Hide" : "Show"} error details + + {expanded ? : null} +
+
+ ); +}; + export const FunctionChart: React.FC = ({ fn, chartSettings, @@ -26,7 +51,8 @@ export const FunctionChart: React.FC = ({ distributionPlotSettings, height, }) => { - if (fn.parameters.length > 1) { + console.log(fn.parameters().length); + if (fn.parameters().length !== 1) { return ( Only functions with one parameter are displayed. @@ -47,9 +73,7 @@ export const FunctionChart: React.FC = ({ const validResult = getValidResult(); if (validResult.tag === "Error") { - return ( - {validResult.value.toString()} - ); + return ; } switch (validResult.value.tag) { diff --git a/packages/components/src/components/SquiggleViewer/VariableBox.tsx b/packages/components/src/components/SquiggleViewer/VariableBox.tsx index 9ad59597..ca0fc875 100644 --- a/packages/components/src/components/SquiggleViewer/VariableBox.tsx +++ b/packages/components/src/components/SquiggleViewer/VariableBox.tsx @@ -1,4 +1,4 @@ -import { SqValue, SqValueLocation } from "@quri/squiggle-lang"; +import { SqValue } from "@quri/squiggle-lang"; import React, { useContext, useReducer } from "react"; import { Tooltip } from "../ui/Tooltip"; import { LocalItemSettings, MergedItemSettings } from "./utils"; @@ -70,7 +70,7 @@ export const VariableBox: React.FC = ({
{location.path.items.length ? (
) : null} From 8cbbdf54892154947d71470e7c6a4d05041d0e56 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 18:07:37 +0400 Subject: [PATCH 16/18] stacktrace test --- .../__tests__/Reducer/Reducer_test.res | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res index 0697d239..2743b51d 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res @@ -78,3 +78,33 @@ describe("test exceptions", () => { // "Error(TODO: unhandled rescript exception)", // ) }) + +describe("stacktraces", () => { + test("nested calls", () => { + open Expect + + let error = + Expression.BackCompatible.evaluateString(` + f(x) = { + y = "a" + x + y + } + g = {|x| f(x)} + h(x) = g(x) + h(5) +`) + ->E.R.getError + ->E.O2.toExn("oops") + ->SqError.toStringWithStackTrace + + expect( + error, + )->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]; [add(date, duration)]; [add(duration, duration)] +Stack trace: + f at line 4, column 5 + at line 6, column 12 + h at line 7, column 10 + at line 8, column 3 +`) + }) +}) From 19a44eb12f74922e8ebb22d03713d71b0ad55215 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 5 Oct 2022 19:19:22 +0400 Subject: [PATCH 17/18] fix #1214 --- .../Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res | 2 +- .../Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res | 2 +- packages/squiggle-lang/__tests__/Reducer/Reducer_test.res | 2 +- .../Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy | 2 +- .../src/rescript/Reducer/Reducer_Peggy/helpers.ts | 4 +++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res index 55611d3a..1e1ba209 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res @@ -197,7 +197,7 @@ describe("Peggy parse", () => { describe("lambda", () => { testParse("{|x| x}", "{{|:x| :x}}") - testParse("f={|x| x}", "{:f = {{|:x| :x}}}") + testParse("f={|x| x}", "{:f = {|:x| :x}}") testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res index cab84681..daeab3f0 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res @@ -135,7 +135,7 @@ describe("Peggy to Expression", () => { describe("lambda", () => { testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ()) - testToExpression("f={|x| x}", "f = {{|x| x}}", ()) + testToExpression("f={|x| x}", "f = {|x| x}", ()) testToExpression("f(x)=x", "f = {|x| {x}}", ()) // Function definitions are lambda assignments testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ()) }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res index 2743b51d..381c4a81 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res @@ -102,7 +102,7 @@ describe("stacktraces", () => { )->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]; [add(date, duration)]; [add(duration, duration)] Stack trace: f at line 4, column 5 - at line 6, column 12 + g at line 6, column 12 h at line 7, column 10 at line 8, column 3 `) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy index 10c64283..7bb01c56 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy @@ -7,7 +7,7 @@ start = _nl start:outerBlock _nl finalComment? {return start} -zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda +zeroOMoreArgumentsBlockOrExpression = lambda / innerBlockOrExpression outerBlock = statements:array_statements finalExpression: (statementSeparator @expression)? diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts index 6253b875..15597ea0 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts @@ -228,7 +228,9 @@ export function nodeLetStatement( value: AnyPeggyNode, location: LocationRange ): NodeLetStatement { - return { type: "LetStatement", variable, value, location }; + const patchedValue = + value.type === "Lambda" ? { ...value, name: variable.value } : value; + return { type: "LetStatement", variable, value: patchedValue, location }; } export function nodeModuleIdentifier(value: string, location: LocationRange) { return { type: "ModuleIdentifier", value, location };