Merge pull request #1172 from quantified-uncertainty/error-locations

Error locations and stacktraces
This commit is contained in:
Vyacheslav Matyukhin 2022-10-08 03:41:55 +03:00 committed by GitHub
commit 2bb9622edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1302 additions and 819 deletions

View File

@ -30,7 +30,7 @@ export const Alert: React.FC<{
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
aria-hidden="true"
/>
<div className="ml-3">
<div className="ml-3 grow">
<header className={clsx("text-sm font-medium", headingColor)}>
{heading}
</header>

View File

@ -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<CodeEditorProps> = ({
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<CodeEditorProps> = ({
const onSubmitRef = useRef<typeof onSubmit | null>(null);
onSubmitRef.current = onSubmit;
const editorEl = useRef<AceEditor | null>(null);
return (
<AceEditor
ref={editorEl}
value={value}
mode="golang"
theme="github"
@ -59,6 +66,14 @@ export const CodeEditor: FC<CodeEditorProps> = ({
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",
}))}
/>
);
};

View File

@ -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 (
<MessageAlert heading="Function Display Failed">
<div className="space-y-2">
<span
className="underline decoration-dashed cursor-pointer"
onClick={() => setExpanded(!expanded)}
>
{expanded ? "Hide" : "Show"} error details
</span>
{expanded ? <SquiggleErrorAlert error={error} /> : null}
</div>
</MessageAlert>
);
};
export const FunctionChart: React.FC<FunctionChartProps> = ({
fn,
chartSettings,
@ -26,7 +51,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
distributionPlotSettings,
height,
}) => {
if (fn.parameters.length > 1) {
console.log(fn.parameters().length);
if (fn.parameters().length !== 1) {
return (
<MessageAlert heading="Function Display Not Supported">
Only functions with one parameter are displayed.
@ -47,9 +73,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
const validResult = getValidResult();
if (validResult.tag === "Error") {
return (
<ErrorAlert heading="Error">{validResult.value.toString()}</ErrorAlert>
);
return <FunctionCallErrorAlert error={validResult.value} />;
}
switch (validResult.value.tag) {

View File

@ -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,16 +66,11 @@ type ProjectExecutionProps = {
};
const defaultOnChange = () => {};
const defaultImports: JsImports = {};
const defaultContinues: string[] = [];
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
(props: SquiggleChartProps) => {
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
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,
@ -93,32 +83,8 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
title,
xAxisType = "number",
distributionChartActions,
enableLocalSettings = false,
code,
continues = [],
} = props;
const p = React.useMemo(() => {
if (props.project) {
return props.project;
} else {
const p = SqProject.create();
if (props.environment) {
p.setEnvironment(props.environment);
}
return p;
}
}, [props.project, props.environment]);
const { result, bindings } = useSquiggle({
continues,
project: p,
code,
jsImports,
onChange,
executionId,
});
const distributionPlotSettings = {
showSummary,
logX,
@ -138,13 +104,51 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
count: diagramCount,
};
const resultToRender = resultMap(result, (value) =>
value.tag === SqValueTag.Void ? bindings.asValue() : value
);
return { distributionPlotSettings, chartSettings };
};
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
(props) => {
const { distributionPlotSettings, chartSettings } =
splitSquiggleChartSettings(props);
const {
code,
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(() => {
if (props.project) {
return props.project;
} else {
const p = SqProject.create();
if (props.environment) {
p.setEnvironment(props.environment);
}
return p;
}
}, [props.project, props.environment]);
const resultAndBindings = useSquiggle({
continues,
project: p,
code,
jsImports,
onChange,
executionId,
});
const valueToRender = getValueToRender(resultAndBindings);
return (
<SquiggleViewer
result={resultToRender}
result={valueToRender}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}

View File

@ -1,13 +1,21 @@
import React from "react";
import { CodeEditor } from "./CodeEditor";
import { SquiggleContainer } from "./SquiggleContainer";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
import { useMaybeControlledValue } from "../lib/hooks";
import {
splitSquiggleChartSettings,
SquiggleChartProps,
} from "./SquiggleChart";
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
import { JsImports } from "../lib/jsImports";
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
import { SquiggleViewer } from "./SquiggleViewer";
import { getErrorLocations, getValueToRender } from "../lib/utility";
const WrappedCodeEditor: React.FC<{
code: string;
setCode: (code: string) => void;
}> = ({ code, setCode }) => (
errorLocations?: SqLocation[];
}> = ({ code, setCode, errorLocations }) => (
<div className="border border-grey-200 p-2 m-4">
<CodeEditor
value={code}
@ -15,6 +23,7 @@ const WrappedCodeEditor: React.FC<{
oneLine={true}
showGutter={false}
height={20}
errorLocations={errorLocations}
/>
</div>
);
@ -24,6 +33,9 @@ export type SquiggleEditorProps = SquiggleChartProps & {
onCodeChange?: (code: string) => void;
};
const defaultOnChange = () => {};
const defaultImports: JsImports = {};
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
const [code, setCode] = useMaybeControlledValue({
value: props.code,
@ -31,11 +43,54 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (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 (
<SquiggleContainer>
<WrappedCodeEditor code={code} setCode={setCode} />
<SquiggleChart {...chartProps} />
<WrappedCodeEditor
code={code}
setCode={setCode}
errorLocations={errorLocations}
/>
<SquiggleViewer
result={valueToRender}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings}
/>
</SquiggleContainer>
);
};

View File

@ -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<Props> = ({ error }) => {
return <ErrorAlert heading="Error">{error.toString()}</ErrorAlert>;
const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => {
const location = frame.location();
return (
<div>
{frame.name()}
{location
? ` at line ${location.start.line}, column ${location.start.column}`
: ""}
</div>
);
};
const StackTrace: React.FC<Props> = ({ error }) => {
const frames = error.getFrameArray();
return frames.length ? (
<div>
<div className="font-medium">Stack trace:</div>
<div className="ml-4">
{frames.map((frame, i) => (
<StackTraceFrame frame={frame} key={i} />
))}
</div>
</div>
) : null;
};
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
return (
<ErrorAlert heading="Error">
<div className="space-y-4">
<div>{error.toString()}</div>
<StackTrace error={error} />
</div>
</ErrorAlert>
);
};

View File

@ -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<PlaygroundProps> = ({
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<PlaygroundProps> = ({
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 : (
<div className="relative">
{isRunning ? (
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
) : null}
<SquiggleChart
code={renderedCode}
executionId={executionId}
environment={env}
{...vars}
jsImports={imports}
<SquiggleViewer
result={valueToRender}
environment={environment}
height={vars.chartHeight || 150}
distributionPlotSettings={{
showSummary: vars.showSummary ?? false,
logX: vars.logX ?? false,
expY: vars.expY ?? false,
format: vars.tickFormat,
minX: vars.minX,
maxX: vars.maxX,
title: vars.title,
actions: vars.distributionChartActions,
}}
chartSettings={{
start: vars.diagramStart ?? 0,
stop: vars.diagramStop ?? 10,
count: vars.diagramCount ?? 20,
}}
enableLocalSettings={true}
/>
</div>
);
const errorLocations = getErrorLocations(resultAndBindings.result);
const firstTab = vars.showEditor ? (
<div className="border border-slate-200">
<CodeEditor
errorLocations={errorLocations}
value={code}
onChange={setCode}
onSubmit={run}

View File

@ -1,4 +1,4 @@
import { SqValue, SqValueLocation } from "@quri/squiggle-lang";
import { SqValue } from "@quri/squiggle-lang";
import React, { useContext, useReducer } from "react";
import { Tooltip } from "../ui/Tooltip";
import { LocalItemSettings, MergedItemSettings } from "./utils";
@ -70,7 +70,7 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
<div className="flex w-full">
{location.path.items.length ? (
<div
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
className="shrink-0 border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
onClick={toggleCollapsed}
></div>
) : null}

View File

@ -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<SqValue, SqError>;
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,
]

View File

@ -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<a, b>(x: result<a, b>[]): result<a[], b> {
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 [];
}
}

View File

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

View File

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

View File

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

View File

@ -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)}}", ())
})

View File

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

View File

@ -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
<top> at line 8, column 3
`)
})
})

View File

@ -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", () => {

View File

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

View File

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

View File

@ -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() : ""
);
};

View File

@ -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("<string>");
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;

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { isParenthesisNode } from "mathjs";
import { SqProject } from "./SqProject";
type PathItem = string | number;

View File

@ -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";

View File

@ -5,7 +5,7 @@ let nameSpace = "" // no namespaced versions
type simpleDefinition = {
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorMessage>,
}
let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
@ -22,7 +22,7 @@ let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
let makeFn = (
name: string,
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorMessage>,
) => makeFnMany(name, [{inputs: inputs, fn: fn}])
let library = [

View File

@ -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<float>,
currentMarginalReturns: result<array<float>, errorValue>,
currentMarginalReturns: result<array<float>, errorMessage>,
}
let findBiggestElementIndex = (xs: array<float>) =>
E.A.reducei(xs, 0, (acc, newElement, index) => {
@ -255,7 +255,7 @@ module DiminishingReturns = {
| false => acc
}
})
type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, errorValue>
type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, errorMessage>
// 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
},
(),

View File

@ -4,7 +4,7 @@ open FunctionRegistry_Helpers
let makeFn = (
name: string,
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorMessage>,
) =>
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)
}

View File

@ -17,7 +17,7 @@ module Internals = {
->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value]))
->Wrappers.evArray
let fromList = (items: array<Reducer_T.value>): result<Reducer_T.value, errorValue> =>
let fromList = (items: array<Reducer_T.value>): result<Reducer_T.value, errorMessage> =>
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

View File

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

View File

@ -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<frType>) => {
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
}
})

View File

@ -30,11 +30,11 @@ module Internals = {
let map = (
array: array<Reducer_T.value>,
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

View File

@ -16,30 +16,30 @@ let inputsToDist = (inputs: array<Reducer_T.value>, 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<Reducer_T.value, Reducer_ErrorValue.errorValue> =>
let toType = (r): result<Reducer_T.value, SqError.Message.t> =>
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)
},
(),

View File

@ -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<Reducer_T.value, Reducer_ErrorValue.errorValue> =>
let toType = (r): result<Reducer_T.value, SqError.Message.t> =>
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<Reducer_T.value>,
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) =>

View File

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

View File

@ -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<string>,
reducerErrorValue,
> => project->Private.getIncludes(sourceId)
let getIncludes = (project: reducerProject, sourceId: string): result<array<string>, 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<squiggleValue, error> =>
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, reducerErrorValue>,
squiggleValue_Record,
) => Private.evaluate(sourceCode)
let evaluate = (sourceCode: string): (result<squiggleValue, error>, 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<squiggleValue>,
// environment: environment,
// ): result<squiggleValue, reducerErrorValue> => {
// let accessors = ReducerProject_ProjectAccessors_T.identityAccessorsWithEnvironment(environment)
// Reducer_Expression_Lambda.foreignFunctionInterface(
// lambdaValue,
// argArray,
// accessors,
// Reducer_Expression.reduceExpressionInProject,
// )
// }

View File

@ -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<syntaxErrorLocation> =>
switch e {
| RESyntaxError(_, optionalLocation) => optionalLocation
| _ => None
}
@genType
let createTodoError = (v: string) => Reducer_ErrorValue.RETodo(v)
@genType
let createOtherError = (v: string) => Reducer_ErrorValue.REOther(v)

View File

@ -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<squiggleValue, reducerErrorValue>) =>
let toStringResult = (variantResult: result<squiggleValue, error>) =>
Reducer_Value.toStringResult(variantResult)
@genType

View File

@ -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<string> => {
v.parameters
}
let parameters = (v: squiggleValue_Lambda): array<string> => Reducer_Lambda.parameters(v)

View File

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

View File

@ -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<frType>,
run: (
array<Reducer_T.value>,
Reducer_T.environment,
Reducer_T.context,
Reducer_T.reducerFn,
) => result<Reducer_T.value, errorValue>,
) => result<Reducer_T.value, errorMessage>,
}
type function = {
@ -122,11 +122,11 @@ module FnDefinition = {
let run = (
t: t,
args: array<Reducer_T.value>,
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<Reducer_T.value>,
env: Reducer_T.environment,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
): result<Reducer_T.value, errorValue> => {
): result<Reducer_T.value, errorMessage> => {
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
}
}

View File

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

View File

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

View File

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

View File

@ -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<result<Reducer_T.value, Reducer_ErrorValue.errorValue>>
// type dispatchChainPieceWithReducer = (
// Reducer_Value.functionCall,
// ProjectAccessorsT.t,
// Reducer_T.reducerFn,
// ) => option<result<Reducer_T.value, Reducer_ErrorValue.errorValue>>
// // This is a switch statement case implementation: get the arguments and compute the result
// type genericIEvFunction = (
// array<Reducer_T.value>,
// ProjectAccessorsT.t,
// ) => result<Reducer_T.value, Reducer_ErrorValue.errorValue>

View File

@ -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<string>, int, int)
| REArrayIndexNotFound(string, int)
| REAssignmentExpected
| REDistributionError(DistributionTypes.error)
| REExpectedType(string, string)
| REExpressionExpected
| REFunctionExpected(string)
| REFunctionNotFound(string)
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception
| REMacroNotFound(string)
| RENotAFunction(string)
| REOperationError(Operation.operationError)
| RERecordPropertyNotFound(string, string)
| RESymbolNotFound(string)
| RESyntaxError(string, option<syntaxErrorLocation>)
| 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))

View File

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

View File

@ -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),
| 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
)
| _ =>
RENotAFunction(lambda->Reducer_Value.toString)->Reducer_ErrorValue.ErrorException->raise
(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<T.expression, errorValue> =>
peggyCode->Reducer_Peggy_Parse.parse->Result.map(Reducer_Peggy_ToExpression.fromNode)
let parse = (peggyCode: string): result<T.expression, Reducer_Peggy_Parse.parseError> =>
peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode)
let evaluate = (expression: T.expression): result<T.value, errorValue> => {
let context = Reducer_Context.createDefaultContext()
let createDefaultContext = () =>
Reducer_Context.createContext(SquiggleLibrary_StdLib.stdLib, Reducer_Context.defaultEnvironment)
let evaluate = (expression: T.expression): result<T.value, SqError.t> => {
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<T.value, errorValue> =>
parse(peggyCode)->Result.flatMap(evaluate)
let evaluateString = (peggyCode: string): result<T.value, SqError.t> =>
parse(peggyCode)->E.R2.errMap(e => e->SqError.fromParseError)->Result.flatMap(evaluate)
}

View File

@ -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<T.expression>) => anArray->T.EArray
let eArray = (anArray: array<T.expression>): expressionContent => anArray->T.EArray
let eBool = aBool => aBool->T.IEvBool->T.EValue
let eCall = (fn: expression, args: array<expression>): expression => T.ECall(fn, args)
let eCall = (fn: expression, args: array<expression>): expressionContent => T.ECall(fn, args)
let eLambda = (parameters: array<string>, expr: expression) => T.ELambda(parameters, expr)
let eLambda = (
parameters: array<string>,
expr: expression,
name: option<string>,
): 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>): expression => T.EBlock(exprs)
let eBlock = (exprs: array<expression>): expressionContent => T.EBlock(exprs)
let eProgram = (exprs: array<expression>): expression => T.EProgram(exprs)
let eProgram = (exprs: array<expression>): 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

View File

@ -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<string>,
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<Reducer_T.value>,
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: ["..."],
}

View File

@ -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<t, Reducer_ErrorValue.errorValue>): result<
t,
Reducer_ErrorValue.errorValue,
> => {
Js.log(toStringResult(r))
r
}
let resultToValue = (rExpression: result<t, Reducer_ErrorValue.t>): t =>
switch rExpression {
| Ok(expression) => expression
| Error(errorValue) => Reducer_ErrorValue.toException(errorValue)
}

View File

@ -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<Reducer_Peggy_Parse.location> => t.location
@genType
let getName = (t: Reducer_T.frame): string => t.name
}
let make = (): t => list{}
let extend = (t: t, name: string, location: option<Reducer_Peggy_Parse.location>) =>
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<Reducer_T.frame> => t->Belt.List.toArray
@genType
let getTopFrame = (t: t): option<Reducer_T.frame> => t->Belt.List.head
let isEmpty = (t: t): bool =>
switch t->Belt.List.head {
| Some(_) => true
| None => false
}

View File

@ -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<string>,
parameters: array<string>,
bindings: Reducer_T.bindings,
body: Reducer_T.expression,
location: Reducer_Peggy_Parse.location,
): t => {
let lambda = (
arguments: array<Reducer_T.value>,
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<string> => {
switch t {
| FnLambda({parameters}) => parameters
| FnBuiltin(_) => ["..."]
}
}
let doLambdaCallFrom = (
t: t,
args: array<Reducer_T.value>,
context: Reducer_T.context,
reducer,
location: option<Reducer_Peggy_Parse.location>,
) => {
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)
}

View File

@ -0,0 +1,8 @@
type t = Reducer_T.lambdaValue
let name = (t: t): string => {
switch t {
| FnLambda({name}) => name->E.O2.default("<anonymous>")
| FnBuiltin({name}) => name
}
}

View File

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

View File

@ -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<node, parseError>
@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<node, errorValue> =>
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<node>}
@ -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<nodeKeyValue>}
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": node}
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": node, "name": option<string>}
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<nodeIdentifier>): 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<node>, 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<peggyNode>, separator: string): string =>
nodes->Js.Array2.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("")
let pgNodesToStringUsingSeparator = (nodes: array<ast>, 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<node, errorValue>): string =>
switch rNode {
| Ok(node) => toString(node)
| Error(error) => `Error(${errorToString(error)})`
let toStringError = (error: parseError): string => {
let SyntaxError(message, _) = error
`Syntax Error: ${message}}`
}
let toStringResult = (rNode: parseResult): string =>
switch rNode {
| Ok(node) => node->toString
| Error(error) => `Error(${error->toStringError})`
}

View File

@ -3,23 +3,27 @@ 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 ast = Parse.nodeToAST(node)
let content: expressionContent = {
let caseBlock = nodeBlock =>
ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode))
let caseProgram = nodeProgram =>
ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode))
let caseLambda = (nodeLambda: Parse.nodeLambda): expression => {
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)
ExpressionBuilder.eLambda(args, body, nodeLambda["name"])
}
let caseRecord = (nodeRecord): expression => {
let caseRecord = (nodeRecord): expressionContent => {
nodeRecord["elements"]
->Js.Array2.map(keyValueNode => (
keyValueNode["key"]->fromNode,
@ -28,30 +32,30 @@ let rec fromNode = (node: Parse.node): expression => {
->ExpressionBuilder.eRecord
}
switch Parse.castNodeType(node) {
| PgNodeBlock(nodeBlock) => caseBlock(nodeBlock)
| PgNodeProgram(nodeProgram) => caseProgram(nodeProgram)
| PgNodeArray(nodeArray) =>
switch ast.content {
| ASTBlock(nodeBlock) => caseBlock(nodeBlock)
| ASTProgram(nodeProgram) => caseProgram(nodeProgram)
| ASTArray(nodeArray) =>
ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode))
| PgNodeRecord(nodeRecord) => caseRecord(nodeRecord)
| PgNodeBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"])
| PgNodeCall(nodeCall) =>
| ASTRecord(nodeRecord) => caseRecord(nodeRecord)
| ASTBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"])
| ASTCall(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) =>
| 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"])])
| PgNodeLambda(nodeLambda) => caseLambda(nodeLambda)
| PgNodeLetStatement(nodeLetStatement) =>
| ASTLambda(nodeLambda) => caseLambda(nodeLambda)
| ASTLetStatement(nodeLetStatement) =>
ExpressionBuilder.eLetStatement(
nodeLetStatement["variable"]["value"],
fromNode(nodeLetStatement["value"]),
)
| PgNodeModuleIdentifier(nodeModuleIdentifier) =>
| ASTModuleIdentifier(nodeModuleIdentifier) =>
ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"])
| PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
| PgNodeTernary(nodeTernary) =>
| ASTString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
| ASTTernary(nodeTernary) =>
ExpressionBuilder.eTernary(
fromNode(nodeTernary["condition"]),
fromNode(nodeTernary["trueExpression"]),
@ -59,6 +63,12 @@ let rec fromNode = (node: Parse.node): expression => {
)
// | PgNodeTypeIdentifier(nodeTypeIdentifier) =>
// ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"])
| PgNodeVoid(_) => ExpressionBuilder.eVoid
| ASTVoid(_) => ExpressionBuilder.eVoid
}
}
{
ast: ast,
content: content,
}
}

View File

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

View File

@ -15,14 +15,18 @@ type rec value =
| IEvVoid
@genType.opaque and arrayValue = array<value>
@genType.opaque and map = Belt.Map.String.t<value>
and lambdaBody = (array<value>, environment, reducerFn) => value
and lambdaBody = (array<value>, context, reducerFn) => value
@genType.opaque
and lambdaValue = {
and lambdaValue =
| FnLambda({
parameters: array<string>,
body: lambdaBody,
}
location: Reducer_Peggy_Parse.location,
name: option<string>,
})
| FnBuiltin({body: lambdaBody, name: string})
@genType.opaque and lambdaDeclaration = Declaration.declaration<lambdaValue>
and expression =
and expressionContent =
| EBlock(array<expression>)
// 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<expression>)
@ -32,18 +36,34 @@ and expression =
| ETernary(expression, expression, expression)
| EAssign(string, expression)
| ECall(expression, array<expression>)
| ELambda(array<string>, expression)
| ELambda(array<string>, expression, option<string>)
| EValue(value)
and expression = {
ast: Reducer_Peggy_Parse.ast,
content: expressionContent,
}
and namespace = Belt.Map.String.t<value>
and bindings = {
namespace: namespace,
parent: option<bindings>,
}
@genType.opaque
and frame = {
name: string,
location: option<Reducer_Peggy_Parse.location>, // can be empty for calls from builtin functions
}
@genType.opaque and frameStack = list<frame>
and context = {
bindings: bindings,
environment: environment,
frameStack: frameStack,
inFunction: option<lambdaValue>,
}
and reducerFn = (expression, context) => (value, context)
let topFrameName = "<top>"

View File

@ -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<t, ErrorValue.errorValue>): string =>
let toStringResultOkless = (codeResult: result<t, SqError.t>): 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<t>): array<t> => arr
let resultToValue = (rExpression: result<t, Reducer_ErrorValue.t>): t =>
let resultToValue = (rExpression: result<t, SqError.Message.t>): 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

View File

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

View File

@ -1,10 +1,7 @@
@module("./ReducerProject_IncludeParser.js")
external parse__: string => array<array<string>> = "parse"
let parseIncludes = (expr: string): result<
array<(string, string)>,
Reducer_ErrorValue.errorValue,
> =>
let parseIncludes = (expr: string): result<array<(string, string)>, 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
}

View File

@ -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<string> => 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 => {

View File

@ -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<Parse.node, errorValue>
type rawParseArgumentType = result<Parse.node, SqError.t>
type rawParseType = option<rawParseArgumentType>
type expressionArgumentType = result<ExpressionT.t, errorValue>
type expressionArgumentType = result<ExpressionT.t, SqError.t>
type expressionType = option<expressionArgumentType>
type continuationArgumentType = Reducer_T.namespace
type continuationType = option<continuationArgumentType>
type continuationResultType = option<result<continuationArgumentType, errorValue>>
type resultArgumentType = result<Reducer_T.value, errorValue>
type resultArgumentType = result<Reducer_T.value, SqError.t>
type resultType = option<resultArgumentType>
type continuesArgumentType = array<string>
type continuesType = array<string>
type includesArgumentType = string
type includesType = result<array<string>, errorValue>
type includesType = result<array<string>, SqError.t>
type importAsVariablesType = array<(string, string)>
type projectItem = {
source: sourceType,
sourceId: string,
rawParse: rawParseType,
expression: expressionType,
continuation: continuationArgumentType,

View File

@ -13,4 +13,4 @@ type t = project
let getSourceIds = (project: t): array<string> => 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))

View File

@ -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<string>, int, int)
| REArrayIndexNotFound(string, int)
| REAssignmentExpected
| REDistributionError(DistributionTypes.error)
| REExpectedType(string, string)
| REExpressionExpected
| REFunctionExpected(string)
| REFunctionNotFound(string)
| REJavaScriptExn(option<string>, option<string>) // 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<Reducer_T.frame> => 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<Reducer_T.frame> => 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)
}
}

View File

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