top-level SqError; expose stacktrace in TS API
This commit is contained in:
parent
69b32d0b93
commit
845d38e375
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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() : ""
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,9 +99,18 @@ module StackTrace = {
|
||||||
| None => ""
|
| None => ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rec toLocationList = (t: t): list<location> => {
|
||||||
|
switch t.parent {
|
||||||
|
| Some(parent) => Belt.List.add(toLocationList(parent), t.location)
|
||||||
|
| None => list{t.location}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@genType
|
||||||
|
let toLocationArray = (t: t): array<location> => t->toLocationList->Belt.List.toArray
|
||||||
}
|
}
|
||||||
|
|
||||||
module Error = {
|
|
||||||
@genType.opaque
|
@genType.opaque
|
||||||
type t = {
|
type t = {
|
||||||
message: Message.t,
|
message: Message.t,
|
||||||
|
@ -109,16 +119,8 @@ module Error = {
|
||||||
|
|
||||||
exception SqException(t)
|
exception SqException(t)
|
||||||
|
|
||||||
let toString = (err: t) => err.message->Message.toString
|
@genType
|
||||||
|
let fromMessage = (errorMessage: Message.t): t => {
|
||||||
let toStringWithStackTrace = (err: t) =>
|
|
||||||
switch err.stackTrace {
|
|
||||||
| Some(stack) => "Traceback:\n" ++ stack->StackTrace.toString
|
|
||||||
| None => ""
|
|
||||||
} ++
|
|
||||||
err->toString
|
|
||||||
|
|
||||||
let fromMessage = (errorMessage: Message.t) => {
|
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
stackTrace: None,
|
stackTrace: None,
|
||||||
}
|
}
|
||||||
|
@ -133,6 +135,26 @@ module Error = {
|
||||||
stackTrace: Some({location: location, parent: stackTrace}),
|
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 throw = (t: t) => t->SqException->raise
|
||||||
|
|
||||||
let fromException = exn =>
|
let fromException = exn =>
|
||||||
|
@ -142,4 +164,3 @@ module Error = {
|
||||||
| Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage
|
| Js.Exn.Error(obj) => REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->fromMessage
|
||||||
| _ => REOther("Unknown exception")->fromMessage
|
| _ => REOther("Unknown exception")->fromMessage
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user