top-level SqError; expose stacktrace in TS API

This commit is contained in:
Vyacheslav Matyukhin 2022-09-26 16:27:45 +04:00
parent 69b32d0b93
commit 845d38e375
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
17 changed files with 128 additions and 99 deletions

View File

@ -1,4 +1,4 @@
import { SqError } from "@quri/squiggle-lang"; import { SqError, SqLocation } from "@quri/squiggle-lang";
import React from "react"; import React from "react";
import { ErrorAlert } from "./Alert"; import { ErrorAlert } from "./Alert";
@ -6,10 +6,34 @@ type Props = {
error: SqError; error: SqError;
}; };
const StackTraceLocation: React.FC<{ location: SqLocation }> = ({
location,
}) => {
return (
<div>
Line {location.start.line}, column {location.start.column}
</div>
);
};
const StackTrace: React.FC<Props> = ({ error }) => {
return (
<div>
{error.toLocationArray().map((location, i) => (
<StackTraceLocation location={location} key={i} />
))}
</div>
);
};
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => { export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
return ( return (
<ErrorAlert heading="Error"> <ErrorAlert heading="Error">
<pre>{error.toString()}</pre> <div>{error.toString()}</div>
<div className="mt-4">Traceback:</div>
<div className="ml-4">
<StackTrace error={error} />
</div>
</ErrorAlert> </ErrorAlert>
); );
}; };

View File

@ -22,7 +22,7 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => {
} else { } else {
let a2 = let a2 =
rExpr 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)) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr))
->Reducer_Value.toStringResultOkless ->Reducer_Value.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v)) (a1, a2)->expect->toEqual((answer, v))

View File

@ -36,6 +36,6 @@ export const run = (src, { output, sampleCount } = {}) => {
"Time:", "Time:",
String(time), String(time),
result.tag === "Error" ? red(result.tag) : green(result.tag), result.tag === "Error" ? red(result.tag) : green(result.tag),
result.tag === "Error" ? result.value.toString() : "" result.tag === "Error" ? result.value.toStringWithStackTrace() : ""
); );
}; };

View File

@ -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 { export class SqError {
constructor(private _value: RSError.error) {} constructor(private _value: RSError.t) {}
toString() { toString() {
return RSError.toString(this._value); return RSError.toString(this._value);
} }
toStringWithStackTrace() {
return RSError.toStringWithStackTrace(this._value);
}
static createOtherError(v: string) { static createOtherError(v: string) {
return new SqError(RSError.createOtherError(v)); return new SqError(RSError.createOtherError(v));
} }
toLocationArray() {
const stackTrace = RSError.getStackTrace(this._value);
return stackTrace ? RSError.StackTrace.toLocationArray(stackTrace) : [];
}
} }

View File

@ -1,12 +1,11 @@
import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; 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 { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen";
import { SqError } from "./SqError"; import { SqError } from "./SqError";
import { SqRecord } from "./SqRecord"; import { SqRecord } from "./SqRecord";
import { wrapValue } from "./SqValue"; import { wrapValue } from "./SqValue";
import { resultMap2 } from "./types"; import { resultMap2 } from "./types";
import { SqValueLocation } from "./SqValueLocation"; import { SqValueLocation } from "./SqValueLocation";
import { errorFromMessage } from "../rescript/ForTS/ForTS_SqError.gen";
export class SqProject { export class SqProject {
constructor(private _value: RSProject.reducerProject) {} constructor(private _value: RSProject.reducerProject) {}
@ -51,7 +50,7 @@ export class SqProject {
return resultMap2( return resultMap2(
RSProject.getIncludes(this._value, sourceId), RSProject.getIncludes(this._value, sourceId),
(a) => a, (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: [], items: [],
}) })
), ),
(v: RSError.error) => new SqError(v) (v: RSError.t) => new SqError(v)
); );
} }

View File

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

View File

@ -1,7 +1,7 @@
@genType type reducerProject = ReducerProject_T.project //re-export @genType type reducerProject = ReducerProject_T.project //re-export
type error = ForTS_SqError.error //use type error = SqError.t //use
type errorMessage = ForTS_SqError.errorMessage //use type errorMessage = SqError.Message.t //use
type squiggleValue = ForTS_SquiggleValue.squiggleValue //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use

View File

@ -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<location> =>
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

View File

@ -1,5 +1,5 @@
@genType type squiggleValue = Reducer_T.value //re-export @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_Array = Reducer_T.arrayValue //re-export recursive type
@genType type squiggleValue_Record = Reducer_T.map //re-export recursive type @genType type squiggleValue_Record = Reducer_T.map //re-export recursive type

View File

@ -1,5 +1,3 @@
@genType type location = ForTS_SqError.location //re-export
@genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export @genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export
@genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export
@genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export @genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export

View File

