WIP (broken)

This commit is contained in:
Vyacheslav Matyukhin 2022-10-02 14:04:45 +04:00
parent 184584c9f3
commit a764f3075c
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
6 changed files with 85 additions and 101 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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