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 + }