@ -8,7 +8,7 @@ let toLocation = (expression: T.expression): SqError.location => {
} }
let throwFrom = (error: SqError.Message.t, expression: T.expression) => 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 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) let result = Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate)
(result, context) (result, context)
} catch { } catch {
| exn => | exn => exn->SqError.fromException->SqError.extend(expression->toLocation)->SqError.throw
exn
->SqError.Error.fromException
->SqError.Error.extend(expression->toLocation)
->SqError.Error.throw
} }
| _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression) | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression)
} }
@ -126,18 +122,18 @@ module BackCompatible = {
let parse = (peggyCode: string): result<T.expression, Reducer_Peggy_Parse.parseError> => let parse = (peggyCode: string): result<T.expression, Reducer_Peggy_Parse.parseError> =>
peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode)
let evaluate = (expression: T.expression): result<T.value, SqError.Error.t> => { let evaluate = (expression: T.expression): result<T.value, SqError.t> => {
let context = Reducer_Context.createDefaultContext() let context = Reducer_Context.createDefaultContext()
try { try {
let (value, _) = expression->evaluate(context) let (value, _) = expression->evaluate(context)
value->Ok value->Ok
} catch { } catch {
| exn => exn->SqError.Error.fromException->Error | exn => exn->SqError.fromException->Error
} }
} }
let evaluateString = (peggyCode: string): result<T.value, SqError.Error.t> => let evaluateString = (peggyCode: string): result<T.value, SqError.t> =>
parse(peggyCode) 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) ->Result.flatMap(evaluate)
} }

View File

@ -1,7 +1,5 @@
module Extra = Reducer_Extra module Extra = Reducer_Extra
// Do not gentype this, use LocationRange from peggy types instead
// TODO - rename locationPoint -> location, location -> locationRange to match peggy
@genType @genType
type locationPoint = { type locationPoint = {
line: int, line: int,

View File

@ -72,13 +72,13 @@ let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)})
let toStringResult = x => let toStringResult = x =>
switch x { switch x {
| Ok(a) => `Ok(${toString(a)})` | Ok(a) => `Ok(${toString(a)})`
| Error(m) => `Error(${SqError.Error.toString(m)})` | Error(m) => `Error(${SqError.toString(m)})`
} }
let toStringResultOkless = (codeResult: result<t, SqError.Error.t>): string => let toStringResultOkless = (codeResult: result<t, SqError.t>): string =>
switch codeResult { switch codeResult {
| Ok(a) => toString(a) | Ok(a) => toString(a)
| Error(m) => `Error(${SqError.Error.toString(m)})` | Error(m) => `Error(${SqError.toString(m)})`
} }
type internalExpressionValueType = type internalExpressionValueType =

View File

@ -116,7 +116,7 @@ let getResultOption = (project: t, sourceId: string): ProjectItem.T.resultType =
let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType => let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType =>
switch getResultOption(project, sourceId) { switch getResultOption(project, sourceId) {
| None => RENeedToRun->SqError.Error.fromMessage->Error | None => RENeedToRun->SqError.fromMessage->Error
| Some(result) => result | Some(result) => result
} }
@ -170,7 +170,7 @@ let linkDependencies = (project: t, sourceId: string): Reducer_T.namespace => {
"__result__", "__result__",
switch project->getResult(id) { switch project->getResult(id) {
| Ok(result) => result | Ok(result) => result
| Error(error) => error->SqError.Error.throw | Error(error) => error->SqError.throw
}, },
), ),
]) ])

View File

@ -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()) this->setResult(e->Error)->setContinuation(Reducer_Namespace.make())
let doRun = (this: t, context: Reducer_T.context): t => 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) ->setResult(result->Ok)
->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals) ->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals)
} catch { } 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 => { let run = (this: t, context: Reducer_T.context): t => {

View File

@ -10,7 +10,7 @@ type expressionType = option<expressionArgumentType>
type continuationArgumentType = Reducer_T.namespace type continuationArgumentType = Reducer_T.namespace
type continuationType = option<continuationArgumentType> type continuationType = option<continuationArgumentType>
type continuationResultType = option<result<continuationArgumentType, SqError.Message.t>> type continuationResultType = option<result<continuationArgumentType, SqError.Message.t>>
type resultArgumentType = result<Reducer_T.value, SqError.Error.t> type resultArgumentType = result<Reducer_T.value, SqError.t>
type resultType = option<resultArgumentType> type resultType = option<resultArgumentType>
type continuesArgumentType = array<string> type continuesArgumentType = array<string>
type continuesType = array<string> type continuesType = array<string>

View File

@ -86,6 +86,7 @@ module Message = {
} }
module StackTrace = { module StackTrace = {
@genType.opaque
type rec t = { type rec t = {
location: location, location: location,
parent: option<t>, parent: option<t>,
@ -98,48 +99,68 @@ module StackTrace = {
| None => "" | None => ""
} }
} }
}
module Error = { let rec toLocationList = (t: t): list<location> => {
@genType.opaque switch t.parent {
type t = { | Some(parent) => Belt.List.add(toLocationList(parent), t.location)
message: Message.t, | None => list{t.location}
stackTrace: option<StackTrace.t>,
}
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
} }
}
@genType
let toLocationArray = (t: t): array<location> => t->toLocationList->Belt.List.toArray
} }
@genType.opaque
type t = {
message: Message.t,
stackTrace: option<StackTrace.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) => {
message: message,
stackTrace: Some({location: location, parent: stackTrace}),
}
@genType
let getLocation = (t: t): option<location> => t.stackTrace->E.O2.fmap(stack => stack.location)
@genType
let getStackTrace = (t: t): option<StackTrace.t> => 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
}