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