WIP (broken)
This commit is contained in:
parent
184584c9f3
commit
a764f3075c
|
@ -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<frame>
|
||||
|
||||
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<frame> => t->Belt.List.toArray
|
||||
|
||||
@genType
|
||||
let getTopFrame = (t: t): option<frame> => t->Belt.List.head
|
||||
|
||||
let isEmpty = (t: t): bool =>
|
||||
switch t->Belt.List.head {
|
||||
| Some(_) => true
|
||||
| None => false
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 => "<main>"
|
||||
}
|
||||
|
||||
let extend = (t: t, lambda: Reducer_T.lambdaValue, location: option<location>) =>
|
||||
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<frame> => t->Belt.List.toArray
|
||||
|
||||
@genType
|
||||
let getTopFrame = (t: t): option<frame> => t->Belt.List.head
|
||||
|
||||
let isEmpty = (t: t): bool =>
|
||||
switch t->Belt.List.head {
|
||||
| Some(_) => true
|
||||
| None => false
|
||||
}
|
|
@ -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<string> => {
|
||||
switch t {
|
||||
|
@ -76,13 +68,13 @@ let parameters = (t: t): array<string> => {
|
|||
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)
|
||||
}
|
||||
|
|
|
@ -50,10 +50,18 @@ and bindings = {
|
|||
parent: option<bindings>,
|
||||
}
|
||||
|
||||
and frame = {
|
||||
name: string,
|
||||
location: option<Reducer_Peggy_Parse.location>, // can be empty for calls from builtin functions
|
||||
}
|
||||
|
||||
and frameStack = list<frame>
|
||||
|
||||
and context = {
|
||||
bindings: bindings,
|
||||
environment: environment,
|
||||
callStack: Reducer_CallStack.t,
|
||||
inFunction: option<string>, // used to build the next frame in frameStack
|
||||
callStack: frameStack,
|
||||
}
|
||||
|
||||
and reducerFn = (expression, context) => (value, context)
|
||||
|
|
|
@ -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<location>,
|
||||
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<Reducer_T.context>,
|
||||
location: option<location>,
|
||||
): 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<Reducer_CallStack.frame> =>
|
||||
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<Reducer_CallStack.frame> =>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user