diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index 49179497..d03e237f 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -7,5 +7,9 @@ type Props = { }; export const SquiggleErrorAlert: React.FC = ({ error }) => { - return {error.toString()}; + return ( + +
{error.toString()}
+
+ ); }; diff --git a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx index 8bd32292..829f323f 100644 --- a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx +++ b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx @@ -298,7 +298,9 @@ export const ExpressionViewer: React.FC = ({ value, width }) => { {() => (
No display for type: {" "} - {value.tag} + + {(value as any).tag} +
)} diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res index 0dadae86..4b9073dd 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_TestHelpers.res @@ -9,12 +9,12 @@ open Jest open Expect let expectParseToBe = (expr, answer) => - Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer) + Parse.parse(expr, "test")->Parse.toStringResult->expect->toBe(answer) let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { - let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode) + let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode) let a1 = rExpr->ExpressionT.toStringResultOkless if v == "_" { @@ -22,6 +22,7 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { } else { let a2 = rExpr + ->E.R2.errMap(e => e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res index 07660044..a6b35ec4 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res @@ -25,7 +25,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common"] - | Error(error) => fail(error->Reducer_ErrorValue.errorToString) + | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) } }) test("past chain", () => { @@ -60,7 +60,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "myModule"] - | Error(error) => fail(error->Reducer_ErrorValue.errorToString) + | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) } }) @@ -99,7 +99,7 @@ x=1`, let mainIncludes = Project.getIncludes(project, "main") switch mainIncludes { | Ok(includes) => expect(includes) == ["common", "common2", "myModule"] - | Error(error) => fail(error->Reducer_ErrorValue.errorToString) + | Error(error) => fail(error->Reducer_ErrorValue.errorValueToString) } }) test("direct past chain", () => { diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res index 2c63dfff..995ce4d3 100644 --- a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res +++ b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_tutorial_3_includes_test.res @@ -36,7 +36,7 @@ Here we will finally proceed to a real life scenario. */ /* Parse includes has set the includes */ switch project->Project.getIncludes("main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->Reducer_ErrorValue.errorToString->fail + | Error(err) => err->Reducer_ErrorValue.errorValueToString->fail } /* If the includes cannot be parsed then you get a syntax error. Otherwise you get the includes. @@ -85,7 +85,7 @@ Here we will finally proceed to a real life scenario. */ let rIncludes = project->Project.getIncludes(sourceName) switch rIncludes { /* Maybe there is an include syntax error */ - | Error(err) => err->Reducer_ErrorValue.errorToString->Js.Exn.raiseError + | Error(err) => err->Reducer_ErrorValue.errorValueToString->Js.Exn.raiseError | Ok(includes) => includes->Belt.Array.forEach(newIncludeName => { @@ -169,7 +169,7 @@ Here we will finally proceed to a real life scenario. */ test("getIncludes", () => { switch Project.getIncludes(project, "main") { | Ok(includes) => includes->expect == ["common"] - | Error(err) => err->Reducer_ErrorValue.errorToString->fail + | Error(err) => err->Reducer_ErrorValue.errorValueToString->fail } }) }) diff --git a/packages/squiggle-lang/scripts/run.mjs b/packages/squiggle-lang/scripts/run.mjs index 6dde265f..91058f33 100755 --- a/packages/squiggle-lang/scripts/run.mjs +++ b/packages/squiggle-lang/scripts/run.mjs @@ -1,11 +1,18 @@ #!/usr/bin/env node import { run } from "./lib.mjs"; -const src = process.argv[2]; +import { Command } from "commander"; + +const program = new Command(); + +program.arguments(""); + +const options = program.parse(process.argv); + +const src = program.args[0]; if (!src) { throw new Error("Expected src"); } -console.log(`Running ${src}`); const sampleCount = process.env.SAMPLE_COUNT; diff --git a/packages/squiggle-lang/src/js/SqError.ts b/packages/squiggle-lang/src/js/SqError.ts index 317fde4f..c0c509b4 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -1,16 +1,12 @@ import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; export class SqError { - constructor(private _value: RSErrorValue.reducerErrorValue) {} + constructor(private _value: RSErrorValue.reducerError) {} toString() { return RSErrorValue.toString(this._value); } - static createTodoError(v: string) { - return new SqError(RSErrorValue.createTodoError(v)); - } - static createOtherError(v: string) { return new SqError(RSErrorValue.createOtherError(v)); } diff --git a/packages/squiggle-lang/src/js/SqProject.ts b/packages/squiggle-lang/src/js/SqProject.ts index 9e79b564..22f2f78a 100644 --- a/packages/squiggle-lang/src/js/SqProject.ts +++ b/packages/squiggle-lang/src/js/SqProject.ts @@ -1,5 +1,9 @@ import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; -import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; +import { + reducerError, + reducerErrorValue, + attachEmptyStackTraceToErrorValue, +} from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen"; import { SqError } from "./SqError"; import { SqRecord } from "./SqRecord"; @@ -50,7 +54,8 @@ export class SqProject { return resultMap2( RSProject.getIncludes(this._value, sourceId), (a) => a, - (v: reducerErrorValue) => new SqError(v) + (v: reducerErrorValue) => + new SqError(attachEmptyStackTraceToErrorValue(v)) ); } @@ -104,7 +109,7 @@ export class SqProject { items: [], }) ), - (v: reducerErrorValue) => new SqError(v) + (v: reducerError) => new SqError(v) ); } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res index 2d60f556..7767f8c1 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res @@ -143,7 +143,7 @@ module Integration = { | Error(b) => ("Integration error 2 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead." ++ "Original error: " ++ - b->Reducer_ErrorValue.errorToString) + b->Reducer_ErrorValue.errorValueToString) ->Reducer_ErrorValue.REOther ->Error } @@ -225,7 +225,7 @@ module Integration = { reducer, )->E.R2.errMap(b => ("Integration error 7 in Danger.integrate. Something went wrong along the way: " ++ - b->Reducer_ErrorValue.errorToString)->Reducer_ErrorValue.REOther + b->Reducer_ErrorValue.errorValueToString)->Reducer_ErrorValue.REOther ) | _ => "Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))" diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res index ac9c167c..857044d7 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res @@ -1,5 +1,6 @@ @genType type reducerProject = ReducerProject_T.project //re-export +type reducerError = ForTS_Reducer_ErrorValue.reducerError //use type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use @@ -191,10 +192,8 @@ let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Rec Get the result after running this source file or the project */ @genType -let getResult = (project: reducerProject, sourceId: string): result< - squiggleValue, - reducerErrorValue, -> => project->Private.getResult(sourceId) +let getResult = (project: reducerProject, sourceId: string): result => + project->Private.getResult(sourceId) /* This is a convenience function to get the result of a single source without creating a project. @@ -202,10 +201,8 @@ However, without a project, you cannot handle include directives. The source has to be include free */ @genType -let evaluate = (sourceCode: string): ( - result, - squiggleValue_Record, -) => Private.evaluate(sourceCode) +let evaluate = (sourceCode: string): (result, squiggleValue_Record) => + Private.evaluate(sourceCode) @genType let setEnvironment = (project: reducerProject, environment: environment): unit => @@ -213,24 +210,3 @@ let setEnvironment = (project: reducerProject, environment: environment): unit = @genType let getEnvironment = (project: reducerProject): environment => project->Private.getEnvironment - -/* -Foreign function interface is intentionally demolished. -There is another way to do that: Umur. -Also there is no more conversion from javascript to squiggle values currently. -If the conversion to the new project is too difficult, I can add it later. -*/ - -// let foreignFunctionInterface = ( -// lambdaValue: squiggleValue_Lambda, -// argArray: array, -// environment: environment, -// ): result => { -// let accessors = ReducerProject_ProjectAccessors_T.identityAccessorsWithEnvironment(environment) -// Reducer_Expression_Lambda.foreignFunctionInterface( -// lambdaValue, -// argArray, -// accessors, -// Reducer_Expression.reduceExpressionInProject, -// ) -// } diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res index d4a8059b..4edc4ed3 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res @@ -1,18 +1,21 @@ +@genType type reducerError = Reducer_ErrorValue.error //alias @genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias -@genType type syntaxErrorLocation = Reducer_ErrorValue.syntaxErrorLocation //alias +@genType type location = Reducer_ErrorValue.location //alias @genType -let toString = (e: reducerErrorValue): string => Reducer_ErrorValue.errorToString(e) +let toString = (e: reducerError): string => Reducer_ErrorValue.errorToString(e) @genType -let getLocation = (e: reducerErrorValue): option => - switch e { - | RESyntaxError(_, optionalLocation) => optionalLocation - | _ => None +let getLocation = (e: reducerError): option => + switch e.stackTrace { + | Some(stack) => Some(stack.location) + | None => None } @genType -let createTodoError = (v: string) => Reducer_ErrorValue.RETodo(v) +let createOtherError = (v: string): reducerError => + Reducer_ErrorValue.REOther(v)->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue @genType -let createOtherError = (v: string) => Reducer_ErrorValue.REOther(v) +let attachEmptyStackTraceToErrorValue = (v: reducerErrorValue): reducerError => + Reducer_ErrorValue.attachEmptyStackTraceToErrorValue(v) diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res index 8331e776..ff029c95 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.res @@ -1,5 +1,5 @@ @genType type squiggleValue = Reducer_T.value //re-export -type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use +type reducerError = ForTS_Reducer_ErrorValue.reducerError //use @genType type squiggleValue_Array = Reducer_T.arrayValue //re-export recursive type @genType type squiggleValue_Record = Reducer_T.map //re-export recursive type @@ -69,7 +69,7 @@ let toString = (variant: squiggleValue) => Reducer_Value.toString(variant) // This is a useful method for unit tests. // Convert the result along with the error message to a string. @genType -let toStringResult = (variantResult: result) => +let toStringResult = (variantResult: result) => Reducer_Value.toStringResult(variantResult) @genType diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res index 1cd74827..949801c7 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res @@ -1,5 +1,5 @@ @genType type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //re-export -@genType type syntaxErrorLocation = ForTS_Reducer_ErrorValue.syntaxErrorLocation //re-export +@genType type location = ForTS_Reducer_ErrorValue.location //re-export @genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index b6baeaf5..4a3d863f 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -1,6 +1,16 @@ -//TODO: Do not export here but in ForTS__Types -@gentype.import("peggy") @genType.as("LocationRange") -type syntaxErrorLocation +// Do not gentype this, use LocationRange from peggy types instead +// TODO - rename locationPoint -> location, location -> locationRange to match peggy +@genType +type locationPoint = { + line: int, + column: int, +} +@genType +type location = { + source: string, + start: locationPoint, + end: locationPoint, +} @genType.opaque type errorValue = @@ -18,7 +28,7 @@ type errorValue = | REOperationError(Operation.operationError) | RERecordPropertyNotFound(string, string) | RESymbolNotFound(string) - | RESyntaxError(string, option) + | RESyntaxError(string, option) | RETodo(string) // To do | REUnitNotFound(string) | RENeedToRun @@ -28,7 +38,20 @@ type t = errorValue exception ErrorException(errorValue) -let errorToString = err => +type rec stackTrace = { + location: location, + parent: option, +} + +@genType.opaque +type error = { + error: t, + stackTrace: option, +} + +exception ExceptionWithStackTrace(error) + +let errorValueToString = (err: errorValue) => switch err { | REArityError(_oFnName, arity, usedArity) => `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( @@ -80,4 +103,37 @@ let fromException = exn => | _e => REOther("Unknown error") } -let toException = (errorValue: t) => raise(ErrorException(errorValue)) +let rec stackTraceToString = ({location, parent}: stackTrace) => { + ` Line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}, source ${location.source}\n` ++ + switch parent { + | Some(parent) => stackTraceToString(parent) + | None => "" + } +} + +let errorToString = (err: error) => + switch err.stackTrace { + | Some(stack) => "Traceback:\n" ++ stack->stackTraceToString + | None => "" + } ++ + err.error->errorValueToString + +let toException = (errorValue: t) => errorValue->ErrorException->raise + +let attachEmptyStackTraceToErrorValue = (errorValue: t) => { + error: errorValue, + stackTrace: None, +} + +let attachLocationToErrorValue = (errorValue: t, location: location): error => { + error: errorValue, + stackTrace: Some({location: location, parent: None}), +} + +let raiseNewExceptionWithStackTrace = (errorValue: t, location: location) => + errorValue->attachLocationToErrorValue(location)->ExceptionWithStackTrace->raise + +let raiseExtendedExceptionWithStackTrace = ({error, stackTrace}: error, location: location) => + {error: error, stackTrace: Some({location: location, parent: stackTrace})} + ->ExceptionWithStackTrace + ->raise 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 c28e121e..5ab213a0 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 @@ -5,12 +5,19 @@ module T = Reducer_T type errorValue = Reducer_ErrorValue.errorValue +let toLocation = (expression: T.expression): Reducer_ErrorValue.location => { + expression.ast.location +} + +let throwFrom = (error: errorValue, expression: T.expression) => + error->Reducer_ErrorValue.raiseNewExceptionWithStackTrace(expression->toLocation) + /* Recursively evaluate the expression */ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { // Js.log(`reduce: ${expression->Reducer_Expression_T.toString}`) - switch expression { + switch expression.content { | T.EBlock(statements) => { let innerContext = {...context, bindings: context.bindings->Bindings.extend} let (value, _) = @@ -49,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")->Reducer_ErrorValue.ErrorException->raise + | _ => REOther("Record keys must be strings")->throwFrom(expression) } let (value, _) = eValue->evaluate(context) (keyString, value) @@ -73,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 => Reducer_ErrorValue.RESymbolNotFound(name)->Reducer_ErrorValue.ErrorException->raise + | None => RESymbolNotFound(name)->throwFrom(expression) } | T.EValue(value) => (value, context) @@ -82,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", "")->Reducer_ErrorValue.ErrorException->raise + | _ => REExpectedType("Boolean", "")->throwFrom(expression) } } @@ -98,12 +105,18 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { argValue }) switch lambda { - | T.IEvLambda(lambda) => ( - Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate), - context, - ) - | _ => - RENotAFunction(lambda->Reducer_Value.toString)->Reducer_ErrorValue.ErrorException->raise + | T.IEvLambda(lambda) => + try { + let result = Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate) + (result, context) + } catch { + | Reducer_ErrorValue.ErrorException(e) => e->throwFrom(expression) // function implementation returned an error without location + | Reducer_ErrorValue.ExceptionWithStackTrace(e) => + Reducer_ErrorValue.raiseExtendedExceptionWithStackTrace(e, expression->toLocation) // function implementation probably called a lambda that threw an exception + | Js.Exn.Error(obj) => + REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwFrom(expression) + } + | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression) } } } @@ -113,18 +126,24 @@ module BackCompatible = { // Those methods are used to support the existing tests // If they are used outside limited testing context, error location reporting will fail let parse = (peggyCode: string): result => - peggyCode->Reducer_Peggy_Parse.parse->Result.map(Reducer_Peggy_ToExpression.fromNode) + peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) - let evaluate = (expression: T.expression): result => { + let evaluate = (expression: T.expression): result => { let context = Reducer_Context.createDefaultContext() try { let (value, _) = expression->evaluate(context) value->Ok } catch { - | exn => Reducer_ErrorValue.fromException(exn)->Error + | exn => + exn + ->Reducer_ErrorValue.fromException + ->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue + ->Error // TODO - this could be done better (see ReducerProject) } } - let evaluateString = (peggyCode: string): result => - parse(peggyCode)->Result.flatMap(evaluate) + let evaluateString = (peggyCode: string): result => + parse(peggyCode) + ->E.R2.errMap(e => e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + ->Result.flatMap(evaluate) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res index 9f1ed5fb..bd0a044a 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_ExpressionBuilder.res @@ -3,14 +3,18 @@ module T = Reducer_T type errorValue = BErrorValue.errorValue type expression = Reducer_T.expression +type expressionContent = Reducer_T.expressionContent -let eArray = (anArray: array) => anArray->T.EArray +let eArray = (anArray: array): expressionContent => anArray->T.EArray let eBool = aBool => aBool->T.IEvBool->T.EValue -let eCall = (fn: expression, args: array): expression => T.ECall(fn, args) +let eCall = (fn: expression, args: array): expressionContent => T.ECall(fn, args) -let eLambda = (parameters: array, expr: expression) => T.ELambda(parameters, expr) +let eLambda = (parameters: array, expr: expression): expressionContent => T.ELambda( + parameters, + expr, +) let eNumber = aNumber => aNumber->T.IEvNumber->T.EValue @@ -18,13 +22,13 @@ let eRecord = (aMap: array<(T.expression, T.expression)>) => aMap->T.ERecord let eString = aString => aString->T.IEvString->T.EValue -let eSymbol = (name: string): expression => T.ESymbol(name) +let eSymbol = (name: string): expressionContent => T.ESymbol(name) -let eBlock = (exprs: array): expression => T.EBlock(exprs) +let eBlock = (exprs: array): expressionContent => T.EBlock(exprs) -let eProgram = (exprs: array): expression => T.EProgram(exprs) +let eProgram = (exprs: array): expressionContent => T.EProgram(exprs) -let eLetStatement = (symbol: string, valueExpression: expression): expression => T.EAssign( +let eLetStatement = (symbol: string, valueExpression: expression): expressionContent => T.EAssign( symbol, valueExpression, ) @@ -33,11 +37,8 @@ let eTernary = ( predicate: expression, trueCase: expression, falseCase: expression, -): expression => T.ETernary(predicate, trueCase, falseCase) +): expressionContent => T.ETernary(predicate, trueCase, falseCase) -let eIdentifier = (name: string): expression => name->T.ESymbol +let eIdentifier = (name: string): expressionContent => name->T.ESymbol -// let eTypeIdentifier = (name: string): expression => -// name->T.IEvTypeIdentifier->T.EValue - -let eVoid: expression = T.IEvVoid->T.EValue +let eVoid: expressionContent = T.IEvVoid->T.EValue diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index 71995c0a..2aca6cdb 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -12,7 +12,7 @@ let semicolonJoin = values => Converts the expression to String */ let rec toString = (expression: t) => - switch expression { + switch expression.content { | EBlock(statements) => `{${Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin}}` | EProgram(statements) => Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin @@ -31,13 +31,13 @@ let rec toString = (expression: t) => let toStringResult = codeResult => switch codeResult { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + | Error(m) => `Error(${Reducer_ErrorValue.errorValueToString(m)})` } let toStringResultOkless = codeResult => switch codeResult { | Ok(a) => toString(a) - | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + | Error(m) => `Error(${Reducer_ErrorValue.errorValueToString(m)})` } let inspect = (expr: t): t => { diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy index 4d8cc135..0cd28a86 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.peggy @@ -12,21 +12,21 @@ zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda outerBlock = statements:array_statements finalExpression: (statementSeparator @expression)? { if (finalExpression) statements.push(finalExpression) - return h.nodeProgram(statements) } + return h.nodeProgram(statements, location()) } / finalExpression: expression - { return h.nodeProgram([finalExpression]) } + { return h.nodeProgram([finalExpression], location()) } innerBlockOrExpression = quotedInnerBlock / finalExpression: expression - { return h.nodeBlock([finalExpression])} + { return h.nodeBlock([finalExpression], location())} quotedInnerBlock = '{' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' { if (finalExpression) statements.push(finalExpression) - return h.nodeBlock(statements) } + return h.nodeBlock(statements, location()) } / '{' _nl finalExpression: expression _nl '}' - { return h.nodeBlock([finalExpression]) } + { return h.nodeBlock([finalExpression], location()) } array_statements = head:statement tail:(statementSeparator @array_statements ) @@ -42,16 +42,16 @@ statement voidStatement = "call" _nl value:zeroOMoreArgumentsBlockOrExpression { var variable = h.nodeIdentifier("_", location()); - return h.nodeLetStatement(variable, value); } + return h.nodeLetStatement(variable, value, location()); } letStatement = variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression - { return h.nodeLetStatement(variable, value) } + { return h.nodeLetStatement(variable, value, location()) } defunStatement = variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression - { var value = h.nodeLambda(args, body) - return h.nodeLetStatement(variable, value) } + { var value = h.nodeLambda(args, body, location()) + return h.nodeLetStatement(variable, value, location()) } assignmentOp "assignment" = '=' @@ -67,16 +67,16 @@ ifthenelse = 'if' __nl condition:logicalAdditive __nl 'then' __nl trueExpression:innerBlockOrExpression __nl 'else' __nl falseExpression:(ifthenelse/innerBlockOrExpression) - { return h.nodeTernary(condition, trueExpression, falseExpression) } + { return h.nodeTernary(condition, trueExpression, falseExpression, location()) } ternary = condition:logicalAdditive _ '?' _nl trueExpression:logicalAdditive _ ':' _nl falseExpression:(ternary/logicalAdditive) - { return h.nodeTernary(condition, trueExpression, falseExpression) } + { return h.nodeTernary(condition, trueExpression, falseExpression, location()) } logicalAdditive = head:logicalMultiplicative tail:(_ operator:logicalAdditiveOp _nl arg:logicalMultiplicative {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} logicalAdditiveOp "operator" = '||' @@ -85,21 +85,21 @@ logicalAdditive logicalMultiplicative = head:equality tail:(_ operator:logicalMultiplicativeOp _nl arg:equality {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} logicalMultiplicativeOp "operator" = '&&' equality = left:relational _ operator:equalityOp _nl right:relational - { return h.makeFunctionCall(h.toFunction[operator], [left, right])} + { return h.makeFunctionCall(h.toFunction[operator], [left, right], location())} / relational equalityOp "operator" = '=='/'!=' relational = left:additive _ operator:relationalOp _nl right:additive - { return h.makeFunctionCall(h.toFunction[operator], [left, right])} + { return h.makeFunctionCall(h.toFunction[operator], [left, right], location())} / additive relationalOp "operator" = '<='/'<'/'>='/'>' @@ -107,7 +107,7 @@ relational additive = head:multiplicative tail:(_ operator:additiveOp _nl arg:multiplicative {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} additiveOp "operator" = '+' / '-' / '.+' / '.-' @@ -115,7 +115,7 @@ additive multiplicative = head:power tail:(_ operator:multiplicativeOp _nl arg:power {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} multiplicativeOp "operator" = '*' / '/' / '.*' / './' @@ -123,7 +123,7 @@ multiplicative power = head:credibleInterval tail:(_ operator:powerOp _nl arg:credibleInterval {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} powerOp "operator" = '^' / '.^' @@ -131,7 +131,7 @@ power credibleInterval = head:chainFunctionCall tail:(__ operator:credibleIntervalOp __nl arg:chainFunctionCall {return {operator: operator, right: arg}})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right]) + return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right], location()) }, head)} credibleIntervalOp "operator" = 'to' @@ -139,7 +139,7 @@ credibleInterval chainFunctionCall = head:unary tail:(_ ('->'/'|>') _nl chained:chainedFunction {return chained})* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(element.fnName, [result, ...element.args]) + return h.makeFunctionCall(element.fnName, [result, ...element.args], location()) }, head)} chainedFunction @@ -154,7 +154,7 @@ chainFunctionCall unary = unaryOperator:unaryOperator _nl right:(unary/postOperator) - { return h.makeFunctionCall(h.unaryToFunction[unaryOperator], [right])} + { return h.makeFunctionCall(h.unaryToFunction[unaryOperator], [right], location())} / postOperator unaryOperator "unary operator" @@ -169,17 +169,17 @@ collectionElement tail:( _ '[' _nl arg:expression _nl ']' {return {fn: h.postOperatorToFunction['[]'], args: [arg]}} / _ '(' _nl args:array_functionArguments _nl ')' {return {fn: h.postOperatorToFunction['()'], args: args}} - / '.' arg:$dollarIdentifier {return {fn: h.postOperatorToFunction['[]'], args: [h.nodeString(arg)]}} + / '.' arg:$dollarIdentifier {return {fn: h.postOperatorToFunction['[]'], args: [h.nodeString(arg, location())]}} )* { return tail.reduce(function(result, element) { - return h.makeFunctionCall(element.fn, [result, ...element.args]) + return h.makeFunctionCall(element.fn, [result, ...element.args], location()) }, head)} array_functionArguments = head:expression tail:(_ ',' _nl @expression)* { return [head, ...tail]; } / "" - {return [h.nodeVoid()];} + {return [h.nodeVoid(location())];} atom = '(' _nl expression:expression _nl ')' {return expression} @@ -195,7 +195,7 @@ basicLiteral / voidLiteral voidLiteral 'void' - = "()" {return h.nodeVoid();} + = "()" {return h.nodeVoid(location());} variable = dollarIdentifierWithModule / dollarIdentifier @@ -221,36 +221,36 @@ dollarIdentifier '$identifier' = ([\$_a-z]+[\$_a-z0-9]i*) {return h.nodeIdentifier(text(), location())} moduleIdentifier 'identifier' - = ([A-Z]+[_a-z0-9]i*) {return h.nodeModuleIdentifier(text())} + = ([A-Z]+[_a-z0-9]i*) {return h.nodeModuleIdentifier(text(), location())} string 'string' - = characters:("'" @([^'])* "'") {return h.nodeString(characters.join(''))} - / characters:('"' @([^"])* '"') {return h.nodeString(characters.join(''))} + = characters:("'" @([^'])* "'") {return h.nodeString(characters.join(''), location())} + / characters:('"' @([^"])* '"') {return h.nodeString(characters.join(''), location())} number = number:(float / integer) unit:unitIdentifier? { if (unit === null) { return number } else - { return h.makeFunctionCall('fromUnit_'+unit.value, [number]) + { return h.makeFunctionCall('fromUnit_'+unit.value, [number], location()) } } integer 'integer' = d+ !"\." ![e]i - { return h.nodeInteger(parseInt(text()))} + { return h.nodeInteger(parseInt(text()), location())} float 'float' = $(((d+ "\." d*) / ("\." d+)) floatExponent? / d+ floatExponent) - { return h.nodeFloat(parseFloat(text()))} + { return h.nodeFloat(parseFloat(text()), location())} floatExponent = [e]i '-'? d+ d = [0-9] boolean 'boolean' = ('true'/'false') ! [a-z]i ! [_$] - { return h.nodeBoolean(text() === 'true')} + { return h.nodeBoolean(text() === 'true', location())} valueConstructor = recordConstructor @@ -261,15 +261,15 @@ valueConstructor lambda = '{' _nl '|' _nl args:array_parameters _nl '|' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' { statements.push(finalExpression) - return h.nodeLambda(args, h.nodeBlock(statements)) } + return h.nodeLambda(args, h.nodeBlock(statements, location()), location()) } / '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}' - { return h.nodeLambda(args, finalExpression) } + { return h.nodeLambda(args, finalExpression, location()) } arrayConstructor 'array' = '[' _nl ']' - { return h.constructArray([]); } + { return h.constructArray([], location()); } / '[' _nl args:array_elements _nl ']' - { return h.constructArray(args); } + { return h.constructArray(args, location()); } array_elements = head:expression tail:(_ ',' _nl @expression)* @@ -277,7 +277,7 @@ arrayConstructor 'array' recordConstructor 'record' = '{' _nl args:array_recordArguments _nl end_of_record - { return h.constructRecord(args); } + { return h.constructRecord(args, location()); } end_of_record = '}' @@ -289,7 +289,7 @@ recordConstructor 'record' keyValuePair = key:expression _ ':' _nl value:expression - { return h.nodeKeyValue(key, value)} + { return h.nodeKeyValue(key, value, location())} // Separators diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res index 9c59c504..c6e21c9e 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res @@ -1,19 +1,20 @@ module Extra = Reducer_Extra open Reducer_ErrorValue -type node = {"type": string} +type node = {"type": string, "location": Reducer_ErrorValue.location} -@module("./Reducer_Peggy_GeneratedParser.js") external parse__: string => node = "parse" +@module("./Reducer_Peggy_GeneratedParser.js") +external parse__: (string, {"grammarSource": string}) => node = "parse" -type withLocation = {"location": Reducer_ErrorValue.syntaxErrorLocation} +type withLocation = {"location": Reducer_ErrorValue.location} external castWithLocation: Js.Exn.t => withLocation = "%identity" -let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.syntaxErrorLocation => +let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.location => castWithLocation(error)["location"] -let parse = (expr: string): result => +let parse = (expr: string, source: string): result => try { - Ok(parse__(expr)) + Ok(parse__(expr, {"grammarSource": source})) } catch { | Js.Exn.Error(obj) => RESyntaxError(Belt.Option.getExn(Js.Exn.message(obj)), syntaxErrorToLocation(obj)->Some)->Error @@ -34,27 +35,30 @@ type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node} type nodeModuleIdentifier = {...node, "value": string} type nodeString = {...node, "value": string} type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node} -// type nodeTypeIdentifier = {...node, "value": string} type nodeVoid = node -type peggyNode = - | PgNodeBlock(nodeBlock) - | PgNodeProgram(nodeProgram) - | PgNodeArray(nodeArray) - | PgNodeRecord(nodeRecord) - | PgNodeBoolean(nodeBoolean) - | PgNodeFloat(nodeFloat) - | PgNodeCall(nodeCall) - | PgNodeIdentifier(nodeIdentifier) - | PgNodeInteger(nodeInteger) - | PgNodeKeyValue(nodeKeyValue) - | PgNodeLambda(nodeLambda) - | PgNodeLetStatement(nodeLetStatement) - | PgNodeModuleIdentifier(nodeModuleIdentifier) - | PgNodeString(nodeString) - | PgNodeTernary(nodeTernary) - // | PgNodeTypeIdentifier(nodeTypeIdentifier) - | PgNodeVoid(nodeVoid) +type astContent = + | ASTBlock(nodeBlock) + | ASTProgram(nodeProgram) + | ASTArray(nodeArray) + | ASTRecord(nodeRecord) + | ASTBoolean(nodeBoolean) + | ASTFloat(nodeFloat) + | ASTCall(nodeCall) + | ASTIdentifier(nodeIdentifier) + | ASTInteger(nodeInteger) + | ASTKeyValue(nodeKeyValue) + | ASTLambda(nodeLambda) + | ASTLetStatement(nodeLetStatement) + | ASTModuleIdentifier(nodeModuleIdentifier) + | ASTString(nodeString) + | ASTTernary(nodeTernary) + | ASTVoid(nodeVoid) + +type ast = { + location: location, + content: astContent, +} external castNodeBlock: node => nodeBlock = "%identity" external castNodeProgram: node => nodeProgram = "%identity" @@ -71,80 +75,87 @@ external castNodeLetStatement: node => nodeLetStatement = "%identity" external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity" external castNodeString: node => nodeString = "%identity" external castNodeTernary: node => nodeTernary = "%identity" -// external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity" external castNodeVoid: node => nodeVoid = "%identity" exception UnsupportedPeggyNodeType(string) // This should never happen; programming error -let castNodeType = (node: node) => - switch node["type"] { - | "Block" => node->castNodeBlock->PgNodeBlock - | "Program" => node->castNodeBlock->PgNodeProgram - | "Array" => node->castNodeArray->PgNodeArray - | "Record" => node->castNodeRecord->PgNodeRecord - | "Boolean" => node->castNodeBoolean->PgNodeBoolean - | "Call" => node->castNodeCall->PgNodeCall - | "Float" => node->castNodeFloat->PgNodeFloat - | "Identifier" => node->castNodeIdentifier->PgNodeIdentifier - | "Integer" => node->castNodeInteger->PgNodeInteger - | "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue - | "Lambda" => node->castNodeLambda->PgNodeLambda - | "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement - | "ModuleIdentifier" => node->castNodeModuleIdentifier->PgNodeModuleIdentifier - | "String" => node->castNodeString->PgNodeString - | "Ternary" => node->castNodeTernary->PgNodeTernary - // | "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier - | "Void" => node->castNodeVoid->PgNodeVoid +let nodeToAST = (node: node) => { + let content = switch node["type"] { + | "Block" => node->castNodeBlock->ASTBlock + | "Program" => node->castNodeBlock->ASTProgram + | "Array" => node->castNodeArray->ASTArray + | "Record" => node->castNodeRecord->ASTRecord + | "Boolean" => node->castNodeBoolean->ASTBoolean + | "Call" => node->castNodeCall->ASTCall + | "Float" => node->castNodeFloat->ASTFloat + | "Identifier" => node->castNodeIdentifier->ASTIdentifier + | "Integer" => node->castNodeInteger->ASTInteger + | "KeyValue" => node->castNodeKeyValue->ASTKeyValue + | "Lambda" => node->castNodeLambda->ASTLambda + | "LetStatement" => node->castNodeLetStatement->ASTLetStatement + | "ModuleIdentifier" => node->castNodeModuleIdentifier->ASTModuleIdentifier + | "String" => node->castNodeString->ASTString + | "Ternary" => node->castNodeTernary->ASTTernary + | "Void" => node->castNodeVoid->ASTVoid | _ => raise(UnsupportedPeggyNodeType(node["type"])) } -let rec pgToString = (peggyNode: peggyNode): string => { + {location: node["location"], content: content} +} + +let nodeIdentifierToAST = (node: nodeIdentifier) => { + {location: node["location"], content: node->ASTIdentifier} +} + +let nodeKeyValueToAST = (node: nodeKeyValue) => { + {location: node["location"], content: node->ASTKeyValue} +} + +let rec pgToString = (ast: ast): string => { let argsToString = (args: array): string => - args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString + args->Belt.Array.map(arg => arg->nodeIdentifierToAST->pgToString)->Js.Array2.toString let nodesToStringUsingSeparator = (nodes: array, separator: string): string => - nodes->Js.Array2.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") + nodes->Belt.Array.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") - let pgNodesToStringUsingSeparator = (nodes: array, separator: string): string => - nodes->Js.Array2.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") + let pgNodesToStringUsingSeparator = (nodes: array, separator: string): string => + nodes->Belt.Array.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") - switch peggyNode { - | PgNodeBlock(node) - | PgNodeProgram(node) => + switch ast.content { + | ASTBlock(node) + | ASTProgram(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}" - | PgNodeArray(node) => "[" ++ node["elements"]->nodesToStringUsingSeparator("; ") ++ "]" - | PgNodeRecord(node) => + | ASTArray(node) => "[" ++ node["elements"]->nodesToStringUsingSeparator("; ") ++ "]" + | ASTRecord(node) => "{" ++ node["elements"] - ->Js.Array2.map(element => PgNodeKeyValue(element)) + ->Belt.Array.map(element => element->nodeKeyValueToAST) ->pgNodesToStringUsingSeparator(", ") ++ "}" - | PgNodeBoolean(node) => node["value"]->Js.String.make - | PgNodeCall(node) => + | ASTBoolean(node) => node["value"]->Js.String.make + | ASTCall(node) => "(" ++ node["fn"]->toString ++ " " ++ node["args"]->nodesToStringUsingSeparator(" ") ++ ")" - | PgNodeFloat(node) => node["value"]->Js.String.make - | PgNodeIdentifier(node) => `:${node["value"]}` - | PgNodeInteger(node) => node["value"]->Js.String.make - | PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"]) - | PgNodeLambda(node) => - "{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}" - | PgNodeLetStatement(node) => - pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"]) - | PgNodeModuleIdentifier(node) => `@${node["value"]}` - | PgNodeString(node) => `'${node["value"]->Js.String.make}'` - | PgNodeTernary(node) => + | ASTFloat(node) => node["value"]->Js.String.make + | ASTIdentifier(node) => `:${node["value"]}` + | ASTInteger(node) => node["value"]->Js.String.make + | ASTKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"]) + | ASTLambda(node) => "{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}" + | ASTLetStatement(node) => + pgToString(node["variable"]->nodeIdentifierToAST) ++ " = " ++ toString(node["value"]) + | ASTModuleIdentifier(node) => `@${node["value"]}` + | ASTString(node) => `'${node["value"]->Js.String.make}'` + | ASTTernary(node) => "(::$$_ternary_$$ " ++ toString(node["condition"]) ++ " " ++ toString(node["trueExpression"]) ++ " " ++ toString(node["falseExpression"]) ++ ")" - // | PgNodeTypeIdentifier(node) => `#${node["value"]}` - | PgNodeVoid(_node) => "()" + | ASTVoid(_node) => "()" } } -and toString = (node: node): string => node->castNodeType->pgToString +and toString = (node: node): string => node->nodeToAST->pgToString let toStringResult = (rNode: result): string => switch rNode { | Ok(node) => toString(node) - | Error(error) => `Error(${errorToString(error)})` + | Error(error) => `Error(${errorValueToString(error)})` } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res index ef8ed75b..6c187476 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression.res @@ -3,62 +3,72 @@ module ExpressionT = Reducer_Expression_T module Parse = Reducer_Peggy_Parse type expression = Reducer_T.expression +type expressionContent = Reducer_T.expressionContent let rec fromNode = (node: Parse.node): expression => { - let caseBlock = nodeBlock => - ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)) + let ast = Parse.nodeToAST(node) - let caseProgram = nodeProgram => - ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode)) + let content: expressionContent = { + let caseBlock = nodeBlock => + ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)) - let caseLambda = (nodeLambda: Parse.nodeLambda): expression => { - let args = - nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) - let body = nodeLambda["body"]->fromNode + let caseProgram = nodeProgram => + ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode)) - ExpressionBuilder.eLambda(args, body) + let caseLambda = (nodeLambda: Parse.nodeLambda): expressionContent => { + let args = + nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) + let body = nodeLambda["body"]->fromNode + + ExpressionBuilder.eLambda(args, body) + } + + let caseRecord = (nodeRecord): expressionContent => { + nodeRecord["elements"] + ->Js.Array2.map(keyValueNode => ( + keyValueNode["key"]->fromNode, + keyValueNode["value"]->fromNode, + )) + ->ExpressionBuilder.eRecord + } + + switch ast.content { + | ASTBlock(nodeBlock) => caseBlock(nodeBlock) + | ASTProgram(nodeProgram) => caseProgram(nodeProgram) + | ASTArray(nodeArray) => + ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode)) + | ASTRecord(nodeRecord) => caseRecord(nodeRecord) + | ASTBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"]) + | ASTCall(nodeCall) => + ExpressionBuilder.eCall(fromNode(nodeCall["fn"]), nodeCall["args"]->Js.Array2.map(fromNode)) + | ASTFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"]) + | ASTIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"]) + | ASTInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"])) + | ASTKeyValue(nodeKeyValue) => + ExpressionBuilder.eArray([fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])]) + | ASTLambda(nodeLambda) => caseLambda(nodeLambda) + | ASTLetStatement(nodeLetStatement) => + ExpressionBuilder.eLetStatement( + nodeLetStatement["variable"]["value"], + fromNode(nodeLetStatement["value"]), + ) + | ASTModuleIdentifier(nodeModuleIdentifier) => + ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"]) + | ASTString(nodeString) => ExpressionBuilder.eString(nodeString["value"]) + | ASTTernary(nodeTernary) => + ExpressionBuilder.eTernary( + fromNode(nodeTernary["condition"]), + fromNode(nodeTernary["trueExpression"]), + fromNode(nodeTernary["falseExpression"]), + ) + // | PgNodeTypeIdentifier(nodeTypeIdentifier) => + // ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"]) + | ASTVoid(_) => ExpressionBuilder.eVoid + } } - let caseRecord = (nodeRecord): expression => { - nodeRecord["elements"] - ->Js.Array2.map(keyValueNode => ( - keyValueNode["key"]->fromNode, - keyValueNode["value"]->fromNode, - )) - ->ExpressionBuilder.eRecord - } - - switch Parse.castNodeType(node) { - | PgNodeBlock(nodeBlock) => caseBlock(nodeBlock) - | PgNodeProgram(nodeProgram) => caseProgram(nodeProgram) - | PgNodeArray(nodeArray) => - ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode)) - | PgNodeRecord(nodeRecord) => caseRecord(nodeRecord) - | PgNodeBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"]) - | PgNodeCall(nodeCall) => - ExpressionBuilder.eCall(fromNode(nodeCall["fn"]), nodeCall["args"]->Js.Array2.map(fromNode)) - | PgNodeFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"]) - | PgNodeIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"]) - | PgNodeInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"])) - | PgNodeKeyValue(nodeKeyValue) => - ExpressionBuilder.eArray([fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])]) - | PgNodeLambda(nodeLambda) => caseLambda(nodeLambda) - | PgNodeLetStatement(nodeLetStatement) => - ExpressionBuilder.eLetStatement( - nodeLetStatement["variable"]["value"], - fromNode(nodeLetStatement["value"]), - ) - | PgNodeModuleIdentifier(nodeModuleIdentifier) => - ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"]) - | PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"]) - | PgNodeTernary(nodeTernary) => - ExpressionBuilder.eTernary( - fromNode(nodeTernary["condition"]), - fromNode(nodeTernary["trueExpression"]), - fromNode(nodeTernary["falseExpression"]), - ) - // | PgNodeTypeIdentifier(nodeTypeIdentifier) => - // ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"]) - | PgNodeVoid(_) => ExpressionBuilder.eVoid + { + ast: ast, + content: content, } } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts index f24fd819..6e9fe80c 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/helpers.ts @@ -34,83 +34,91 @@ export const postOperatorToFunction = { "[]": "$_atIndex_$", }; -type NodeBlock = { +type Node = { + location: LocationRange; +}; + +type NodeBlock = Node & { type: "Block"; statements: AnyPeggyNode[]; }; -type NodeProgram = { +type NodeProgram = Node & { type: "Program"; statements: AnyPeggyNode[]; }; -type NodeArray = { +type NodeArray = Node & { type: "Array"; elements: AnyPeggyNode[]; }; -type NodeRecord = { +type NodeRecord = Node & { type: "Record"; elements: NodeKeyValue[]; }; -type NodeCall = { +type NodeCall = Node & { type: "Call"; fn: AnyPeggyNode; args: AnyPeggyNode[]; }; -type NodeFloat = { +type NodeFloat = Node & { type: "Float"; value: number; }; -type NodeInteger = { +type NodeInteger = Node & { type: "Integer"; value: number; }; -type NodeIdentifier = { +type NodeIdentifier = Node & { type: "Identifier"; value: string; }; -type NodeLetStatement = { +type NodeLetStatement = Node & { type: "LetStatement"; variable: NodeIdentifier; value: AnyPeggyNode; }; -type NodeLambda = { +type NodeLambda = Node & { type: "Lambda"; args: AnyPeggyNode[]; body: AnyPeggyNode; }; -type NodeTernary = { +type NodeTernary = Node & { type: "Ternary"; condition: AnyPeggyNode; trueExpression: AnyPeggyNode; falseExpression: AnyPeggyNode; }; -type NodeKeyValue = { +type NodeKeyValue = Node & { type: "KeyValue"; key: AnyPeggyNode; value: AnyPeggyNode; }; -type NodeString = { +type NodeString = Node & { type: "String"; value: string; location?: LocationRange; }; -type NodeBoolean = { +type NodeBoolean = Node & { type: "Boolean"; value: boolean; }; +type NodeVoid = Node & { + type: "Void"; +}; + export type AnyPeggyNode = | NodeArray | NodeRecord @@ -125,47 +133,78 @@ export type AnyPeggyNode = | NodeTernary | NodeKeyValue | NodeString - | NodeBoolean; + | NodeBoolean + | NodeVoid; -export function makeFunctionCall(fn: string, args: AnyPeggyNode[]) { +export function makeFunctionCall( + fn: string, + args: AnyPeggyNode[], + location: LocationRange +) { if (fn === "$$_applyAll_$$") { - return nodeCall(args[0], args.splice(1)); + return nodeCall(args[0], args.splice(1), location); } else { - return nodeCall(nodeIdentifier(fn), args); + return nodeCall(nodeIdentifier(fn, location), args, location); } } -export function constructArray(elements: AnyPeggyNode[]) { - return { type: "Array", elements }; +export function constructArray( + elements: AnyPeggyNode[], + location: LocationRange +): NodeArray { + return { type: "Array", elements, location }; } -export function constructRecord(elements: AnyPeggyNode[]) { - return { type: "Record", elements }; +export function constructRecord( + elements: NodeKeyValue[], + location: LocationRange +): NodeRecord { + return { type: "Record", elements, location }; } -export function nodeBlock(statements: AnyPeggyNode[]): NodeBlock { - return { type: "Block", statements }; +export function nodeBlock( + statements: AnyPeggyNode[], + location: LocationRange +): NodeBlock { + return { type: "Block", statements, location }; } -export function nodeProgram(statements: AnyPeggyNode[]): NodeProgram { - return { type: "Program", statements }; +export function nodeProgram( + statements: AnyPeggyNode[], + location: LocationRange +): NodeProgram { + return { type: "Program", statements, location }; } -export function nodeBoolean(value: boolean): NodeBoolean { - return { type: "Boolean", value }; +export function nodeBoolean( + value: boolean, + location: LocationRange +): NodeBoolean { + return { type: "Boolean", value, location }; } -export function nodeCall(fn: AnyPeggyNode, args: AnyPeggyNode[]): NodeCall { - return { type: "Call", fn, args }; +export function nodeCall( + fn: AnyPeggyNode, + args: AnyPeggyNode[], + location: LocationRange +): NodeCall { + return { type: "Call", fn, args, location }; } -export function nodeFloat(value: number): NodeFloat { - return { type: "Float", value }; +export function nodeFloat(value: number, location: LocationRange): NodeFloat { + return { type: "Float", value, location }; } -export function nodeIdentifier(value: string): NodeIdentifier { - return { type: "Identifier", value }; +export function nodeIdentifier( + value: string, + location: LocationRange +): NodeIdentifier { + return { type: "Identifier", value, location }; } -export function nodeInteger(value: number): NodeInteger { - return { type: "Integer", value }; +export function nodeInteger( + value: number, + location: LocationRange +): NodeInteger { + return { type: "Integer", value, location }; } export function nodeKeyValue( key: AnyPeggyNode, - value: AnyPeggyNode + value: AnyPeggyNode, + location: LocationRange ): NodeKeyValue { if (key.type === "Identifier") { key = { @@ -173,43 +212,43 @@ export function nodeKeyValue( type: "String", }; } - return { type: "KeyValue", key, value }; + return { type: "KeyValue", key, value, location }; } export function nodeLambda( args: AnyPeggyNode[], - body: AnyPeggyNode + body: AnyPeggyNode, + location: LocationRange ): NodeLambda { - return { type: "Lambda", args, body }; + return { type: "Lambda", args, body, location }; } export function nodeLetStatement( variable: NodeIdentifier, - value: AnyPeggyNode + value: AnyPeggyNode, + location: LocationRange ): NodeLetStatement { - return { type: "LetStatement", variable, value }; + return { type: "LetStatement", variable, value, location }; } -export function nodeModuleIdentifier(value: string) { - return { type: "ModuleIdentifier", value }; +export function nodeModuleIdentifier(value: string, location: LocationRange) { + return { type: "ModuleIdentifier", value, location }; } -export function nodeString(value: string): NodeString { - return { type: "String", value }; +export function nodeString(value: string, location: LocationRange): NodeString { + return { type: "String", value, location }; } export function nodeTernary( condition: AnyPeggyNode, trueExpression: AnyPeggyNode, - falseExpression: AnyPeggyNode + falseExpression: AnyPeggyNode, + location: LocationRange ): NodeTernary { return { type: "Ternary", condition, trueExpression, falseExpression, + location, }; } -export function nodeTypeIdentifier(typeValue: string) { - return { type: "TypeIdentifier", value: typeValue }; -} - -export function nodeVoid() { - return { type: "Void" }; +export function nodeVoid(location: LocationRange): NodeVoid { + return { type: "Void", location }; } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res index 85546905..2a704b23 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -22,7 +22,7 @@ and lambdaValue = { body: lambdaBody, } @genType.opaque and lambdaDeclaration = Declaration.declaration -and expression = +and expressionContent = | EBlock(array) // programs are similar to blocks, but don't create an inner scope. there can be only one program at the top level of the expression. | EProgram(array) @@ -35,6 +35,11 @@ and expression = | ELambda(array, expression) | EValue(value) +and expression = { + ast: Reducer_Peggy_Parse.ast, + content: expressionContent, +} + and namespace = Belt.Map.String.t and bindings = { namespace: namespace, diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res index cd1eefef..f7681ee7 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res @@ -79,24 +79,12 @@ let toStringResult = x => | Error(m) => `Error(${ErrorValue.errorToString(m)})` } -let toStringOptionResult = x => - switch x { - | Some(a) => toStringResult(a) - | None => "None" - } - -let toStringResultOkless = (codeResult: result): string => +let toStringResultOkless = (codeResult: result): string => switch codeResult { | Ok(a) => toString(a) | Error(m) => `Error(${ErrorValue.errorToString(m)})` } -let toStringResultRecord = x => - switch x { - | Ok(a) => `Ok(${toStringMap(a)})` - | Error(m) => `Error(${ErrorValue.errorToString(m)})` - } - type internalExpressionValueType = | EvtArray | EvtBool diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res index 1942a136..082b620c 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res @@ -117,7 +117,7 @@ let getResultOption = (project: t, sourceId: string): ProjectItem.T.resultType = let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType => switch getResultOption(project, sourceId) { - | None => RENeedToRun->Error + | None => RENeedToRun->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue->Error | Some(result) => result } @@ -171,7 +171,7 @@ let linkDependencies = (project: t, sourceId: string): Reducer_T.namespace => { "__result__", switch project->getResult(id) { | Ok(result) => result - | Error(error) => error->Reducer_ErrorValue.ErrorException->raise + | Error(error) => error->Reducer_ErrorValue.ExceptionWithStackTrace->raise }, ), ]) diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 93d434a3..4313f6e1 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res @@ -4,8 +4,9 @@ module T = ReducerProject_ProjectItem_T type projectItem = T.projectItem type t = T.t -let emptyItem: projectItem = { +let emptyItem = (sourceId: string): projectItem => { source: "", + sourceId: sourceId, rawParse: None, expression: None, continuation: Reducer_Namespace.make(), @@ -18,6 +19,7 @@ let emptyItem: projectItem = { // source -> rawParse -> includes -> expression -> continuation -> result let getSource = (r: t): T.sourceType => r.source +let getSourceId = (r: t): T.sourceType => r.sourceId let getRawParse = (r: t): T.rawParseType => r.rawParse let getExpression = (r: t): T.expressionType => r.expression let getContinuation = (r: t): T.continuationArgumentType => r.continuation @@ -29,7 +31,7 @@ let getDirectIncludes = (r: t): array => r.directIncludes let getIncludesAsVariables = (r: t): T.importAsVariablesType => r.includeAsVariables let touchSource = (this: t): t => { - let r = emptyItem + let r = emptyItem(this->getSourceId) { ...r, source: getSource(this), @@ -41,8 +43,9 @@ let touchSource = (this: t): t => { } let touchRawParse = (this: t): t => { + let r = emptyItem(this->getSourceId) { - ...emptyItem, + ...r, source: getSource(this), continues: getContinues(this), includes: getIncludes(this), @@ -148,7 +151,8 @@ let parseIncludes = (this: t): t => { } } } -let doRawParse = (this: t): T.rawParseArgumentType => this->getSource->Reducer_Peggy_Parse.parse +let doRawParse = (this: t): T.rawParseArgumentType => + this->getSource->Reducer_Peggy_Parse.parse(this.sourceId) let rawParse = (this: t): t => this->getRawParse->E.O2.defaultFn(() => doRawParse(this))->setRawParse(this, _) @@ -167,7 +171,7 @@ let buildExpression = (this: t): t => { } } -let failRun = (this: t, e: Reducer_ErrorValue.errorValue): t => +let failRun = (this: t, e: Reducer_ErrorValue.error): t => this->setResult(e->Error)->setContinuation(Reducer_Namespace.make()) let doRun = (this: t, context: Reducer_T.context): t => @@ -181,12 +185,24 @@ let doRun = (this: t, context: Reducer_T.context): t => ->setResult(result->Ok) ->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals) } catch { - | Reducer_ErrorValue.ErrorException(e) => this->failRun(e) - | _ => this->failRun(RETodo("unhandled rescript exception")) + | Reducer_ErrorValue.ErrorException(e) => + this->failRun(e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) + | Reducer_ErrorValue.ExceptionWithStackTrace(e) => this->failRun(e) + | _ => + this->failRun( + RETodo( + "unhandled rescript exception", + )->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue, + ) } - | Error(e) => this->failRun(e) + | Error(e) => this->failRun(e->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue) } - | None => this->failRun(RETodo("attempt to run without expression")) + | None => + this->failRun( + RETodo( + "attempt to run without expression", + )->Reducer_ErrorValue.attachEmptyStackTraceToErrorValue, + ) } let run = (this: t, context: Reducer_T.context): t => { diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res index bf1cbfcb..5d32a161 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -11,7 +11,7 @@ type expressionType = option type continuationArgumentType = Reducer_T.namespace type continuationType = option type continuationResultType = option> -type resultArgumentType = result +type resultArgumentType = result type resultType = option type continuesArgumentType = array type continuesType = array @@ -21,6 +21,7 @@ type importAsVariablesType = array<(string, string)> type projectItem = { source: sourceType, + sourceId: string, rawParse: rawParseType, expression: expressionType, continuation: continuationArgumentType, diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res index 0eac623a..bb0ae2ee 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_T.res @@ -13,4 +13,4 @@ type t = project let getSourceIds = (project: t): array => Belt.MutableMap.String.keysToArray(project.items) let getItem = (project: t, sourceId: string) => - Belt.MutableMap.String.getWithDefault(project.items, sourceId, ProjectItem.emptyItem) + Belt.MutableMap.String.getWithDefault(project.items, sourceId, ProjectItem.emptyItem(sourceId))