diff --git a/packages/components/src/components/Alert.tsx b/packages/components/src/components/Alert.tsx index bc2e2f92..cb579536 100644 --- a/packages/components/src/components/Alert.tsx +++ b/packages/components/src/components/Alert.tsx @@ -30,7 +30,7 @@ export const Alert: React.FC<{ className={clsx("h-5 w-5 flex-shrink-0", iconColor)} aria-hidden="true" /> -
+
{heading}
diff --git a/packages/components/src/components/CodeEditor.tsx b/packages/components/src/components/CodeEditor.tsx index 15802131..ec2fdee7 100644 --- a/packages/components/src/components/CodeEditor.tsx +++ b/packages/components/src/components/CodeEditor.tsx @@ -5,6 +5,8 @@ import AceEditor from "react-ace"; import "ace-builds/src-noconflict/mode-golang"; import "ace-builds/src-noconflict/theme-github"; +import { SqLocation } from "@quri/squiggle-lang"; + interface CodeEditorProps { value: string; onChange: (value: string) => void; @@ -13,15 +15,17 @@ interface CodeEditorProps { width?: number; height: number; showGutter?: boolean; + errorLocations?: SqLocation[]; } export const CodeEditor: FC = ({ value, onChange, onSubmit, + height, oneLine = false, showGutter = false, - height, + errorLocations = [], }) => { const lineCount = value.split("\n").length; const id = useMemo(() => _.uniqueId(), []); @@ -30,8 +34,11 @@ export const CodeEditor: FC = ({ const onSubmitRef = useRef(null); onSubmitRef.current = onSubmit; + const editorEl = useRef(null); + return ( = ({ exec: () => onSubmitRef.current?.(), }, ]} + markers={errorLocations?.map((location) => ({ + startRow: location.start.line - 1, + startCol: location.start.column - 1, + endRow: location.end.line - 1, + endCol: location.end.column - 1, + className: "ace-error-marker", + type: "text", + }))} /> ); }; diff --git a/packages/components/src/components/FunctionChart.tsx b/packages/components/src/components/FunctionChart.tsx index 2da5d367..fda699e9 100644 --- a/packages/components/src/components/FunctionChart.tsx +++ b/packages/components/src/components/FunctionChart.tsx @@ -1,9 +1,15 @@ import * as React from "react"; -import { SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; +import { + SqLambda, + environment, + SqValueTag, + SqError, +} from "@quri/squiggle-lang"; import { FunctionChart1Dist } from "./FunctionChart1Dist"; import { FunctionChart1Number } from "./FunctionChart1Number"; import { DistributionPlottingSettings } from "./DistributionChart"; -import { ErrorAlert, MessageAlert } from "./Alert"; +import { MessageAlert } from "./Alert"; +import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; export type FunctionChartSettings = { start: number; @@ -19,6 +25,25 @@ interface FunctionChartProps { height: number; } +const FunctionCallErrorAlert = ({ error }: { error: SqError }) => { + const [expanded, setExpanded] = React.useState(false); + if (expanded) { + } + return ( + +
+ setExpanded(!expanded)} + > + {expanded ? "Hide" : "Show"} error details + + {expanded ? : null} +
+
+ ); +}; + export const FunctionChart: React.FC = ({ fn, chartSettings, @@ -26,7 +51,8 @@ export const FunctionChart: React.FC = ({ distributionPlotSettings, height, }) => { - if (fn.parameters.length > 1) { + console.log(fn.parameters().length); + if (fn.parameters().length !== 1) { return ( Only functions with one parameter are displayed. @@ -47,9 +73,7 @@ export const FunctionChart: React.FC = ({ const validResult = getValidResult(); if (validResult.tag === "Error") { - return ( - {validResult.value.toString()} - ); + return ; } switch (validResult.value.tag) { diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 08c0c4ff..215031a7 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -1,14 +1,9 @@ import * as React from "react"; -import { - SqValue, - environment, - resultMap, - SqValueTag, - SqProject, -} from "@quri/squiggle-lang"; +import { SqValue, environment, SqProject } from "@quri/squiggle-lang"; import { useSquiggle } from "../lib/hooks"; import { SquiggleViewer } from "./SquiggleViewer"; import { JsImports } from "../lib/jsImports"; +import { getValueToRender } from "../lib/utility"; export type SquiggleChartProps = { /** The input string for squiggle */ @@ -71,31 +66,61 @@ type ProjectExecutionProps = { }; const defaultOnChange = () => {}; const defaultImports: JsImports = {}; +const defaultContinues: string[] = []; + +export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { + const { + showSummary = false, + logX = false, + expY = false, + diagramStart = 0, + diagramStop = 10, + diagramCount = 20, + tickFormat, + minX, + maxX, + color, + title, + xAxisType = "number", + distributionChartActions, + } = props; + + const distributionPlotSettings = { + showSummary, + logX, + expY, + format: tickFormat, + minX, + maxX, + color, + title, + xAxisType, + actions: distributionChartActions, + }; + + const chartSettings = { + start: diagramStart, + stop: diagramStop, + count: diagramCount, + }; + + return { distributionPlotSettings, chartSettings }; +}; export const SquiggleChart: React.FC = React.memo( - (props: SquiggleChartProps) => { + (props) => { + const { distributionPlotSettings, chartSettings } = + splitSquiggleChartSettings(props); + const { - executionId = 0, - onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here - height = 200, - jsImports = defaultImports, - showSummary = false, - width, - logX = false, - expY = false, - diagramStart = 0, - diagramStop = 10, - diagramCount = 20, - tickFormat, - minX, - maxX, - color, - title, - xAxisType = "number", - distributionChartActions, - enableLocalSettings = false, code, - continues = [], + jsImports = defaultImports, + onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here + executionId = 0, + width, + height = 200, + enableLocalSettings = false, + continues = defaultContinues, } = props; const p = React.useMemo(() => { @@ -110,7 +135,7 @@ export const SquiggleChart: React.FC = React.memo( } }, [props.project, props.environment]); - const { result, bindings } = useSquiggle({ + const resultAndBindings = useSquiggle({ continues, project: p, code, @@ -119,32 +144,11 @@ export const SquiggleChart: React.FC = React.memo( executionId, }); - const distributionPlotSettings = { - showSummary, - logX, - expY, - format: tickFormat, - minX, - maxX, - color, - title, - xAxisType, - actions: distributionChartActions, - }; - - const chartSettings = { - start: diagramStart, - stop: diagramStop, - count: diagramCount, - }; - - const resultToRender = resultMap(result, (value) => - value.tag === SqValueTag.Void ? bindings.asValue() : value - ); + const valueToRender = getValueToRender(resultAndBindings); return ( void; -}> = ({ code, setCode }) => ( + errorLocations?: SqLocation[]; +}> = ({ code, setCode, errorLocations }) => (
); @@ -24,6 +33,9 @@ export type SquiggleEditorProps = SquiggleChartProps & { onCodeChange?: (code: string) => void; }; +const defaultOnChange = () => {}; +const defaultImports: JsImports = {}; + export const SquiggleEditor: React.FC = (props) => { const [code, setCode] = useMaybeControlledValue({ value: props.code, @@ -31,11 +43,54 @@ export const SquiggleEditor: React.FC = (props) => { onChange: props.onCodeChange, }); - let chartProps = { ...props, code }; + const { distributionPlotSettings, chartSettings } = + splitSquiggleChartSettings(props); + + const { + environment, + jsImports = defaultImports, + onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here + executionId = 0, + width, + height = 200, + enableLocalSettings = false, + } = props; + + const project = React.useMemo(() => { + const p = SqProject.create(); + if (environment) { + p.setEnvironment(environment); + } + return p; + }, [environment]); + + const resultAndBindings = useSquiggle({ + code, + project, + jsImports, + onChange, + executionId, + }); + + const valueToRender = getValueToRender(resultAndBindings); + const errorLocations = getErrorLocations(resultAndBindings.result); + return ( - - + + ); }; diff --git a/packages/components/src/components/SquiggleErrorAlert.tsx b/packages/components/src/components/SquiggleErrorAlert.tsx index 49179497..fee0fca5 100644 --- a/packages/components/src/components/SquiggleErrorAlert.tsx +++ b/packages/components/src/components/SquiggleErrorAlert.tsx @@ -1,4 +1,4 @@ -import { SqError } from "@quri/squiggle-lang"; +import { SqError, SqFrame } from "@quri/squiggle-lang"; import React from "react"; import { ErrorAlert } from "./Alert"; @@ -6,6 +6,39 @@ type Props = { error: SqError; }; -export const SquiggleErrorAlert: React.FC = ({ error }) => { - return {error.toString()}; +const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => { + const location = frame.location(); + return ( +
+ {frame.name()} + {location + ? ` at line ${location.start.line}, column ${location.start.column}` + : ""} +
+ ); +}; + +const StackTrace: React.FC = ({ error }) => { + const frames = error.getFrameArray(); + return frames.length ? ( +
+
Stack trace:
+
+ {frames.map((frame, i) => ( + + ))} +
+
+ ) : null; +}; + +export const SquiggleErrorAlert: React.FC = ({ error }) => { + return ( + +
+
{error.toString()}
+ +
+
+ ); }; diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index 8039bbe0..59898d6b 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -8,7 +8,11 @@ import React, { } from "react"; import { useForm, UseFormRegister, useWatch } from "react-hook-form"; import * as yup from "yup"; -import { useMaybeControlledValue, useRunnerState } from "../lib/hooks"; +import { + useMaybeControlledValue, + useRunnerState, + useSquiggle, +} from "../lib/hooks"; import { yupResolver } from "@hookform/resolvers/yup"; import { ChartSquareBarIcon, @@ -24,9 +28,9 @@ import { } from "@heroicons/react/solid"; import clsx from "clsx"; -import { environment } from "@quri/squiggle-lang"; +import { environment, SqProject } from "@quri/squiggle-lang"; -import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; +import { SquiggleChartProps } from "./SquiggleChart"; import { CodeEditor } from "./CodeEditor"; import { JsonEditor } from "./JsonEditor"; import { ErrorAlert, SuccessAlert } from "./Alert"; @@ -40,6 +44,8 @@ import { HeadedSection } from "./ui/HeadedSection"; import { defaultTickFormat } from "../lib/distributionSpecBuilder"; import { Button } from "./ui/Button"; import { JsImports } from "../lib/jsImports"; +import { getErrorLocations, getValueToRender } from "../lib/utility"; +import { SquiggleViewer } from "./SquiggleViewer"; type PlaygroundProps = SquiggleChartProps & { /** The initial squiggle string to put in the playground */ @@ -282,7 +288,7 @@ export const SquigglePlayground: FC = ({ onSettingsChange?.(vars); }, [vars, onSettingsChange]); - const env: environment = useMemo( + const environment: environment = useMemo( () => ({ sampleCount: Number(vars.sampleCount), xyPointLength: Number(vars.xyPointLength), @@ -299,26 +305,59 @@ export const SquigglePlayground: FC = ({ executionId, } = useRunnerState(code); + const project = React.useMemo(() => { + const p = SqProject.create(); + if (environment) { + p.setEnvironment(environment); + } + return p; + }, [environment]); + + const resultAndBindings = useSquiggle({ + code, + project, + jsImports: imports, + executionId, + }); + + const valueToRender = getValueToRender(resultAndBindings); + const squiggleChart = renderedCode === "" ? null : (
{isRunning ? (
) : null} -
); + const errorLocations = getErrorLocations(resultAndBindings.result); + const firstTab = vars.showEditor ? (
= ({
{location.path.items.length ? (
) : null} diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 7f6fed96..63fa3839 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -1,4 +1,10 @@ -import { SqProject, SqValue } from "@quri/squiggle-lang"; +import { + result, + SqError, + SqProject, + SqRecord, + SqValue, +} from "@quri/squiggle-lang"; import { useEffect, useMemo } from "react"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; import * as uuid from "uuid"; @@ -8,29 +14,36 @@ type SquiggleArgs = { executionId?: number; jsImports?: JsImports; project: SqProject; - continues: string[]; + continues?: string[]; onChange?: (expr: SqValue | undefined, sourceName: string) => void; }; -const importSourceName = (sourceName: string) => "imports-" + sourceName; +export type ResultAndBindings = { + result: result; + bindings: SqRecord; +}; -export const useSquiggle = (args: SquiggleArgs) => { +const importSourceName = (sourceName: string) => "imports-" + sourceName; +const defaultContinues = []; + +export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => { const sourceName = useMemo(() => uuid.v4(), []); const env = args.project.getEnvironment(); + const continues = args.continues || defaultContinues; const result = useMemo( () => { const project = args.project; project.setSource(sourceName, args.code); - let continues = args.continues; + let fullContinues = continues; if (args.jsImports && Object.keys(args.jsImports).length) { const importsSource = jsImportsToSquiggleCode(args.jsImports); project.setSource(importSourceName(sourceName), importsSource); - continues = args.continues.concat(importSourceName(sourceName)); + fullContinues = continues.concat(importSourceName(sourceName)); } - project.setContinues(sourceName, continues); + project.setContinues(sourceName, fullContinues); project.run(sourceName); const result = project.getResult(sourceName); const bindings = project.getBindings(sourceName); @@ -45,7 +58,7 @@ export const useSquiggle = (args: SquiggleArgs) => { args.jsImports, args.executionId, sourceName, - args.continues, + continues, args.project, env, ] diff --git a/packages/components/src/lib/utility.ts b/packages/components/src/lib/utility.ts index d4d3661b..cb002954 100644 --- a/packages/components/src/lib/utility.ts +++ b/packages/components/src/lib/utility.ts @@ -1,4 +1,5 @@ -import { result } from "@quri/squiggle-lang"; +import { result, resultMap, SqValueTag } from "@quri/squiggle-lang"; +import { ResultAndBindings } from "./hooks/useSquiggle"; export function flattenResult(x: result[]): result { if (x.length === 0) { @@ -35,3 +36,18 @@ export function all(arr: boolean[]): boolean { export function some(arr: boolean[]): boolean { return arr.reduce((x, y) => x || y, false); } + +export function getValueToRender({ result, bindings }: ResultAndBindings) { + return resultMap(result, (value) => + value.tag === SqValueTag.Void ? bindings.asValue() : value + ); +} + +export function getErrorLocations(result: ResultAndBindings["result"]) { + if (result.tag === "Error") { + const location = result.value.location(); + return location ? [location] : []; + } else { + return []; + } +} diff --git a/packages/components/src/styles/main.css b/packages/components/src/styles/main.css index 987c3714..257c5712 100644 --- a/packages/components/src/styles/main.css +++ b/packages/components/src/styles/main.css @@ -22,3 +22,8 @@ but this line is still necessary for proper initialization of `--tw-*` variables .ace_cursor { border-left: 2px solid !important; } + +.ace-error-marker { + position: absolute; + border-bottom: 1px solid red; +} diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res index 55611d3a..1e1ba209 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_test.res @@ -197,7 +197,7 @@ describe("Peggy parse", () => { describe("lambda", () => { testParse("{|x| x}", "{{|:x| :x}}") - testParse("f={|x| x}", "{:f = {{|:x| :x}}}") + testParse("f={|x| x}", "{:f = {|:x| :x}}") testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments }) 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..1731168f 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 expectExpressionToBe = (expr, answer, ~v="_", ()) => { + 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->SqError.fromParseError) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Reducer_Value.toStringResultOkless (a1, a2)->expect->toEqual((answer, v)) @@ -29,16 +30,16 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { } let testToExpression = (expr, answer, ~v="_", ()) => - test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) + test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) module MyOnly = { let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) let testToExpression = (expr, answer, ~v="_", ()) => - Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) + Only.test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) } module MySkip = { let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) let testToExpression = (expr, answer, ~v="_", ()) => - Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) + Skip.test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) } diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res index cab84681..daeab3f0 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res @@ -135,7 +135,7 @@ describe("Peggy to Expression", () => { describe("lambda", () => { testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ()) - testToExpression("f={|x| x}", "f = {{|x| x}}", ()) + testToExpression("f={|x| x}", "f = {|x| x}", ()) testToExpression("f(x)=x", "f = {|x| {x}}", ()) // Function definitions are lambda assignments testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ()) }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res index 43de902e..efff179e 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res @@ -1,4 +1,3 @@ -module ErrorValue = Reducer_ErrorValue module Expression = Reducer_Expression module ExpressionT = Reducer_Expression_T @@ -9,7 +8,7 @@ let unwrapRecord = rValue => rValue->Belt.Result.flatMap(value => switch value { | Reducer_T.IEvRecord(aRecord) => Ok(aRecord) - | _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error + | _ => SqError.Message.RETodo("TODO: Internal bindings must be returned")->Error } ) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res index 49180135..381c4a81 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_test.res @@ -70,7 +70,7 @@ describe("test exceptions", () => { testDescriptionEvalToBe( "javascript exception", "javascriptraise('div by 0')", - "Error(Error: 'div by 0')", + "Error(JS Exception: Error: 'div by 0')", ) // testDescriptionEvalToBe( // "rescript exception", @@ -78,3 +78,33 @@ describe("test exceptions", () => { // "Error(TODO: unhandled rescript exception)", // ) }) + +describe("stacktraces", () => { + test("nested calls", () => { + open Expect + + let error = + Expression.BackCompatible.evaluateString(` + f(x) = { + y = "a" + x + y + } + g = {|x| f(x)} + h(x) = g(x) + h(5) +`) + ->E.R.getError + ->E.O2.toExn("oops") + ->SqError.toStringWithStackTrace + + expect( + error, + )->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]; [add(date, duration)]; [add(duration, duration)] +Stack trace: + f at line 4, column 5 + g at line 6, column 12 + h at line 7, column 10 + at line 8, column 3 +`) + }) +}) diff --git a/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res b/packages/squiggle-lang/__tests__/ReducerProject/ReducerProject_includes_test.res index 07660044..9c21ff1d 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->SqError.toString) } }) 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->SqError.toString) } }) @@ -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->SqError.toString) } }) 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..5692d93c 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->SqError.toString->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->SqError.toString->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->SqError.toString->fail } }) }) diff --git a/packages/squiggle-lang/__tests__/SqError_test.res b/packages/squiggle-lang/__tests__/SqError_test.res new file mode 100644 index 00000000..55109dde --- /dev/null +++ b/packages/squiggle-lang/__tests__/SqError_test.res @@ -0,0 +1,41 @@ +open Jest +open Expect + +describe("SqError.Message", () => { + test("toString", () => + expect(SqError.Message.REOther("test error")->SqError.Message.toString)->toBe( + "Error: test error", + ) + ) +}) + +describe("SqError", () => { + test("fromMessage", () => + expect(SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toString)->toBe( + "Error: test error", + ) + ) + + test("toStringWithStackTrace with empty stacktrace", () => + expect( + SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toStringWithStackTrace, + )->toBe("Error: test error") + ) + + test("toStringWithStackTrace", () => { + let frameStack = + Reducer_FrameStack.make() + ->Reducer_FrameStack.extend("frame1", None) + ->Reducer_FrameStack.extend("frame2", None) + + expect( + SqError.Message.REOther("test error") + ->SqError.fromMessageWithFrameStack(frameStack) + ->SqError.toStringWithStackTrace, + )->toBe(`Error: test error +Stack trace: + frame2 + frame1 +`) + }) +}) diff --git a/packages/squiggle-lang/scripts/lib.mjs b/packages/squiggle-lang/scripts/lib.mjs index 6f778769..b51bcc7c 100644 --- a/packages/squiggle-lang/scripts/lib.mjs +++ b/packages/squiggle-lang/scripts/lib.mjs @@ -36,6 +36,6 @@ export const run = (src, { output, sampleCount } = {}) => { "Time:", String(time), result.tag === "Error" ? red(result.tag) : green(result.tag), - result.tag === "Error" ? result.value.toString() : "" + result.tag === "Error" ? result.value.toStringWithFrameStack() : "" ); }; 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..5c63d907 100644 --- a/packages/squiggle-lang/src/js/SqError.ts +++ b/packages/squiggle-lang/src/js/SqError.ts @@ -1,17 +1,48 @@ -import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; +import * as RSError from "../rescript/SqError.gen"; +import * as RSReducerT from "../rescript/Reducer/Reducer_T.gen"; + +import * as RSFrameStack from "../rescript/Reducer/Reducer_FrameStack.gen"; + +export { location as SqLocation } from "../rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.gen"; export class SqError { - constructor(private _value: RSErrorValue.reducerErrorValue) {} + constructor(private _value: RSError.t) {} toString() { - return RSErrorValue.toString(this._value); + return RSError.toString(this._value); } - static createTodoError(v: string) { - return new SqError(RSErrorValue.createTodoError(v)); + toStringWithStackTrace() { + return RSError.toStringWithStackTrace(this._value); } static createOtherError(v: string) { - return new SqError(RSErrorValue.createOtherError(v)); + return new SqError(RSError.createOtherError(v)); + } + + getTopFrame(): SqFrame | undefined { + const frame = RSFrameStack.getTopFrame(RSError.getFrameStack(this._value)); + return frame ? new SqFrame(frame) : undefined; + } + + getFrameArray(): SqFrame[] { + const frames = RSError.getFrameArray(this._value); + return frames.map((frame) => new SqFrame(frame)); + } + + location() { + return this.getTopFrame()?.location(); + } +} + +export class SqFrame { + constructor(private _value: RSReducerT.frame) {} + + name(): string { + return RSFrameStack.Frame.getName(this._value); + } + + location() { + return RSFrameStack.Frame.getLocation(this._value); } } diff --git a/packages/squiggle-lang/src/js/SqProject.ts b/packages/squiggle-lang/src/js/SqProject.ts index 84fda5a7..ca65c7ec 100644 --- a/packages/squiggle-lang/src/js/SqProject.ts +++ b/packages/squiggle-lang/src/js/SqProject.ts @@ -1,5 +1,5 @@ import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; -import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; +import * as RSError from "../rescript/SqError.gen"; import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen"; import { SqError } from "./SqError"; import { SqRecord } from "./SqRecord"; @@ -54,7 +54,7 @@ export class SqProject { return resultMap2( RSProject.getIncludes(this._value, sourceId), (a) => a, - (v: reducerErrorValue) => new SqError(v) + (v: RSError.t) => new SqError(v) ); } @@ -108,7 +108,7 @@ export class SqProject { items: [], }) ), - (v: reducerErrorValue) => new SqError(v) + (v: RSError.t) => new SqError(v) ); } diff --git a/packages/squiggle-lang/src/js/SqValueLocation.ts b/packages/squiggle-lang/src/js/SqValueLocation.ts index 33c7060b..ad2e7e02 100644 --- a/packages/squiggle-lang/src/js/SqValueLocation.ts +++ b/packages/squiggle-lang/src/js/SqValueLocation.ts @@ -1,4 +1,3 @@ -import { isParenthesisNode } from "mathjs"; import { SqProject } from "./SqProject"; type PathItem = string | number; diff --git a/packages/squiggle-lang/src/js/index.ts b/packages/squiggle-lang/src/js/index.ts index aac26bd5..886c830a 100644 --- a/packages/squiggle-lang/src/js/index.ts +++ b/packages/squiggle-lang/src/js/index.ts @@ -13,7 +13,7 @@ export { environment, defaultEnvironment, } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen"; -export { SqError } from "./SqError"; +export { SqError, SqFrame, SqLocation } from "./SqError"; export { SqShape } from "./SqPointSetDist"; export { resultMap } from "./types"; diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res b/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res index 84e6a50f..3e7a9e8b 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Builtin.res @@ -5,7 +5,7 @@ let nameSpace = "" // no namespaced versions type simpleDefinition = { inputs: array, - fn: array => result, + fn: array => result, } let makeFnMany = (name: string, definitions: array) => @@ -22,7 +22,7 @@ let makeFnMany = (name: string, definitions: array) => let makeFn = ( name: string, inputs: array, - fn: array => result, + fn: array => result, ) => makeFnMany(name, [{inputs: inputs, fn: fn}]) let library = [ diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res index 2d60f556..cd8c1c88 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Danger.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Danger.res @@ -67,7 +67,7 @@ module Integration = { let applyFunctionAtFloatToFloatOption = (point: float) => { // Defined here so that it has access to environment, reducer let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point) - let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall( + let resultAsInternalExpression = Reducer_Lambda.doLambdaCall( aLambda, [pointAsInternalExpression], environment, @@ -77,7 +77,7 @@ module Integration = { | Reducer_T.IEvNumber(x) => Ok(x) | _ => Error( - "Error 1 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"->Reducer_ErrorValue.REOther, + "Error 1 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"->SqError.Message.REOther, ) } result @@ -143,8 +143,8 @@ 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) - ->Reducer_ErrorValue.REOther + b->SqError.Message.toString) + ->SqError.Message.REOther ->Error } result @@ -169,7 +169,7 @@ module Integration = { let result = switch inputs { | [_, _, _, IEvNumber(0.0)] => "Integration error 4 in Danger.integrate: Increment can't be 0." - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error | [ IEvLambda(aLambda), @@ -187,7 +187,7 @@ module Integration = { ) | _ => Error( - Reducer_ErrorValue.REOther( + SqError.Message.REOther( "Integration error 5 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))", ), ) @@ -213,7 +213,7 @@ module Integration = { let result = switch inputs { | [_, _, _, IEvNumber(0.0)] => "Integration error in Danger.integrate: Increment can't be 0." - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error | [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] => Helpers.integrateFunctionBetweenWithNumIntegrationPoints( @@ -225,11 +225,11 @@ 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->SqError.Message.toString)->SqError.Message.REOther ) | _ => "Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))" - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error } result @@ -246,7 +246,7 @@ module DiminishingReturns = { module Helpers = { type diminishingReturnsAccumulatorInner = { optimalAllocations: array, - currentMarginalReturns: result, errorValue>, + currentMarginalReturns: result, errorMessage>, } let findBiggestElementIndex = (xs: array) => E.A.reducei(xs, 0, (acc, newElement, index) => { @@ -255,7 +255,7 @@ module DiminishingReturns = { | false => acc } }) - type diminishingReturnsAccumulator = result + type diminishingReturnsAccumulator = result // TODO: This is so complicated, it probably should be its own file. It might also make sense to have it work in Rescript directly, taking in a function rather than a reducer; then something else can wrap that function in the reducer/lambdas/environment. /* The key idea for this function is that @@ -290,25 +290,25 @@ module DiminishingReturns = { ) { | (false, _, _, _) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->SqError.Message.REOther, ) | (_, false, _, _) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->SqError.Message.REOther, ) | (_, _, false, _) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->SqError.Message.REOther, ) | (_, _, _, false) => Error( - "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->Reducer_ErrorValue.REOther, + "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->SqError.Message.REOther, ) | (true, true, true, true) => { let applyFunctionAtPoint = (lambda, point: float) => { // Defined here so that it has access to environment, reducer let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point) - let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall( + let resultAsInternalExpression = Reducer_Lambda.doLambdaCall( lambda, [pointAsInternalExpression], environment, @@ -318,7 +318,7 @@ module DiminishingReturns = { | Reducer_T.IEvNumber(x) => Ok(x) | _ => Error( - "Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->Reducer_ErrorValue.REOther, + "Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->SqError.Message.REOther, ) } } @@ -411,7 +411,7 @@ module DiminishingReturns = { | Reducer_T.IEvLambda(lambda) => Ok(lambda) | _ => "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. A member of the array wasn't a function" - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error } }, innerlambdas) @@ -433,7 +433,7 @@ module DiminishingReturns = { } | _ => "Error in Danger.diminishingMarginalReturnsForTwoFunctions" - ->Reducer_ErrorValue.REOther + ->SqError.Message.REOther ->Error }, (), diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Date.res b/packages/squiggle-lang/src/rescript/FR/FR_Date.res index c9732980..0e4b828d 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Date.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Date.res @@ -4,7 +4,7 @@ open FunctionRegistry_Helpers let makeFn = ( name: string, inputs: array, - fn: array => result, + fn: array => result, ) => Function.make( ~name, @@ -66,7 +66,7 @@ let library = [ | [IEvNumber(year)] => switch DateTime.Date.makeFromYear(year) { | Ok(t) => IEvDate(t)->Ok - | Error(e) => Reducer_ErrorValue.RETodo(e)->Error + | Error(e) => SqError.Message.RETodo(e)->Error } | _ => Error(impossibleError) } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Dict.res b/packages/squiggle-lang/src/rescript/FR/FR_Dict.res index 77ca49a7..a858e1bb 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Dict.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Dict.res @@ -17,7 +17,7 @@ module Internals = { ->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value])) ->Wrappers.evArray - let fromList = (items: array): result => + let fromList = (items: array): result => items ->E.A2.fmap(item => { switch (item: Reducer_T.value) { @@ -76,7 +76,7 @@ let library = [ ->Belt.Array.map(dictValue => switch dictValue { | IEvRecord(dict) => dict - | _ => impossibleError->Reducer_ErrorValue.toException + | _ => impossibleError->SqError.Message.throw } ) ->Internals.mergeMany diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Dist.res b/packages/squiggle-lang/src/rescript/FR/FR_Dist.res index 97befcd4..8f5ec0a1 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Dist.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Dist.res @@ -16,13 +16,14 @@ module DistributionCreation = { r ->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env)) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REOther(e)) + ->E.R2.errMap(e => SqError.Message.REOther(e)) let make = (name, fn) => { FnDefinition.make( ~name, ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], - ~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env), + ~run=(inputs, context, _) => + inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env=context.environment), (), ) } @@ -31,8 +32,10 @@ module DistributionCreation = { FnDefinition.make( ~name, ~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])], - ~run=(inputs, env, _) => - inputs->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))->process(~fn, ~env), + ~run=(inputs, context, _) => + inputs + ->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95")) + ->process(~fn, ~env=context.environment), (), ) } @@ -41,10 +44,10 @@ module DistributionCreation = { FnDefinition.make( ~name, ~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])], - ~run=(inputs, env, _) => + ~run=(inputs, context, _) => inputs ->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev")) - ->process(~fn, ~env), + ->process(~fn, ~env=context.environment), (), ) } @@ -55,13 +58,14 @@ module DistributionCreation = { r ->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env)) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REOther(e)) + ->E.R2.errMap(e => SqError.Message.REOther(e)) let make = (name, fn) => FnDefinition.make( ~name, ~inputs=[FRTypeDistOrNumber], - ~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env), + ~run=(inputs, context, _) => + inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env=context.environment), (), ) } diff --git a/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res b/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res index 6f98d132..ad456650 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_GenericDist.res @@ -296,7 +296,7 @@ module Old = { let genericOutputToReducerValue = (o: DistributionOperation.outputType): result< Reducer_T.value, - Reducer_ErrorValue.errorValue, + SqError.Message.t, > => switch o { | Dist(d) => Ok(Reducer_T.IEvDistribution(d)) @@ -311,9 +311,9 @@ module Old = { switch dispatchToGenericOutput(call, environment) { | Some(o) => genericOutputToReducerValue(o) | None => - Reducer_ErrorValue.REOther("Internal error in FR_GenericDist implementation") - ->Reducer_ErrorValue.ErrorException - ->raise + SqError.Message.REOther( + "Internal error in FR_GenericDist implementation", + )->SqError.Message.throw } } @@ -326,7 +326,7 @@ let makeProxyFn = (name: string, inputs: array) => { FnDefinition.make( ~name, ~inputs, - ~run=(inputs, env, _) => Old.dispatch((name, inputs), env), + ~run=(inputs, context, _) => Old.dispatch((name, inputs), context.environment), (), ), ], @@ -402,9 +402,9 @@ let library = E.A.concatMany([ ]) // FIXME - impossible to implement with FR due to arbitrary parameters length; -let mxLambda = Reducer_Expression_Lambda.makeFFILambda((inputs, env, _) => { - switch Old.dispatch(("mx", inputs), env) { +let mxLambda = Reducer_Lambda.makeFFILambda("mx", (inputs, context, _) => { + switch Old.dispatch(("mx", inputs), context.environment) { | Ok(value) => value - | Error(e) => e->Reducer_ErrorValue.ErrorException->raise + | Error(e) => e->SqError.Message.throw } }) diff --git a/packages/squiggle-lang/src/rescript/FR/FR_List.res b/packages/squiggle-lang/src/rescript/FR/FR_List.res index 172b1073..803bfc05 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_List.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_List.res @@ -30,11 +30,11 @@ module Internals = { let map = ( array: array, eLambdaValue, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ): Reducer_T.value => { Belt.Array.map(array, elem => - Reducer_Expression_Lambda.doLambdaCall(eLambdaValue, [elem], env, reducer) + Reducer_Lambda.doLambdaCall(eLambdaValue, [elem], context, reducer) )->Wrappers.evArray } @@ -42,11 +42,11 @@ module Internals = { aValueArray, initialValue, aLambdaValue, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ) => { aValueArray->E.A.reduce(initialValue, (acc, elem) => - Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer) + Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer) ) } @@ -54,22 +54,22 @@ module Internals = { aValueArray, initialValue, aLambdaValue, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ) => { aValueArray->Belt.Array.reduceReverse(initialValue, (acc, elem) => - Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer) + Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer) ) } let filter = ( aValueArray, aLambdaValue, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ) => { Js.Array2.filter(aValueArray, elem => { - let result = Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [elem], env, reducer) + let result = Reducer_Lambda.doLambdaCall(aLambdaValue, [elem], context, reducer) switch result { | IEvBool(true) => true | _ => false diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res b/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res index a12164bd..f7caaabb 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Pointset.res @@ -16,30 +16,30 @@ let inputsToDist = (inputs: array, xyShapeToPointSetDist) => { let yValue = map->Belt.Map.String.get("y") switch (xValue, yValue) { | (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y) - | _ => impossibleError->Reducer_ErrorValue.toException + | _ => impossibleError->SqError.Message.throw } } - | _ => impossibleError->Reducer_ErrorValue.toException + | _ => impossibleError->SqError.Message.throw } ) ->Ok ->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString)) ->E.R2.fmap(r => Reducer_T.IEvDistribution(PointSet(r->xyShapeToPointSetDist))) - | _ => impossibleError->Reducer_ErrorValue.toException + | _ => impossibleError->SqError.Message.throw } } module Internal = { type t = PointSetDist.t - let toType = (r): result => + let toType = (r): result => switch r { | Ok(r) => Ok(Wrappers.evDistribution(PointSet(r))) | Error(err) => Error(REOperationError(err)) } let doLambdaCall = (aLambdaValue, list, env, reducer) => - switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) { + switch Reducer_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) { | Reducer_T.IEvNumber(f) => Ok(f) | _ => Error(Operation.SampleMapNeedsNtoNFunction) } @@ -61,18 +61,18 @@ let library = [ FnDefinition.make( ~name="fromDist", ~inputs=[FRTypeDist], - ~run=(inputs, env, _) => + ~run=(inputs, context, _) => switch inputs { | [IEvDistribution(dist)] => GenericDist.toPointSet( dist, - ~xyPointLength=env.xyPointLength, - ~sampleCount=env.sampleCount, + ~xyPointLength=context.environment.xyPointLength, + ~sampleCount=context.environment.sampleCount, (), ) ->E.R2.fmap(Wrappers.pointSet) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) + ->E.R2.errMap(e => SqError.Message.REDistributionError(e)) | _ => Error(impossibleError) }, (), diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res b/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res index 19faeb8d..ab50a04b 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Sampleset.res @@ -10,41 +10,40 @@ module Internal = { let doLambdaCall = ( aLambdaValue, list, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ) => - switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) { + switch Reducer_Lambda.doLambdaCall(aLambdaValue, list, context, reducer) { | IEvNumber(f) => Ok(f) | _ => Error(Operation.SampleMapNeedsNtoNFunction) } - let toType = (r): result => + let toType = (r): result => switch r { | Ok(r) => Ok(Wrappers.evDistribution(SampleSet(r))) | Error(r) => Error(REDistributionError(SampleSetError(r))) } //TODO: I don't know why this seems to need at least one input - let fromFn = (aLambdaValue, environment: Reducer_T.environment, reducer: Reducer_T.reducerFn) => { - let sampleCount = environment.sampleCount - let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer) + let fromFn = (aLambdaValue, context: Reducer_T.context, reducer: Reducer_T.reducerFn) => { + let sampleCount = context.environment.sampleCount + let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer) Belt_Array.makeBy(sampleCount, r => fn(r->Js.Int.toFloat))->E.A.R.firstErrorOrOpen } - let map1 = (sampleSetDist: t, aLambdaValue, environment: Reducer_T.environment, reducer) => { - let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer) + let map1 = (sampleSetDist: t, aLambdaValue, context: Reducer_T.context, reducer) => { + let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer) SampleSetDist.samplesMap(~fn, sampleSetDist)->toType } - let map2 = (t1: t, t2: t, aLambdaValue, environment: Reducer_T.environment, reducer) => { - let fn = (a, b) => - doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], environment, reducer) + let map2 = (t1: t, t2: t, aLambdaValue, context: Reducer_T.context, reducer) => { + let fn = (a, b) => doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], context, reducer) SampleSetDist.map2(~fn, ~t1, ~t2)->toType } - let map3 = (t1: t, t2: t, t3: t, aLambdaValue, environment: Reducer_T.environment, reducer) => { + let map3 = (t1: t, t2: t, t3: t, aLambdaValue, context: Reducer_T.context, reducer) => { let fn = (a, b, c) => - doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], environment, reducer) + doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], context, reducer) SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType } @@ -60,7 +59,7 @@ module Internal = { let mapN = ( aValueArray: array, aLambdaValue, - environment: Reducer_T.environment, + context: Reducer_T.context, reducer, ) => { switch parseSampleSetArray(aValueArray) { @@ -69,7 +68,7 @@ module Internal = { doLambdaCall( aLambdaValue, [IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))], - environment, + context, reducer, ) SampleSetDist.mapN(~fn, ~t1)->toType @@ -89,13 +88,13 @@ let libaryBase = [ FnDefinition.make( ~name="fromDist", ~inputs=[FRTypeDist], - ~run=(inputs, environment, _) => + ~run=(inputs, context, _) => switch inputs { | [IEvDistribution(dist)] => - GenericDist.toSampleSetDist(dist, environment.sampleCount) + GenericDist.toSampleSetDist(dist, context.environment.sampleCount) ->E.R2.fmap(Wrappers.sampleSet) ->E.R2.fmap(Wrappers.evDistribution) - ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) + ->E.R2.errMap(e => SqError.Message.REDistributionError(e)) | _ => Error(impossibleError) }, (), @@ -163,7 +162,7 @@ let libaryBase = [ | [IEvLambda(lambda)] => switch Internal.fromFn(lambda, environment, reducer) { | Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution) - | Error(e) => e->Reducer_ErrorValue.REOperationError->Error + | Error(e) => e->SqError.Message.REOperationError->Error } | _ => Error(impossibleError) }, @@ -290,7 +289,7 @@ module Comparison = { r ->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution) ->E.R2.errMap(e => - e->DistributionTypes.Error.sampleErrorToDistErr->Reducer_ErrorValue.REDistributionError + e->DistributionTypes.Error.sampleErrorToDistErr->SqError.Message.REDistributionError ) let mkBig = (name, withDist, withFloat) => diff --git a/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res b/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res index 5b780da5..ffe10384 100644 --- a/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res +++ b/packages/squiggle-lang/src/rescript/FR/FR_Scoring.res @@ -6,7 +6,7 @@ let requiresNamespace = true let runScoring = (estimate, answer, prior, env) => { GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env) ->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber) - ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) + ->E.R2.errMap(e => SqError.Message.REDistributionError(e)) } let library = [ @@ -30,15 +30,15 @@ let library = [ ("prior", FRTypeDist), ]), ], - ~run=(inputs, environment, _) => { + ~run=(inputs, context, _) => { switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs( inputs, ("estimate", "answer", "prior"), ) { | Ok([IEvDistribution(estimate), IEvDistribution(d), IEvDistribution(prior)]) => - runScoring(estimate, Score_Dist(d), Some(prior), environment) + runScoring(estimate, Score_Dist(d), Some(prior), context.environment) | Ok([IEvDistribution(estimate), IEvNumber(d), IEvDistribution(prior)]) => - runScoring(estimate, Score_Scalar(d), Some(prior), environment) + runScoring(estimate, Score_Scalar(d), Some(prior), context.environment) | Error(e) => Error(e->FunctionRegistry_Helpers.wrapError) | _ => Error(FunctionRegistry_Helpers.impossibleError) } @@ -48,15 +48,15 @@ let library = [ FnDefinition.make( ~name="logScore", ~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])], - ~run=(inputs, environment, _) => { + ~run=(inputs, context, _) => { switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs( inputs, ("estimate", "answer"), ) { | Ok([IEvDistribution(estimate), IEvDistribution(d)]) => - runScoring(estimate, Score_Dist(d), None, environment) + runScoring(estimate, Score_Dist(d), None, context.environment) | Ok([IEvDistribution(estimate), IEvNumber(d)]) => - runScoring(estimate, Score_Scalar(d), None, environment) + runScoring(estimate, Score_Scalar(d), None, context.environment) | Error(e) => Error(e->FunctionRegistry_Helpers.wrapError) | _ => Error(FunctionRegistry_Helpers.impossibleError) } @@ -76,10 +76,10 @@ let library = [ FnDefinition.make( ~name="klDivergence", ~inputs=[FRTypeDist, FRTypeDist], - ~run=(inputs, environment, _) => { + ~run=(inputs, context, _) => { switch inputs { | [IEvDistribution(estimate), IEvDistribution(d)] => - runScoring(estimate, Score_Dist(d), None, environment) + runScoring(estimate, Score_Dist(d), None, context.environment) | _ => Error(FunctionRegistry_Helpers.impossibleError) } }, diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res index 89972e8e..c8904e00 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_ReducerProject.res @@ -1,6 +1,7 @@ @genType type reducerProject = ReducerProject_T.project //re-export -type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use +type error = SqError.t //use +type errorMessage = SqError.Message.t //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use @@ -103,10 +104,8 @@ let cleanAllResults = (project: reducerProject): unit => project->Private.cleanA To set the includes one first has to call "parseIncludes". The parsed includes or the parser error is returned. */ @genType -let getIncludes = (project: reducerProject, sourceId: string): result< - array, - reducerErrorValue, -> => project->Private.getIncludes(sourceId) +let getIncludes = (project: reducerProject, sourceId: string): result, error> => + project->Private.getIncludes(sourceId) /* Other sources contributing to the global namespace of this source. */ @genType @@ -198,10 +197,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. @@ -209,10 +206,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 => @@ -220,24 +215,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 deleted file mode 100644 index d4a8059b..00000000 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_Reducer_ErrorValue.res +++ /dev/null @@ -1,18 +0,0 @@ -@genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias -@genType type syntaxErrorLocation = Reducer_ErrorValue.syntaxErrorLocation //alias - -@genType -let toString = (e: reducerErrorValue): string => Reducer_ErrorValue.errorToString(e) - -@genType -let getLocation = (e: reducerErrorValue): option => - switch e { - | RESyntaxError(_, optionalLocation) => optionalLocation - | _ => None - } - -@genType -let createTodoError = (v: string) => Reducer_ErrorValue.RETodo(v) - -@genType -let createOtherError = (v: string) => Reducer_ErrorValue.REOther(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..d116b74b 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 error = SqError.t //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_SquiggleValue/ForTS_SquiggleValue_Lambda.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res index a9793e87..58213c04 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.res @@ -1,9 +1,7 @@ @genType type squiggleValue_Lambda = Reducer_T.lambdaValue //re-export @genType -let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringFunction(v) +let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringLambda(v) @genType -let parameters = (v: squiggleValue_Lambda): array => { - v.parameters -} +let parameters = (v: squiggleValue_Lambda): array => Reducer_Lambda.parameters(v) diff --git a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res index 1cd74827..b2bb9d98 100644 --- a/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res +++ b/packages/squiggle-lang/src/rescript/ForTS/ForTS__Types.res @@ -1,6 +1,3 @@ -@genType type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //re-export -@genType type syntaxErrorLocation = ForTS_Reducer_ErrorValue.syntaxErrorLocation //re-export - @genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export @genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res index 9683147f..d2d0cfbe 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Core.res @@ -1,5 +1,5 @@ type internalExpressionValueType = Reducer_Value.internalExpressionValueType -type errorValue = Reducer_ErrorValue.errorValue +type errorMessage = SqError.Message.t /* Function Registry "Type". A type, without any other information. @@ -30,9 +30,9 @@ type fnDefinition = { inputs: array, run: ( array, - Reducer_T.environment, + Reducer_T.context, Reducer_T.reducerFn, - ) => result, + ) => result, } type function = { @@ -122,11 +122,11 @@ module FnDefinition = { let run = ( t: t, args: array, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, ) => { switch t->isMatch(args) { - | true => t.run(args, env, reducer) + | true => t.run(args, context, reducer) | false => REOther("Incorrect Types")->Error } } @@ -164,7 +164,7 @@ module Function = { nameSpace: nameSpace, definitions: definitions, output: output, - examples: examples |> E.O.default([]), + examples: examples->E.O2.default([]), isExperimental: isExperimental, requiresNamespace: requiresNamespace, description: description, @@ -225,9 +225,9 @@ module Registry = { registry, fnName: string, args: array, - env: Reducer_T.environment, + context: Reducer_T.context, reducer: Reducer_T.reducerFn, - ): result => { + ): result => { switch Belt.Map.String.get(registry.fnNameDict, fnName) { | Some(definitions) => { let showNameMatchDefinitions = () => { @@ -241,7 +241,7 @@ module Registry = { let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args)) switch match { - | Some(def) => def->FnDefinition.run(args, env, reducer) + | Some(def) => def->FnDefinition.run(args, context, reducer) | None => REOther(showNameMatchDefinitions())->Error } } diff --git a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res index 9a7f04db..9ace7e20 100644 --- a/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res +++ b/packages/squiggle-lang/src/rescript/FunctionRegistry/FunctionRegistry_Helpers.res @@ -2,8 +2,8 @@ open FunctionRegistry_Core open Reducer_T let impossibleErrorString = "Wrong inputs / Logically impossible" -let impossibleError: errorValue = impossibleErrorString->Reducer_ErrorValue.REOther -let wrapError = e => Reducer_ErrorValue.REOther(e) +let impossibleError: errorMessage = impossibleErrorString->SqError.Message.REOther +let wrapError = e => SqError.Message.REOther(e) module Wrappers = { let symbolic = r => DistributionTypes.Symbolic(r) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res index c45994bb..809ac514 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Context.res @@ -4,9 +4,13 @@ let defaultEnvironment: Reducer_T.environment = DistributionOperation.defaultEnv let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environment): t => { { + frameStack: list{}, bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend, environment: environment, + inFunction: None, } } -let createDefaultContext = (): t => createContext(SquiggleLibrary_StdLib.stdLib, defaultEnvironment) +let currentFunctionName = (t: t): string => { + t.inFunction->E.O2.fmap(Reducer_Lambda_T.name)->E.O2.default(Reducer_T.topFrameName) +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res deleted file mode 100644 index e545bb2d..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_ChainPiece.res +++ /dev/null @@ -1,27 +0,0 @@ -// types are disabled until review and rewrite for 0.5 interpreter compatibility -/* -module ProjectAccessorsT = ReducerProject_ProjectAccessors_T -module T = Reducer_Dispatch_T -module TypeChecker = Reducer_Type_TypeChecker -open Reducer_Value - -type errorValue = Reducer_ErrorValue.errorValue - -let makeFromTypes = jumpTable => { - let dispatchChainPiece: T.dispatchChainPiece = ( - (fnName, fnArgs): functionCall, - accessors: ProjectAccessorsT.t, - ) => { - let jumpTableEntry = jumpTable->Js.Array2.find(elem => { - let (candidName, candidType, _) = elem - candidName == fnName && TypeChecker.checkITypeArgumentsBool(candidType, fnArgs) - }) - switch jumpTableEntry { - | Some((_, _, bridgeFn)) => bridgeFn(fnArgs, accessors)->Some - | _ => None - } - } - dispatchChainPiece -} - -*/ diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res deleted file mode 100644 index 0d852783..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_T.res +++ /dev/null @@ -1,21 +0,0 @@ -// module ExpressionT = Reducer_Expression_T -// module ProjectAccessorsT = ReducerProject_ProjectAccessors_T - -// // Each piece of the dispatch chain computes the result or returns None so that the chain can continue -// type dispatchChainPiece = ( -// Reducer_Value.functionCall, -// ProjectAccessorsT.t, -// ) => option> - -// type dispatchChainPieceWithReducer = ( -// Reducer_Value.functionCall, -// ProjectAccessorsT.t, -// Reducer_T.reducerFn, -// ) => option> - -// // This is a switch statement case implementation: get the arguments and compute the result -// type genericIEvFunction = ( -// array, -// ProjectAccessorsT.t, -// ) => result - diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res deleted file mode 100644 index b6baeaf5..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ /dev/null @@ -1,83 +0,0 @@ -//TODO: Do not export here but in ForTS__Types -@gentype.import("peggy") @genType.as("LocationRange") -type syntaxErrorLocation - -@genType.opaque -type errorValue = - | REArityError(option, int, int) - | REArrayIndexNotFound(string, int) - | REAssignmentExpected - | REDistributionError(DistributionTypes.error) - | REExpectedType(string, string) - | REExpressionExpected - | REFunctionExpected(string) - | REFunctionNotFound(string) - | REJavaScriptExn(option, option) // Javascript Exception - | REMacroNotFound(string) - | RENotAFunction(string) - | REOperationError(Operation.operationError) - | RERecordPropertyNotFound(string, string) - | RESymbolNotFound(string) - | RESyntaxError(string, option) - | RETodo(string) // To do - | REUnitNotFound(string) - | RENeedToRun - | REOther(string) - -type t = errorValue - -exception ErrorException(errorValue) - -let errorToString = err => - switch err { - | REArityError(_oFnName, arity, usedArity) => - `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( - usedArity, - )} argument(s) were passed.` - | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` - | REAssignmentExpected => "Assignment expected" - | REExpressionExpected => "Expression expected" - | REFunctionExpected(msg) => `Function expected: ${msg}` - | REFunctionNotFound(msg) => `Function not found: ${msg}` - | REDistributionError(err) => `Distribution Math Error: ${DistributionTypes.Error.toString(err)}` - | REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}` - | REJavaScriptExn(omsg, oname) => { - let answer = "JS Exception:" - let answer = switch oname { - | Some(name) => `${answer} ${name}` - | _ => answer - } - let answer = switch omsg { - | Some(msg) => `${answer}: ${msg}` - | _ => answer - } - answer - } - | REMacroNotFound(macro) => `Macro not found: ${macro}` - | RENotAFunction(valueString) => `${valueString} is not a function` - | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` - | RESymbolNotFound(symbolName) => `${symbolName} is not defined` - | RESyntaxError(desc, _) => `Syntax Error: ${desc}` - | RETodo(msg) => `TODO: ${msg}` - | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}` - | REUnitNotFound(unitName) => `Unit not found: ${unitName}` - | RENeedToRun => "Need to run" - | REOther(msg) => `Error: ${msg}` - } - -let fromException = exn => - switch exn { - | ErrorException(e) => e - | Js.Exn.Error(e) => - switch Js.Exn.message(e) { - | Some(message) => REOther(message) - | None => - switch Js.Exn.name(e) { - | Some(name) => REOther(name) - | None => REOther("Unknown error") - } - } - | _e => REOther("Unknown error") - } - -let toException = (errorValue: t) => raise(ErrorException(errorValue)) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res deleted file mode 100644 index 14db0843..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res +++ /dev/null @@ -1,3 +0,0 @@ -// There are switch statement cases in the code which are impossible to reach by design. -// ImpossibleException is a sign of programming error. -exception ImpossibleException(string) 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..4906dd54 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 @@ -1,16 +1,25 @@ module Bindings = Reducer_Bindings -module Lambda = Reducer_Expression_Lambda module Result = Belt.Result module T = Reducer_T -type errorValue = Reducer_ErrorValue.errorValue +let toLocation = (expression: T.expression): Reducer_Peggy_Parse.location => { + expression.ast.location +} + +let throwFrom = (error: SqError.Message.t, expression: T.expression, context: T.context) => + error->SqError.throwMessageWithFrameStack( + context.frameStack->Reducer_FrameStack.extend( + context->Reducer_Context.currentFunctionName, + Some(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 +58,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, context) } let (value, _) = eValue->evaluate(context) (keyString, value) @@ -73,7 +82,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, context) } | T.EValue(value) => (value, context) @@ -82,28 +91,39 @@ 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, context) } } - | T.ELambda(parameters, body) => ( - Lambda.makeLambda(parameters, context.bindings, body)->T.IEvLambda, + | T.ELambda(parameters, body, name) => ( + Reducer_Lambda.makeLambda( + name, + parameters, + context.bindings, + body, + expression->toLocation, + )->T.IEvLambda, context, ) | T.ECall(fn, args) => { let (lambda, _) = fn->evaluate(context) - let argValues = Js.Array2.map(args, arg => { + let argValues = Belt.Array.map(args, arg => { let (argValue, _) = arg->evaluate(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) => { + let result = Reducer_Lambda.doLambdaCallFrom( + lambda, + argValues, + context, + evaluate, + Some(expression->toLocation), // we have to pass the location of a current expression here, to put it on frameStack + ) + (result, context) + } + | _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression, context) } } } @@ -112,19 +132,22 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { 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) + let parse = (peggyCode: string): result => + peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode) - let evaluate = (expression: T.expression): result => { - let context = Reducer_Context.createDefaultContext() + let createDefaultContext = () => + Reducer_Context.createContext(SquiggleLibrary_StdLib.stdLib, Reducer_Context.defaultEnvironment) + + let evaluate = (expression: T.expression): result => { + let context = createDefaultContext() try { let (value, _) = expression->evaluate(context) value->Ok } catch { - | exn => Reducer_ErrorValue.fromException(exn)->Error + | exn => exn->SqError.fromException->Error } } - let evaluateString = (peggyCode: string): result => - parse(peggyCode)->Result.flatMap(evaluate) + let evaluateString = (peggyCode: string): result => + parse(peggyCode)->E.R2.errMap(e => e->SqError.fromParseError)->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..527331fb 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 @@ -1,16 +1,19 @@ -module BErrorValue = Reducer_ErrorValue 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, + name: option, +): expressionContent => T.ELambda(parameters, expr, name) let eNumber = aNumber => aNumber->T.IEvNumber->T.EValue @@ -18,13 +21,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 +36,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_Lambda.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res deleted file mode 100644 index e9a85a1a..00000000 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_Lambda.res +++ /dev/null @@ -1,60 +0,0 @@ -module ErrorValue = Reducer_ErrorValue - -let doLambdaCall = ( - lambdaValue: Reducer_T.lambdaValue, - args, - environment: Reducer_T.environment, - reducer: Reducer_T.reducerFn, -): Reducer_T.value => { - lambdaValue.body(args, environment, reducer) -} - -let makeLambda = ( - parameters: array, - bindings: Reducer_T.bindings, - body: Reducer_T.expression, -): Reducer_T.lambdaValue => { - // TODO - clone bindings to avoid later redefinitions affecting lambdas? - - // Note: with this implementation, FFI lambdas (created by other methods than calling `makeLambda`) are allowed to violate the rules, pollute the bindings, etc. - // Not sure yet if that's a bug or a feature. - // FunctionRegistry functions are unaffected by this, their API is too limited. - - let lambda = ( - arguments: array, - environment: Reducer_T.environment, - reducer: Reducer_T.reducerFn, - ) => { - let argsLength = arguments->Js.Array2.length - let parametersLength = parameters->Js.Array2.length - if argsLength !== parametersLength { - ErrorValue.REArityError(None, parametersLength, argsLength)->ErrorValue.ErrorException->raise - } - - let localBindings = bindings->Reducer_Bindings.extend - let localBindingsWithParameters = parameters->Belt.Array.reduceWithIndex(localBindings, ( - currentBindings, - parameter, - index, - ) => { - currentBindings->Reducer_Bindings.set(parameter, arguments[index]) - }) - - let (value, _) = reducer( - body, - {bindings: localBindingsWithParameters, environment: environment}, - ) - value - } - - { - // context: bindings, - body: lambda, - parameters: parameters, - } -} - -let makeFFILambda = (body: Reducer_T.lambdaBody): Reducer_T.lambdaValue => { - body: body, - parameters: ["..."], -} 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..c7804c7a 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 @@ -24,37 +24,23 @@ let rec toString = (expression: t) => `${predicate->toString} ? (${trueCase->toString}) : (${falseCase->toString})` | EAssign(name, value) => `${name} = ${value->toString}` | ECall(fn, args) => `(${fn->toString})(${args->Js.Array2.map(toString)->commaJoin})` - | ELambda(parameters, body) => `{|${parameters->commaJoin}| ${body->toString}}` + | ELambda(parameters, body, _) => `{|${parameters->commaJoin}| ${body->toString}}` | EValue(aValue) => Reducer_Value.toString(aValue) } let toStringResult = codeResult => switch codeResult { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + | Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})` } let toStringResultOkless = codeResult => switch codeResult { | Ok(a) => toString(a) - | Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` + | Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})` } let inspect = (expr: t): t => { Js.log(toString(expr)) expr } - -let inspectResult = (r: result): result< - t, - Reducer_ErrorValue.errorValue, -> => { - Js.log(toStringResult(r)) - r -} - -let resultToValue = (rExpression: result): t => - switch rExpression { - | Ok(expression) => expression - | Error(errorValue) => Reducer_ErrorValue.toException(errorValue) - } 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..b7370e00 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_FrameStack.res @@ -0,0 +1,51 @@ +// This is called "frameStack" and not "callStack", because the last frame in errors is often not a function call. +// A "frame" is a pair of a scope (function or top-level scope, currently stored as a string) and a location inside it. +// See this comment to deconfuse about what a frame is: https://github.com/quantified-uncertainty/squiggle/pull/1172#issuecomment-1264115038 +type t = Reducer_T.frameStack + +module Frame = { + let toString = ({name, location}: Reducer_T.frame) => + name ++ + switch location { + | Some(location) => + ` at line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` // TODO - source id? + | None => "" + } + + @genType + let getLocation = (t: Reducer_T.frame): option => t.location + + @genType + let getName = (t: Reducer_T.frame): string => t.name +} + +let make = (): t => list{} + +let extend = (t: t, name: string, location: option) => + t->Belt.List.add({ + name: name, + location: location, + }) + +// this is useful for SyntaxErrors +let makeSingleFrameStack = (location: Reducer_Peggy_Parse.location): t => + make()->extend(Reducer_T.topFrameName, Some(location)) + +// this includes the left offset because it's mostly used in SqError.toStringWithStackTrace +let toString = (t: t) => + t + ->Belt.List.map(s => " " ++ s->Frame.toString ++ "\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 new file mode 100644 index 00000000..b814ea45 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda.res @@ -0,0 +1,95 @@ +type t = Reducer_T.lambdaValue + +// user-defined functions, i.e. `add2 = {|x, y| x + y}`, are built by this method +let makeLambda = ( + name: option, + parameters: array, + bindings: Reducer_T.bindings, + body: Reducer_T.expression, + location: Reducer_Peggy_Parse.location, +): t => { + let lambda = ( + arguments: array, + context: Reducer_T.context, + reducer: Reducer_T.reducerFn, + ) => { + let argsLength = arguments->E.A.length + let parametersLength = parameters->E.A.length + if argsLength !== parametersLength { + SqError.Message.REArityError(None, parametersLength, argsLength)->SqError.Message.throw + } + + // create new bindings scope - technically not necessary, since bindings are immutable, but might help with debugging/new features in the future + let localBindings = bindings->Reducer_Bindings.extend + + let localBindingsWithParameters = parameters->Belt.Array.reduceWithIndex(localBindings, ( + currentBindings, + parameter, + index, + ) => { + currentBindings->Reducer_Bindings.set(parameter, arguments[index]) + }) + + let lambdaContext: Reducer_T.context = { + bindings: localBindingsWithParameters, // based on bindings at the moment of lambda creation + environment: context.environment, // environment at the moment when lambda is called + frameStack: context.frameStack, // already extended in `doLambdaCall` + inFunction: context.inFunction, // already updated in `doLambdaCall` + } + + let (value, _) = reducer(body, lambdaContext) + value + } + + FnLambda({ + // context: bindings, + name: name, + body: lambda, + parameters: parameters, + location: location, + }) +} + +// stdlib functions (everything in FunctionRegistry) are built by this method. Body is generated in SquiggleLibrary_StdLib.res +let makeFFILambda = (name: string, body: Reducer_T.lambdaBody): t => FnBuiltin({ + // Note: current bindings could be accidentally exposed here through context (compare with native lambda implementation above, where we override them with local bindings). + // But FunctionRegistry API is too limited for that to matter. Please take care not to violate that in the future by accident. + body: body, + name: name, +}) + +// this function doesn't scale to FunctionRegistry's polymorphic functions +let parameters = (t: t): array => { + switch t { + | FnLambda({parameters}) => parameters + | FnBuiltin(_) => ["..."] + } +} + +let doLambdaCallFrom = ( + t: t, + args: array, + context: Reducer_T.context, + reducer, + location: option, +) => { + let newContext = { + ...context, + frameStack: context.frameStack->Reducer_FrameStack.extend( + context->Reducer_Context.currentFunctionName, + location, + ), + inFunction: Some(t), + } + + SqError.rethrowWithFrameStack(() => { + switch t { + | FnLambda({body}) => body(args, newContext, reducer) + | FnBuiltin({body}) => body(args, newContext, reducer) + } + }, newContext.frameStack) +} + +let doLambdaCall = (t: t, args, context, reducer) => { + doLambdaCallFrom(t, args, context, reducer, None) +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res new file mode 100644 index 00000000..ab0724b4 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Lambda_T.res @@ -0,0 +1,8 @@ +type t = Reducer_T.lambdaValue + +let name = (t: t): string => { + switch t { + | FnLambda({name}) => name->E.O2.default("") + | FnBuiltin({name}) => name + } +} 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 5f0a5a25..7bb01c56 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 @@ -7,26 +7,26 @@ start = _nl start:outerBlock _nl finalComment? {return start} -zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda +zeroOMoreArgumentsBlockOrExpression = lambda / innerBlockOrExpression 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(), variable) + 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:credibleInterval _ operator:relationalOp _nl right:credibleInterval - { return h.makeFunctionCall(h.toFunction[operator], [left, right])} + { return h.makeFunctionCall(h.toFunction[operator], [left, right], location())} / credibleInterval relationalOp "operator" = '<='/'<'/'>='/'>' @@ -107,7 +107,7 @@ relational credibleInterval = head:additive tail:(__ operator:credibleIntervalOp __nl arg:additive {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' @@ -115,7 +115,7 @@ credibleInterval 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" = '+' / '-' / '.+' / '.-' @@ -123,7 +123,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" = '*' / '/' / '.*' / './' @@ -131,7 +131,7 @@ multiplicative power = head:chainFunctionCall tail:(_ operator:powerOp _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)} powerOp "operator" = '^' / '.^' @@ -139,7 +139,7 @@ power 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(), undefined) } / '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}' - { return h.nodeLambda(args, finalExpression) } + { return h.nodeLambda(args, finalExpression, location(), undefined) } 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..001f3e5b 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,22 +1,37 @@ module Extra = Reducer_Extra -open Reducer_ErrorValue -type node = {"type": string} +@genType +type locationPoint = { + line: int, + column: int, +} +@genType +type location = { + source: string, + start: locationPoint, + end: locationPoint, +} -@module("./Reducer_Peggy_GeneratedParser.js") external parse__: string => node = "parse" +type node = {"type": string, "location": location} -type withLocation = {"location": Reducer_ErrorValue.syntaxErrorLocation} +type parseError = SyntaxError(string, location) + +type parseResult = result + +@module("./Reducer_Peggy_GeneratedParser.js") +external parse__: (string, {"grammarSource": string}) => node = "parse" + +type withLocation = {"location": location} external castWithLocation: Js.Exn.t => withLocation = "%identity" -let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.syntaxErrorLocation => - castWithLocation(error)["location"] +let syntaxErrorToLocation = (error: Js.Exn.t): location => castWithLocation(error)["location"] -let parse = (expr: string): result => +let parse = (expr: string, source: string): parseResult => 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 + SyntaxError(Belt.Option.getExn(Js.Exn.message(obj)), syntaxErrorToLocation(obj))->Error } type nodeBlock = {...node, "statements": array} @@ -29,32 +44,35 @@ type nodeIdentifier = {...node, "value": string} type nodeInteger = {...node, "value": int} type nodeKeyValue = {...node, "key": node, "value": node} type nodeRecord = {...node, "elements": array} -type nodeLambda = {...node, "args": array, "body": node} +type nodeLambda = {...node, "args": array, "body": node, "name": option} 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 +89,92 @@ 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 => +let toStringError = (error: parseError): string => { + let SyntaxError(message, _) = error + `Syntax Error: ${message}}` +} + +let toStringResult = (rNode: parseResult): string => switch rNode { - | Ok(node) => toString(node) - | Error(error) => `Error(${errorToString(error)})` + | Ok(node) => node->toString + | Error(error) => `Error(${error->toStringError})` } 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..2e858c55 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, nodeLambda["name"]) + } + + 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..15597ea0 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,92 @@ 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; + name?: string; }; -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 +134,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 +213,46 @@ 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, + name?: NodeIdentifier ): NodeLambda { - return { type: "Lambda", args, body }; + return { type: "Lambda", args, body, location, name: name?.value }; } export function nodeLetStatement( variable: NodeIdentifier, - value: AnyPeggyNode + value: AnyPeggyNode, + location: LocationRange ): NodeLetStatement { - return { type: "LetStatement", variable, value }; + const patchedValue = + value.type === "Lambda" ? { ...value, name: variable.value } : value; + return { type: "LetStatement", variable, value: patchedValue, 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..36025e40 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_T.res @@ -15,14 +15,18 @@ type rec value = | IEvVoid @genType.opaque and arrayValue = array @genType.opaque and map = Belt.Map.String.t -and lambdaBody = (array, environment, reducerFn) => value +and lambdaBody = (array, context, reducerFn) => value @genType.opaque -and lambdaValue = { - parameters: array, - body: lambdaBody, -} +and lambdaValue = + | FnLambda({ + parameters: array, + body: lambdaBody, + location: Reducer_Peggy_Parse.location, + name: option, + }) + | FnBuiltin({body: lambdaBody, name: string}) @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) @@ -32,18 +36,34 @@ and expression = | ETernary(expression, expression, expression) | EAssign(string, expression) | ECall(expression, array) - | ELambda(array, expression) + | ELambda(array, expression, option) | EValue(value) +and expression = { + ast: Reducer_Peggy_Parse.ast, + content: expressionContent, +} + and namespace = Belt.Map.String.t and bindings = { namespace: namespace, parent: option, } +@genType.opaque +and frame = { + name: string, + location: option, // can be empty for calls from builtin functions +} +@genType.opaque and frameStack = list + and context = { bindings: bindings, environment: environment, + frameStack: frameStack, + inFunction: option, } and reducerFn = (expression, context) => (value, context) + +let topFrameName = "" diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res index cd1eefef..01f97647 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Value.res @@ -1,7 +1,3 @@ -// deprecated, use Reducer_T instead -// (value methods should be moved to Reducer_Value.res) - -module ErrorValue = Reducer_ErrorValue type environment = GenericDist.env module T = Reducer_T @@ -32,10 +28,12 @@ and toStringCall = fName => `:${fName}` and toStringDate = date => DateTime.Date.toString(date) and toStringDeclaration = d => Declaration.toString(d, r => toString(IEvLambda(r))) and toStringDistribution = dist => GenericDist.toString(dist) -and toStringLambda = (lambdaValue: T.lambdaValue) => - `lambda(${Js.Array2.toString(lambdaValue.parameters)}=>internal code)` -and toStringFunction = (lambdaValue: T.lambdaValue) => - `function(${Js.Array2.toString(lambdaValue.parameters)})` +and toStringLambda = (lambdaValue: T.lambdaValue) => { + switch lambdaValue { + | FnLambda({parameters}) => `lambda(${Js.Array2.toString(parameters)}=>internal code)` + | FnBuiltin(_) => "Builtin function" + } +} and toStringNumber = aNumber => Js.String.make(aNumber) and toStringRecord = aMap => aMap->toStringMap and toStringString = aString => `'${aString}'` @@ -76,25 +74,13 @@ let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)}) let toStringResult = x => switch x { | Ok(a) => `Ok(${toString(a)})` - | Error(m) => `Error(${ErrorValue.errorToString(m)})` + | Error(m) => `Error(${SqError.toString(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)})` + | Error(m) => `Error(${SqError.toString(m)})` } type internalExpressionValueType = @@ -156,10 +142,10 @@ let functionCallSignatureToString = (functionCallSignature: functionCallSignatur let arrayToValueArray = (arr: array): array => arr -let resultToValue = (rExpression: result): t => +let resultToValue = (rExpression: result): t => switch rExpression { | Ok(expression) => expression - | Error(errorValue) => Reducer_ErrorValue.toException(errorValue) + | Error(errorValue) => SqError.Message.throw(errorValue) } let recordToKeyValuePairs = (record: T.map): array<(string, t)> => record->Belt.Map.String.toArray diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res index 418eaa28..a0d143b6 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject.res @@ -1,7 +1,6 @@ // TODO: Auto clean project based on topology module Bindings = Reducer_Bindings -module ErrorValue = Reducer_ErrorValue module ProjectItem = ReducerProject_ProjectItem module T = ReducerProject_T module Topology = ReducerProject_Topology @@ -121,7 +120,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->SqError.fromMessage->Error | Some(result) => result } @@ -175,7 +174,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->SqError.throw }, ), ]) diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res index 38370488..bab3fa88 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ParseIncludes.res @@ -1,10 +1,7 @@ @module("./ReducerProject_IncludeParser.js") external parse__: string => array> = "parse" -let parseIncludes = (expr: string): result< - array<(string, string)>, - Reducer_ErrorValue.errorValue, -> => +let parseIncludes = (expr: string): result, SqError.t> => try { let answer = parse__(expr) // let logEntry = answer->Js.Array2.joinWith(",") @@ -12,8 +9,9 @@ let parseIncludes = (expr: string): result< Belt.Array.map(answer, item => (item[0], item[1]))->Ok } catch { | Js.Exn.Error(obj) => - RESyntaxError( - Belt.Option.getExn(Js.Exn.message(obj)), - Reducer_Peggy_Parse.syntaxErrorToLocation(obj)->Some, - )->Error + RESyntaxError(Belt.Option.getExn(Js.Exn.message(obj))) + ->SqError.fromMessageWithFrameStack( + Reducer_FrameStack.makeSingleFrameStack(Reducer_Peggy_Parse.syntaxErrorToLocation(obj)), + ) + ->Error } diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem.res index 93d434a3..3612c492 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)->E.R2.errMap(SqError.fromParseError) 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: SqError.t): t => this->setResult(e->Error)->setContinuation(Reducer_Namespace.make()) let doRun = (this: t, context: Reducer_T.context): t => @@ -181,12 +185,11 @@ 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")) + | e => this->failRun(e->SqError.fromException) } | Error(e) => this->failRun(e) } - | None => this->failRun(RETodo("attempt to run without expression")) + | None => this->failRun(RETodo("attempt to run without expression")->SqError.fromMessage) } 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..24e690eb 100644 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res +++ b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_ProjectItem_T.res @@ -1,26 +1,24 @@ module Parse = Reducer_Peggy_Parse module ExpressionT = Reducer_Expression_T -open Reducer_ErrorValue type sourceArgumentType = string type sourceType = string -type rawParseArgumentType = result +type rawParseArgumentType = result type rawParseType = option -type expressionArgumentType = result +type expressionArgumentType = result 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 type includesArgumentType = string -type includesType = result, errorValue> +type includesType = result, SqError.t> 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)) diff --git a/packages/squiggle-lang/src/rescript/SqError.res b/packages/squiggle-lang/src/rescript/SqError.res new file mode 100644 index 00000000..dd28fa4b --- /dev/null +++ b/packages/squiggle-lang/src/rescript/SqError.res @@ -0,0 +1,159 @@ +type location = Reducer_Peggy_Parse.location + +// Messages don't contain any stack trace information. +// FunctionRegistry functions are allowed to throw MessageExceptions, though, +// because they will be caught and rewrapped by Reducer_Lambda code. +module Message = { + @genType.opaque + type t = + | REArityError(option, int, int) + | REArrayIndexNotFound(string, int) + | REAssignmentExpected + | REDistributionError(DistributionTypes.error) + | REExpectedType(string, string) + | REExpressionExpected + | REFunctionExpected(string) + | REFunctionNotFound(string) + | REJavaScriptExn(option, option) // Javascript Exception + | REMacroNotFound(string) + | RENotAFunction(string) + | REOperationError(Operation.operationError) + | RERecordPropertyNotFound(string, string) + | RESymbolNotFound(string) + | RESyntaxError(string) + | RETodo(string) // To do + | REUnitNotFound(string) + | RENeedToRun + | REOther(string) + + exception MessageException(t) + + let toString = (err: t) => + switch err { + | REArityError(_oFnName, arity, usedArity) => + `${Js.String.make(arity)} arguments expected. Instead ${Js.String.make( + usedArity, + )} argument(s) were passed.` + | REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}` + | REAssignmentExpected => "Assignment expected" + | REExpressionExpected => "Expression expected" + | REFunctionExpected(msg) => `Function expected: ${msg}` + | REFunctionNotFound(msg) => `Function not found: ${msg}` + | REDistributionError(err) => + `Distribution Math Error: ${DistributionTypes.Error.toString(err)}` + | REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}` + | REJavaScriptExn(omsg, oname) => { + let answer = "JS Exception:" + let answer = switch oname { + | Some(name) => `${answer} ${name}` + | _ => answer + } + let answer = switch omsg { + | Some(msg) => `${answer}: ${msg}` + | _ => answer + } + answer + } + | REMacroNotFound(macro) => `Macro not found: ${macro}` + | RENotAFunction(valueString) => `${valueString} is not a function` + | RERecordPropertyNotFound(msg, index) => `${msg}: ${index}` + | RESymbolNotFound(symbolName) => `${symbolName} is not defined` + | RESyntaxError(desc) => `Syntax Error: ${desc}` + | RETodo(msg) => `TODO: ${msg}` + | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}` + | REUnitNotFound(unitName) => `Unit not found: ${unitName}` + | RENeedToRun => "Need to run" + | REOther(msg) => `Error: ${msg}` + } + + let fromException = exn => + switch exn { + | MessageException(e) => e + | Js.Exn.Error(e) => + switch Js.Exn.message(e) { + | Some(message) => REOther(message) + | None => + switch Js.Exn.name(e) { + | Some(name) => REOther(name) + | None => REOther("Unknown error") + } + } + | _e => REOther("Unknown error") + } + + let throw = (errorValue: t) => errorValue->MessageException->raise +} + +@genType.opaque +type t = { + message: Message.t, + frameStack: Reducer_FrameStack.t, +} + +exception SqException(t) + +let fromMessageWithFrameStack = (message: Message.t, frameStack: Reducer_FrameStack.t): t => { + message: message, + frameStack: frameStack, +} + +// this shouldn't be used much, since frame stack will be empty +// but it's useful for global errors, e.g. in ReducerProject or somethere in the frontend +@genType +let fromMessage = (message: Message.t) => + fromMessageWithFrameStack(message, Reducer_FrameStack.make()) + +let fromParseError = (SyntaxError(message, location): Reducer_Peggy_Parse.parseError) => + RESyntaxError(message)->fromMessageWithFrameStack( + Reducer_FrameStack.makeSingleFrameStack(location), + ) + +@genType +let getTopFrame = (t: t): option => t.frameStack->Reducer_FrameStack.getTopFrame + +@genType +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 + +@genType +let getFrameArray = (t: t): array => t.frameStack->Reducer_FrameStack.toFrameArray + +@genType +let toStringWithStackTrace = (t: t) => + t->toString ++ if t.frameStack->Reducer_FrameStack.isEmpty { + "\nStack trace:\n" ++ t.frameStack->Reducer_FrameStack.toString + } else { + "" + } +let throw = (t: t) => t->SqException->raise + +let throwMessageWithFrameStack = (message: Message.t, frameStack: Reducer_FrameStack.t) => + message->fromMessageWithFrameStack(frameStack)->throw + +// this shouldn't be used for most runtime errors - the resulting error would have an empty framestack +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 + } + +// converts raw exceptions into exceptions with framestack attached +// already converted exceptions won't be affected +let rethrowWithFrameStack = (fn: unit => 'a, frameStack: Reducer_FrameStack.t) => { + try { + fn() + } catch { + | SqException(e) => e->throw // exception already has a framestack + | Message.MessageException(e) => e->throwMessageWithFrameStack(frameStack) // probably comes from FunctionRegistry, adding framestack + | Js.Exn.Error(obj) => + REJavaScriptExn(obj->Js.Exn.message, obj->Js.Exn.name)->throwMessageWithFrameStack(frameStack) + | _ => REOther("Unknown exception")->throwMessageWithFrameStack(frameStack) + } +} diff --git a/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res b/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res index 3be93851..9b2fea1d 100644 --- a/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res +++ b/packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_StdLib.res @@ -1,5 +1,3 @@ -exception ErrorException = Reducer_ErrorValue.ErrorException - let stdLib: Reducer_T.namespace = { // constants let res = @@ -10,23 +8,23 @@ let stdLib: Reducer_T.namespace = { // array and record lookups let res = res->Reducer_Namespace.set( "$_atIndex_$", - Reducer_Expression_Lambda.makeFFILambda((inputs, _, _) => { + Reducer_Lambda.makeFFILambda("$_atIndex_$", (inputs, _, _) => { switch inputs { | [IEvArray(aValueArray), IEvNumber(fIndex)] => { let index = Belt.Int.fromFloat(fIndex) // TODO - fail on non-integer indices? switch Belt.Array.get(aValueArray, index) { | Some(value) => value - | None => REArrayIndexNotFound("Array index not found", index)->ErrorException->raise + | None => REArrayIndexNotFound("Array index not found", index)->SqError.Message.throw } } | [IEvRecord(dict), IEvString(sIndex)] => switch Belt.Map.String.get(dict, sIndex) { | Some(value) => value | None => - RERecordPropertyNotFound("Record property not found", sIndex)->ErrorException->raise + RERecordPropertyNotFound("Record property not found", sIndex)->SqError.Message.throw } - | _ => REOther("Trying to access key on wrong value")->ErrorException->raise + | _ => REOther("Trying to access key on wrong value")->SqError.Message.throw } })->Reducer_T.IEvLambda, ) @@ -46,10 +44,10 @@ let stdLib: Reducer_T.namespace = { ->Belt.Array.reduce(res, (cur, name) => { cur->Reducer_Namespace.set( name, - Reducer_Expression_Lambda.makeFFILambda((arguments, environment, reducer) => { - switch FunctionRegistry_Library.call(name, arguments, environment, reducer) { + Reducer_Lambda.makeFFILambda(name, (arguments, context, reducer) => { + switch FunctionRegistry_Library.call(name, arguments, context, reducer) { | Ok(value) => value - | Error(error) => error->Reducer_ErrorValue.ErrorException->raise + | Error(error) => error->SqError.Message.throw } })->Reducer_T.IEvLambda, )