Merge branch 'develop' into project-serialization

This commit is contained in:
Sam Nolan 2022-10-11 12:44:36 +11:00
commit fb5fd8edf8
109 changed files with 2015 additions and 1212 deletions

2
.github/CODEOWNERS vendored
View File

@ -24,7 +24,7 @@
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr *.json @quinn-dougherty @Hazelfire @berekuk @OAGr
*.y*ml @quinn-dougherty @berekuk @OAGr *.y*ml @quinn-dougherty @berekuk @OAGr
*.config.js @Hazelfire @berekuk @OAGr *.config.js @Hazelfire @berekuk @OAGr
netlify.toml @quinn-dougherty @OAGr @berekuk @Hazelfire vercel.json @OAGr @berekuk @Hazelfire
# Documentation # Documentation
*.md @quinn-dougherty @OAGr @Hazelfire *.md @quinn-dougherty @OAGr @Hazelfire

View File

@ -1,4 +1,4 @@
name: Squiggle packages check name: Squiggle packages checks
on: on:
push: push:
@ -9,217 +9,38 @@ on:
branches: branches:
- master - master
- develop - develop
- reducer-dev
- epic-reducer-project env:
- epic-0.5.0 TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: quantified-uncertainty
jobs: jobs:
pre_check: build-test-lint:
name: Precheck for skipping redundant jobs name: Build, test, lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
steps: steps:
- id: skip_lang_check - uses: actions/checkout@v3
name: Check if the changes are about squiggle-lang src files - name: Setup Node.js environment
uses: fkirc/skip-duplicate-actions@v5.2.0 uses: actions/setup-node@v3
with: with:
paths: '["packages/squiggle-lang/**"]' node-version: 16
- id: skip_components_check cache: 'yarn'
name: Check if the changes are about components src files - name: Install dependencies
uses: fkirc/skip-duplicate-actions@v5.2.0 run: yarn --frozen-lockfile
with: - name: Turbo run
paths: '["packages/components/**"]' run: npx turbo run build test lint bundle
- id: skip_website_check
name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@v5.2.0
with:
paths: '["packages/website/**"]'
- id: skip_vscodeext_check
name: Check if the changes are about vscode extension src files
uses: fkirc/skip-duplicate-actions@v5.2.0
with:
paths: '["packages/vscode-ext/**"]'
- id: skip_cli_check
name: Check if the changes are about cli src files
uses: fkirc/skip-duplicate-actions@v5.2.0
with:
paths: '["packages/cli/**"]'
lang-lint: coverage:
name: Language lint name: Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/squiggle-lang
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Dependencies - name: Setup Node.js environment
run: cd ../../ && yarn uses: actions/setup-node@v2
- name: Check rescript lint
run: yarn lint:rescript
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with: with:
dry: true node-version: 16
prettier_options: --check packages/squiggle-lang cache: 'yarn'
- name: Install dependencies
lang-build-test-bundle: run: yarn
name: Language build, test, and bundle - name: Coverage
runs-on: ubuntu-latest run: npx turbo run coverage
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/squiggle-lang
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build rescript codebase
run: yarn build
- name: Run rescript tests
run: yarn test:rescript
- name: Run typescript tests
run: yarn test:ts
- name: Run webpack
run: yarn bundle
- name: Upload rescript coverage report
run: yarn coverage:rescript:ci
- name: Upload typescript coverage report
run: yarn coverage:ts:ci
components-lint:
name: Components lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/components
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/components --ignore-path packages/components/.prettierignore
components-bundle-build-test:
name: Components bundle, build and test
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/components
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build rescript codebase in squiggle-lang
run: cd ../squiggle-lang && yarn build
- name: Run webpack
run: yarn bundle
- name: Build storybook
run: yarn build
- name: Test components
run: yarn test
website-lint:
name: Website lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/website
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/website
website-build:
name: Website build
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_website != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') || (needs.pre_check.outputs.should_skip_components != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/website
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build rescript in squiggle-lang
run: cd ../squiggle-lang && yarn build
- name: Build components
run: cd ../components && yarn build
- name: Build website assets
run: yarn build
vscode-ext-lint:
name: VS Code extension lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/vscode-ext
vscode-ext-build:
name: VS Code extension build
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} || (needs.pre_check.outputs.should_skip_vscodeext != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build
run: yarn compile
cli-lint:
name: CLI lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/cli
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/cli

2
.gitignore vendored
View File

@ -11,3 +11,5 @@ yarn-error.log
.vscode .vscode
todo.txt todo.txt
result result
shell.nix
.turbo

View File

@ -16,7 +16,7 @@ Squiggle is currently pre-alpha.
# Bug reports # Bug reports
Anyone (with a github account) can file an issue at any time. Please allow Quinn, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates. Anyone (with a github account) can file an issue at any time. Please allow Slava, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
# Project structure # Project structure
@ -28,7 +28,7 @@ Squiggle is a **monorepo** with three **packages**.
# Deployment ops # Deployment ops
We use netlify, and it should only concern Quinn, Sam, and Ozzie. We use Vercel, and it should only concern Slava, Sam, and Ozzie.
# Development environment, building, testing, dev server # Development environment, building, testing, dev server
@ -56,9 +56,9 @@ If you absolutely must, please prefix your commit message with `hotfix: `.
Please work against `develop` branch. **Do not** work against `master`. Please work against `develop` branch. **Do not** work against `master`.
- For rescript code: Quinn and Ozzie are reviewers - For rescript code: Slava and Ozzie are reviewers
- For js or typescript code: Sam and Ozzie are reviewers - For js or typescript code: Sam and Ozzie are reviewers
- For ops code (i.e. yaml, package.json): Quinn and Sam are reviewers - For ops code (i.e. yaml, package.json): Slava and Sam are reviewers
Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges. Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges.

View File

@ -21,10 +21,10 @@ _An estimation language_.
## Our deployments ## Our deployments
- **website/docs prod**: https://squiggle-language.com [![Netlify Status](https://api.netlify.com/api/v1/badges/2139af5c-671d-473d-a9f6-66c96077d8a1/deploy-status)](https://app.netlify.com/sites/squiggle-documentation/deploys) - **website/docs prod**: https://squiggle-language.com
- **website/docs staging**: https://develop--squiggle-documentation.netlify.app/ - **website/docs staging**: https://preview.squiggle-language.com
- **components storybook prod**: https://squiggle-components.netlify.app/ [![Netlify Status](https://api.netlify.com/api/v1/badges/b7f724aa-6b20-4d0e-bf86-3fcd1a3e9a70/deploy-status)](https://app.netlify.com/sites/squiggle-components/deploys) - **components storybook prod**: https://components.squiggle-language.com
- **components storybook staging**: https://develop--squiggle-components.netlify.app/ - **components storybook staging**: https://preview-components.squiggle-language.com
- **legacy (2020) playground**: https://playground.squiggle-language.com - **legacy (2020) playground**: https://playground.squiggle-language.com
## Packages ## Packages
@ -51,7 +51,19 @@ For any project in the repo, begin by running `yarn` in the top level
yarn yarn
``` ```
See `packages/*/README.md` to work with whatever project you're interested in. Then use `turbo` to build the specific packages or the entire monorepo:
```sh
turbo run build
```
Or:
```sh
turbo run build --filter=@quri/squiggle-components
```
You can also run specific npm scripts for the package you're working on. See `packages/*/README.md` for the details.
# Contributing # Contributing

View File

@ -2,12 +2,11 @@
"private": true, "private": true,
"name": "squiggle", "name": "squiggle",
"scripts": { "scripts": {
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules", "nodeclean": "rm -r node_modules && rm -r packages/*/node_modules"
"format:all": "prettier --write . && cd packages/squiggle-lang && yarn format",
"lint:all": "prettier --check . && cd packages/squiggle-lang && yarn lint:rescript"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^2.7.1" "prettier": "^2.7.1",
"turbo": "^1.5.5"
}, },
"workspaces": [ "workspaces": [
"packages/*" "packages/*"

View File

@ -7,7 +7,9 @@
"bin": "index.js", "bin": "index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node ." "start": "node .",
"lint": "prettier --check .",
"format": "prettier --write ."
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@ -1,8 +0,0 @@
[build]
base = "packages/components/"
command = "cd ../squiggle-lang && yarn build && cd ../components && yarn build"
publish = "storybook-static/"
ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../squiggle-lang"
[build.environment]
NETLIFY_USE_YARN = "true"

View File

@ -10,6 +10,7 @@
"@hookform/resolvers": "^2.9.8", "@hookform/resolvers": "^2.9.8",
"@quri/squiggle-lang": "^0.5.0", "@quri/squiggle-lang": "^0.5.0",
"@react-hook/size": "^2.1.2", "@react-hook/size": "^2.1.2",
"@types/uuid": "^8.3.4",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"framer-motion": "^7.5.1", "framer-motion": "^7.5.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -18,6 +19,7 @@
"react-hook-form": "^7.36.1", "react-hook-form": "^7.36.1",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"react-vega": "^7.6.0", "react-vega": "^7.6.0",
"uuid": "^9.0.0",
"vega": "^5.22.1", "vega": "^5.22.1",
"vega-embed": "^6.21.0", "vega-embed": "^6.21.0",
"vega-lite": "^5.5.0", "vega-lite": "^5.5.0",
@ -42,6 +44,7 @@
"@types/node": "^18.8.0", "@types/node": "^18.8.0",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/uuid": "^8.3.4",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"canvas": "^2.10.1", "canvas": "^2.10.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -82,7 +85,8 @@
"format": "prettier --write .", "format": "prettier --write .",
"prepack": "yarn run build:cjs && yarn run bundle", "prepack": "yarn run build:cjs && yarn run bundle",
"test": "jest", "test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand" "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

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

View File

@ -5,6 +5,8 @@ import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-golang"; import "ace-builds/src-noconflict/mode-golang";
import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-github";
import { SqLocation } from "@quri/squiggle-lang";
interface CodeEditorProps { interface CodeEditorProps {
value: string; value: string;
onChange: (value: string) => void; onChange: (value: string) => void;
@ -13,15 +15,17 @@ interface CodeEditorProps {
width?: number; width?: number;
height: number; height: number;
showGutter?: boolean; showGutter?: boolean;
errorLocations?: SqLocation[];
} }
export const CodeEditor: FC<CodeEditorProps> = ({ export const CodeEditor: FC<CodeEditorProps> = ({
value, value,
onChange, onChange,
onSubmit, onSubmit,
height,
oneLine = false, oneLine = false,
showGutter = false, showGutter = false,
height, errorLocations = [],
}) => { }) => {
const lineCount = value.split("\n").length; const lineCount = value.split("\n").length;
const id = useMemo(() => _.uniqueId(), []); const id = useMemo(() => _.uniqueId(), []);
@ -30,8 +34,11 @@ export const CodeEditor: FC<CodeEditorProps> = ({
const onSubmitRef = useRef<typeof onSubmit | null>(null); const onSubmitRef = useRef<typeof onSubmit | null>(null);
onSubmitRef.current = onSubmit; onSubmitRef.current = onSubmit;
const editorEl = useRef<AceEditor | null>(null);
return ( return (
<AceEditor <AceEditor
ref={editorEl}
value={value} value={value}
mode="golang" mode="golang"
theme="github" theme="github"
@ -59,6 +66,14 @@ export const CodeEditor: FC<CodeEditorProps> = ({
exec: () => onSubmitRef.current?.(), 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 * 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 { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number"; import { FunctionChart1Number } from "./FunctionChart1Number";
import { DistributionPlottingSettings } from "./DistributionChart"; import { DistributionPlottingSettings } from "./DistributionChart";
import { ErrorAlert, MessageAlert } from "./Alert"; import { MessageAlert } from "./Alert";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
export type FunctionChartSettings = { export type FunctionChartSettings = {
start: number; start: number;
@ -19,6 +25,25 @@ interface FunctionChartProps {
height: number; 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> = ({ export const FunctionChart: React.FC<FunctionChartProps> = ({
fn, fn,
chartSettings, chartSettings,
@ -26,7 +51,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
distributionPlotSettings, distributionPlotSettings,
height, height,
}) => { }) => {
if (fn.parameters.length > 1) { console.log(fn.parameters().length);
if (fn.parameters().length !== 1) {
return ( return (
<MessageAlert heading="Function Display Not Supported"> <MessageAlert heading="Function Display Not Supported">
Only functions with one parameter are displayed. Only functions with one parameter are displayed.
@ -47,9 +73,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
const validResult = getValidResult(); const validResult = getValidResult();
if (validResult.tag === "Error") { if (validResult.tag === "Error") {
return ( return <FunctionCallErrorAlert error={validResult.value} />;
<ErrorAlert heading="Error">{validResult.value.toString()}</ErrorAlert>
);
} }
switch (validResult.value.tag) { switch (validResult.value.tag) {

View File

@ -1,24 +1,17 @@
import * as React from "react"; import * as React from "react";
import { import { SqValue, environment, SqProject } from "@quri/squiggle-lang";
SqValue,
environment,
defaultEnvironment,
resultMap,
SqValueTag,
} from "@quri/squiggle-lang";
import { useSquiggle } from "../lib/hooks"; import { useSquiggle } from "../lib/hooks";
import { SquiggleViewer } from "./SquiggleViewer"; import { SquiggleViewer } from "./SquiggleViewer";
import { JsImports } from "../lib/jsImports"; import { JsImports } from "../lib/jsImports";
import { getValueToRender } from "../lib/utility";
export interface SquiggleChartProps { export type SquiggleChartProps = {
/** The input string for squiggle */ /** The input string for squiggle */
code?: string; code: string;
/** Allows to re-run the code if code hasn't changed */ /** Allows to re-run the code if code hasn't changed */
executionId?: number; executionId?: number;
/** If the output requires monte carlo sampling, the amount of samples */ /** If the output requires monte carlo sampling, the amount of samples */
sampleCount?: number; sampleCount?: number;
/** The amount of points returned to draw the distribution */
environment?: environment;
/** If the result is a function, where the function domain starts */ /** If the result is a function, where the function domain starts */
diagramStart?: number; diagramStart?: number;
/** If the result is a function, where the function domain ends */ /** If the result is a function, where the function domain ends */
@ -26,7 +19,7 @@ export interface SquiggleChartProps {
/** If the result is a function, the amount of stops sampled */ /** If the result is a function, the amount of stops sampled */
diagramCount?: number; diagramCount?: number;
/** When the squiggle code gets reevaluated */ /** When the squiggle code gets reevaluated */
onChange?(expr: SqValue | undefined): void; onChange?(expr: SqValue | undefined, sourceName: string): void;
/** CSS width of the element */ /** CSS width of the element */
width?: number; width?: number;
height?: number; height?: number;
@ -53,21 +46,31 @@ export interface SquiggleChartProps {
/** Whether to show vega actions to the user, so they can copy the chart spec */ /** Whether to show vega actions to the user, so they can copy the chart spec */
distributionChartActions?: boolean; distributionChartActions?: boolean;
enableLocalSettings?: boolean; enableLocalSettings?: boolean;
} } & (StandaloneExecutionProps | ProjectExecutionProps);
// Props needed for a standalone execution
type StandaloneExecutionProps = {
project?: undefined;
continues?: undefined;
/** The amount of points returned to draw the distribution, not needed if using a project */
environment?: environment;
};
// Props needed when executing inside a project.
type ProjectExecutionProps = {
environment?: undefined;
/** The project that this execution is part of */
project: SqProject;
/** What other squiggle sources from the project to continue. Default [] */
continues?: string[];
};
const defaultOnChange = () => {}; const defaultOnChange = () => {};
const defaultImports: JsImports = {}; const defaultImports: JsImports = {};
const defaultContinues: string[] = [];
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
({ const {
code = "",
executionId = 0,
environment,
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
height = 200,
jsImports = defaultImports,
showSummary = false, showSummary = false,
width,
logX = false, logX = false,
expY = false, expY = false,
diagramStart = 0, diagramStart = 0,
@ -80,15 +83,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
title, title,
xAxisType = "number", xAxisType = "number",
distributionChartActions, distributionChartActions,
enableLocalSettings = false, } = props;
}) => {
const { result, bindings } = useSquiggle({
code,
environment,
jsImports,
onChange,
executionId,
});
const distributionPlotSettings = { const distributionPlotSettings = {
showSummary, showSummary,
@ -109,18 +104,56 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
count: diagramCount, count: diagramCount,
}; };
const resultToRender = resultMap(result, (value) => return { distributionPlotSettings, chartSettings };
value.tag === SqValueTag.Void ? bindings.asValue() : value };
);
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 ( return (
<SquiggleViewer <SquiggleViewer
result={resultToRender} result={valueToRender}
width={width} width={width}
height={height} height={height}
distributionPlotSettings={distributionPlotSettings} distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings} chartSettings={chartSettings}
environment={environment ?? defaultEnvironment} environment={p.getEnvironment()}
enableLocalSettings={enableLocalSettings} enableLocalSettings={enableLocalSettings}
/> />
); );

View File

@ -1,13 +1,21 @@
import React from "react"; import React from "react";
import { CodeEditor } from "./CodeEditor"; import { CodeEditor } from "./CodeEditor";
import { SquiggleContainer } from "./SquiggleContainer"; import { SquiggleContainer } from "./SquiggleContainer";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; import {
import { useMaybeControlledValue } from "../lib/hooks"; 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<{ const WrappedCodeEditor: React.FC<{
code: string; code: string;
setCode: (code: string) => void; setCode: (code: string) => void;
}> = ({ code, setCode }) => ( errorLocations?: SqLocation[];
}> = ({ code, setCode, errorLocations }) => (
<div className="border border-grey-200 p-2 m-4"> <div className="border border-grey-200 p-2 m-4">
<CodeEditor <CodeEditor
value={code} value={code}
@ -15,6 +23,7 @@ const WrappedCodeEditor: React.FC<{
oneLine={true} oneLine={true}
showGutter={false} showGutter={false}
height={20} height={20}
errorLocations={errorLocations}
/> />
</div> </div>
); );
@ -24,6 +33,9 @@ export type SquiggleEditorProps = SquiggleChartProps & {
onCodeChange?: (code: string) => void; onCodeChange?: (code: string) => void;
}; };
const defaultOnChange = () => {};
const defaultImports: JsImports = {};
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => { export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
const [code, setCode] = useMaybeControlledValue({ const [code, setCode] = useMaybeControlledValue({
value: props.code, value: props.code,
@ -31,11 +43,54 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
onChange: props.onCodeChange, 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 ( return (
<SquiggleContainer> <SquiggleContainer>
<WrappedCodeEditor code={code} setCode={setCode} /> <WrappedCodeEditor
<SquiggleChart {...chartProps} /> code={code}
setCode={setCode}
errorLocations={errorLocations}
/>
<SquiggleViewer
result={valueToRender}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings}
/>
</SquiggleContainer> </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 React from "react";
import { ErrorAlert } from "./Alert"; import { ErrorAlert } from "./Alert";
@ -6,6 +6,39 @@ type Props = {
error: SqError; error: SqError;
}; };
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => { const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => {
return <ErrorAlert heading="Error">{error.toString()}</ErrorAlert>; 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"; } from "react";
import { useForm, UseFormRegister, useWatch } from "react-hook-form"; import { useForm, UseFormRegister, useWatch } from "react-hook-form";
import * as yup from "yup"; 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 { yupResolver } from "@hookform/resolvers/yup";
import { import {
ChartSquareBarIcon, ChartSquareBarIcon,
@ -24,9 +28,9 @@ import {
} from "@heroicons/react/solid"; } from "@heroicons/react/solid";
import clsx from "clsx"; 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 { CodeEditor } from "./CodeEditor";
import { JsonEditor } from "./JsonEditor"; import { JsonEditor } from "./JsonEditor";
import { ErrorAlert, SuccessAlert } from "./Alert"; import { ErrorAlert, SuccessAlert } from "./Alert";
@ -40,6 +44,8 @@ import { HeadedSection } from "./ui/HeadedSection";
import { defaultTickFormat } from "../lib/distributionSpecBuilder"; import { defaultTickFormat } from "../lib/distributionSpecBuilder";
import { Button } from "./ui/Button"; import { Button } from "./ui/Button";
import { JsImports } from "../lib/jsImports"; import { JsImports } from "../lib/jsImports";
import { getErrorLocations, getValueToRender } from "../lib/utility";
import { SquiggleViewer } from "./SquiggleViewer";
type PlaygroundProps = SquiggleChartProps & { type PlaygroundProps = SquiggleChartProps & {
/** The initial squiggle string to put in the playground */ /** The initial squiggle string to put in the playground */
@ -282,7 +288,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onSettingsChange?.(vars); onSettingsChange?.(vars);
}, [vars, onSettingsChange]); }, [vars, onSettingsChange]);
const env: environment = useMemo( const environment: environment = useMemo(
() => ({ () => ({
sampleCount: Number(vars.sampleCount), sampleCount: Number(vars.sampleCount),
xyPointLength: Number(vars.xyPointLength), xyPointLength: Number(vars.xyPointLength),
@ -299,26 +305,59 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
executionId, executionId,
} = useRunnerState(code); } = 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 = const squiggleChart =
renderedCode === "" ? null : ( renderedCode === "" ? null : (
<div className="relative"> <div className="relative">
{isRunning ? ( {isRunning ? (
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" /> <div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
) : null} ) : null}
<SquiggleChart <SquiggleViewer
code={renderedCode} result={valueToRender}
executionId={executionId} environment={environment}
environment={env} height={vars.chartHeight || 150}
{...vars} distributionPlotSettings={{
jsImports={imports} 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} enableLocalSettings={true}
/> />
</div> </div>
); );
const errorLocations = getErrorLocations(resultAndBindings.result);
const firstTab = vars.showEditor ? ( const firstTab = vars.showEditor ? (
<div className="border border-slate-200"> <div className="border border-slate-200">
<CodeEditor <CodeEditor
errorLocations={errorLocations}
value={code} value={code}
onChange={setCode} onChange={setCode}
onSubmit={run} onSubmit={run}

View File

@ -2,7 +2,7 @@ import React, { useContext } from "react";
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang"; import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
import { NumberShower } from "../NumberShower"; import { NumberShower } from "../NumberShower";
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; import { FunctionChart } from "../FunctionChart";
import clsx from "clsx"; import clsx from "clsx";
import { VariableBox } from "./VariableBox"; import { VariableBox } from "./VariableBox";
import { ItemSettingsMenu } from "./ItemSettingsMenu"; import { ItemSettingsMenu } from "./ItemSettingsMenu";

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 React, { useContext, useReducer } from "react";
import { Tooltip } from "../ui/Tooltip"; import { Tooltip } from "../ui/Tooltip";
import { LocalItemSettings, MergedItemSettings } from "./utils"; import { LocalItemSettings, MergedItemSettings } from "./utils";
@ -70,7 +70,7 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
<div className="flex w-full"> <div className="flex w-full">
{location.path.items.length ? ( {location.path.items.length ? (
<div <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} onClick={toggleCollapsed}
></div> ></div>
) : null} ) : null}

View File

@ -1,3 +1,4 @@
export { SqProject } from "@quri/squiggle-lang/";
export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleChart } from "./components/SquiggleChart";
export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquiggleEditor } from "./components/SquiggleEditor";
export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquigglePlayground } from "./components/SquigglePlayground";

View File

@ -1,42 +1,85 @@
import { environment, SqProject, SqValue } from "@quri/squiggle-lang"; import {
result,
SqError,
SqProject,
SqRecord,
SqValue,
} from "@quri/squiggle-lang";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
import * as uuid from "uuid";
type SquiggleArgs = { type SquiggleArgs = {
code: string; code: string;
executionId?: number; executionId?: number;
jsImports?: JsImports; jsImports?: JsImports;
environment?: environment; project: SqProject;
onChange?: (expr: SqValue | undefined) => void; continues?: string[];
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
}; };
export const useSquiggle = (args: SquiggleArgs) => { export type ResultAndBindings = {
result: result<SqValue, SqError>;
bindings: SqRecord;
};
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 result = useMemo(
() => { () => {
const project = SqProject.create(); const project = args.project;
project.setSource("main", args.code);
if (args.environment) { project.setSource(sourceName, args.code);
project.setEnvironment(args.environment); let fullContinues = continues;
}
if (args.jsImports && Object.keys(args.jsImports).length) { if (args.jsImports && Object.keys(args.jsImports).length) {
const importsSource = jsImportsToSquiggleCode(args.jsImports); const importsSource = jsImportsToSquiggleCode(args.jsImports);
project.setSource("imports", importsSource); project.setSource(importSourceName(sourceName), importsSource);
project.setContinues("main", ["imports"]); fullContinues = continues.concat(importSourceName(sourceName));
} }
project.run("main"); project.setContinues(sourceName, fullContinues);
const result = project.getResult("main"); project.run(sourceName);
const bindings = project.getBindings("main"); const result = project.getResult(sourceName);
const bindings = project.getBindings(sourceName);
return { result, bindings }; return { result, bindings };
}, },
// This complains about executionId not being used inside the function body.
// This is on purpose, as executionId simply allows you to run the squiggle
// code again
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[args.code, args.environment, args.jsImports, args.executionId] [
args.code,
args.jsImports,
args.executionId,
sourceName,
continues,
args.project,
env,
]
); );
const { onChange } = args; const { onChange } = args;
useEffect(() => { useEffect(() => {
onChange?.(result.result.tag === "Ok" ? result.result.value : undefined); onChange?.(
}, [result, onChange]); result.result.tag === "Ok" ? result.result.value : undefined,
sourceName
);
}, [result, onChange, sourceName]);
useEffect(() => {
return () => {
args.project.removeSource(sourceName);
if (args.project.getSource(importSourceName(sourceName)))
args.project.removeSource(importSourceName(sourceName));
};
}, [args.project, sourceName]);
return result; return result;
}; };

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> { export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
if (x.length === 0) { if (x.length === 0) {
@ -35,3 +36,18 @@ export function all(arr: boolean[]): boolean {
export function some(arr: boolean[]): boolean { export function some(arr: boolean[]): boolean {
return arr.reduce((x, y) => x || y, false); 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 { .ace_cursor {
border-left: 2px solid !important; border-left: 2px solid !important;
} }
.ace-error-marker {
position: absolute;
border-bottom: 1px solid red;
}

View File

@ -3,11 +3,11 @@ import React from "react";
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { SquiggleChart } from "../src/index"; import { SquiggleChart } from "../src/index";
test("Logs no warnings or errors", async () => { test("Logs nothing on render", async () => {
debugger;
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />); const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
unmount(); unmount();
expect(console.log).not.toBeCalled();
expect(console.warn).not.toBeCalled(); expect(console.warn).not.toBeCalled();
expect(console.error).not.toBeCalled(); expect(console.error).not.toBeCalled();
}); });

View File

@ -0,0 +1,39 @@
import { render } from "@testing-library/react";
import React from "react";
import "@testing-library/jest-dom";
import { SquiggleChart } from "../src/index";
import { SqProject } from "@quri/squiggle-lang";
test("Creates and cleans up source", async () => {
const project = SqProject.create();
const { unmount } = render(
<SquiggleChart code={"normal(0, 1)"} project={project} />
);
expect(project.getSourceIds().length).toBe(1);
const sourceId = project.getSourceIds()[0];
expect(project.getSource(sourceId)).toBe("normal(0, 1)");
unmount();
expect(project.getSourceIds().length).toBe(0);
expect(project.getSource(sourceId)).toBe(undefined);
});
test("Creates and cleans up source and imports", async () => {
const project = SqProject.create();
const { unmount } = render(
<SquiggleChart
code={"normal($x, 1)"}
project={project}
jsImports={{ x: 3 }}
/>
);
expect(project.getSourceIds().length).toBe(2);
unmount();
expect(project.getSourceIds()).toStrictEqual([]);
});

View File

@ -0,0 +1,4 @@
{
"buildCommand": "cd ../.. && npx turbo run build --filter=@quri/squiggle-components",
"outputDirectory": "storybook-static"
}

View File

@ -6,3 +6,5 @@ lib
_coverage/ _coverage/
.cache/ .cache/
Reducer_Peggy_GeneratedParser.js Reducer_Peggy_GeneratedParser.js
ReducerProject_IncludeParser.js
src/rescript/Reducer/Reducer_Peggy/helpers.js

View File

@ -3,7 +3,7 @@ This is the most basic file in our invariants family of tests.
Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication. Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication.
Details in https://develop--squiggle-documentation.netlify.app/docs/internal/invariants/ Details in https://squiggle-language.com/docs/internal/invariants/
Note: epsilon of 1e3 means the invariants are, in general, not being satisfied. Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.
*/ */

View File

@ -182,7 +182,7 @@ describe("Peggy parse", () => {
"a.p1 to a.p2", "a.p1 to a.p2",
"{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}", "{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}",
) // lower than post ) // lower than post
testParse("1 to 2 + 3", "{(:add (:credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators testParse("1 to 2 + 3", "{(:credibleIntervalToDistribution 1 (:add 2 3))}")
testParse( testParse(
"1->add(2) to 3->add(4) -> add(4)", "1->add(2) to 3->add(4) -> add(4)",
"{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}", "{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}",
@ -197,7 +197,7 @@ describe("Peggy parse", () => {
describe("lambda", () => { describe("lambda", () => {
testParse("{|x| x}", "{{|:x| :x}}") 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", "{: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 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 open Expect
let expectParseToBe = (expr, answer) => 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 testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { let expectExpressionToBe = (expr, answer, ~v="_", ()) => {
let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode) let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode)
let a1 = rExpr->ExpressionT.toStringResultOkless let a1 = rExpr->ExpressionT.toStringResultOkless
if v == "_" { if v == "_" {
@ -22,6 +22,7 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
} else { } else {
let a2 = let a2 =
rExpr rExpr
->E.R2.errMap(e => e->SqError.fromParseError)
->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr))
->Reducer_Value.toStringResultOkless ->Reducer_Value.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v)) (a1, a2)->expect->toEqual((answer, v))
@ -29,16 +30,16 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
} }
let testToExpression = (expr, answer, ~v="_", ()) => let testToExpression = (expr, answer, ~v="_", ()) =>
test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
module MyOnly = { module MyOnly = {
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
let testToExpression = (expr, answer, ~v="_", ()) => let testToExpression = (expr, answer, ~v="_", ()) =>
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) Only.test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
} }
module MySkip = { module MySkip = {
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
let testToExpression = (expr, answer, ~v="_", ()) => 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", () => { describe("lambda", () => {
testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ()) 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", "f = {|x| {x}}", ()) // Function definitions are lambda assignments
testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ()) 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 Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
@ -9,7 +8,7 @@ let unwrapRecord = rValue =>
rValue->Belt.Result.flatMap(value => rValue->Belt.Result.flatMap(value =>
switch value { switch value {
| Reducer_T.IEvRecord(aRecord) => Ok(aRecord) | 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( testDescriptionEvalToBe(
"javascript exception", "javascript exception",
"javascriptraise('div by 0')", "javascriptraise('div by 0')",
"Error(Error: 'div by 0')", "Error(JS Exception: Error: 'div by 0')",
) )
// testDescriptionEvalToBe( // testDescriptionEvalToBe(
// "rescript exception", // "rescript exception",
@ -78,3 +78,33 @@ describe("test exceptions", () => {
// "Error(TODO: unhandled rescript exception)", // "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") let mainIncludes = Project.getIncludes(project, "main")
switch mainIncludes { switch mainIncludes {
| Ok(includes) => expect(includes) == ["common"] | Ok(includes) => expect(includes) == ["common"]
| Error(error) => fail(error->Reducer_ErrorValue.errorToString) | Error(error) => fail(error->SqError.toString)
} }
}) })
test("past chain", () => { test("past chain", () => {
@ -60,7 +60,7 @@ x=1`,
let mainIncludes = Project.getIncludes(project, "main") let mainIncludes = Project.getIncludes(project, "main")
switch mainIncludes { switch mainIncludes {
| Ok(includes) => expect(includes) == ["common", "myModule"] | 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") let mainIncludes = Project.getIncludes(project, "main")
switch mainIncludes { switch mainIncludes {
| Ok(includes) => expect(includes) == ["common", "common2", "myModule"] | Ok(includes) => expect(includes) == ["common", "common2", "myModule"]
| Error(error) => fail(error->Reducer_ErrorValue.errorToString) | Error(error) => fail(error->SqError.toString)
} }
}) })
test("direct past chain", () => { 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 */ /* Parse includes has set the includes */
switch project->Project.getIncludes("main") { switch project->Project.getIncludes("main") {
| Ok(includes) => includes->expect == ["common"] | 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. /* If the includes cannot be parsed then you get a syntax error.
Otherwise you get the includes. 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) let rIncludes = project->Project.getIncludes(sourceName)
switch rIncludes { switch rIncludes {
/* Maybe there is an include syntax error */ /* 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) => | Ok(includes) =>
includes->Belt.Array.forEach(newIncludeName => { includes->Belt.Array.forEach(newIncludeName => {
@ -169,7 +169,7 @@ Here we will finally proceed to a real life scenario. */
test("getIncludes", () => { test("getIncludes", () => {
switch Project.getIncludes(project, "main") { switch Project.getIncludes(project, "main") {
| Ok(includes) => includes->expect == ["common"] | 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

@ -10,6 +10,8 @@ let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry))
describe("FunctionRegistry Library", () => { describe("FunctionRegistry Library", () => {
describe("Regular tests", () => { describe("Regular tests", () => {
testEvalToBe("List.length([3,5,8])", "Ok(3)")
testEvalToBe("List.length([])", "Ok(0)")
testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])") testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])")
testEvalToBe("make(3, 'HI')", "Error(make is not defined)") testEvalToBe("make(3, 'HI')", "Error(make is not defined)")
testEvalToBe("List.upTo(1,3)", "Ok([1,2,3])") testEvalToBe("List.upTo(1,3)", "Ok([1,2,3])")
@ -80,6 +82,10 @@ describe("FunctionRegistry Library", () => {
"SampleSet.toList(SampleSet.mapN([SampleSet.fromList([1,2,3,4,5,6]), SampleSet.fromList([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))", "SampleSet.toList(SampleSet.mapN([SampleSet.fromList([1,2,3,4,5,6]), SampleSet.fromList([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))",
"Ok([6,5,4,4,5,6])", "Ok([6,5,4,4,5,6])",
) )
testEvalToBe(
"SampleSet.fromList([1, 2, 3])",
"Error(Error: Too few samples when constructing sample set)",
)
testEvalToBe("Dict.merge({a: 1, b: 2}, {b: 3, c: 4, d: 5})", "Ok({a: 1,b: 3,c: 4,d: 5})") testEvalToBe("Dict.merge({a: 1, b: 2}, {b: 3, c: 4, d: 5})", "Ok({a: 1,b: 3,c: 4,d: 5})")
testEvalToBe( testEvalToBe(

View File

@ -22,10 +22,12 @@
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*", "test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
"test:watch": "jest --watchAll", "test:watch": "jest --watchAll",
"test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js", "test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js",
"coverage:rescript": "rm -f *.coverage && yarn clean && BISECT_ENABLE=yes yarn build && yarn test:rescript && bisect-ppx-report html", "coverage:rescript:local": "rm -f *.coverage && yarn clean && BISECT_ENABLE=yes yarn build && yarn test:rescript && bisect-ppx-report html",
"coverage:ts": "yarn clean && yarn build && nyc --reporter=lcov yarn test:ts", "coverage:ts:local": "yarn clean && yarn build && nyc --reporter=lcov yarn test:ts",
"coverage:rescript:ci": "yarn clean && BISECT_ENABLE=yes yarn build:rescript && yarn test:rescript && bisect-ppx-report send-to Codecov", "coverage:rescript": "yarn clean && BISECT_ENABLE=yes yarn build:rescript && yarn test:rescript && bisect-ppx-report send-to Codecov",
"coverage:ts:ci": "yarn coverage:ts && codecov", "coverage:ts": "yarn coverage:ts:local && codecov",
"coverage": "yarn coverage:ts && yarn coverage:rescript",
"coverage:local": "yarn coverage:ts:local && yarn coverage:rescript:local",
"lint:rescript": "./lint.sh", "lint:rescript": "./lint.sh",
"lint:prettier": "prettier --check .", "lint:prettier": "prettier --check .",
"lint": "yarn lint:rescript && yarn lint:prettier", "lint": "yarn lint:rescript && yarn lint:prettier",

View File

@ -36,7 +36,7 @@ export const run = (src, { output, sampleCount } = {}) => {
"Time:", "Time:",
String(time), String(time),
result.tag === "Error" ? red(result.tag) : green(result.tag), 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 #!/usr/bin/env node
import { run } from "./lib.mjs"; 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) { if (!src) {
throw new Error("Expected src"); throw new Error("Expected src");
} }
console.log(`Running ${src}`);
const sampleCount = process.env.SAMPLE_COUNT; 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 { export class SqError {
constructor(private _value: RSErrorValue.reducerErrorValue) {} constructor(private _value: RSError.t) {}
toString() { toString() {
return RSErrorValue.toString(this._value); return RSError.toString(this._value);
} }
static createTodoError(v: string) { toStringWithStackTrace() {
return new SqError(RSErrorValue.createTodoError(v)); return RSError.toStringWithStackTrace(this._value);
} }
static createOtherError(v: string) { 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 * 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 { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen";
import { SqError } from "./SqError"; import { SqError } from "./SqError";
import { SqRecord } from "./SqRecord"; import { SqRecord } from "./SqRecord";
@ -54,7 +54,7 @@ export class SqProject {
return resultMap2( return resultMap2(
RSProject.getIncludes(this._value, sourceId), RSProject.getIncludes(this._value, sourceId),
(a) => a, (a) => a,
(v: reducerErrorValue) => new SqError(v) (v: RSError.t) => new SqError(v)
); );
} }
@ -108,7 +108,7 @@ export class SqProject {
items: [], 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"; import { SqProject } from "./SqProject";
type PathItem = string | number; type PathItem = string | number;

View File

@ -13,7 +13,7 @@ export {
environment, environment,
defaultEnvironment, defaultEnvironment,
} from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen"; } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
export { SqError } from "./SqError"; export { SqError, SqFrame, SqLocation } from "./SqError";
export { SqShape } from "./SqPointSetDist"; export { SqShape } from "./SqPointSetDist";
export { resultMap } from "./types"; export { resultMap } from "./types";

View File

@ -4,13 +4,6 @@ module Error = {
type sampleSetError = type sampleSetError =
TooFewSamples | NonNumericInput(string) | OperationError(Operation.operationError) TooFewSamples | NonNumericInput(string) | OperationError(Operation.operationError)
let sampleSetErrorToString = (err: sampleSetError): string =>
switch err {
| TooFewSamples => "Too few samples when constructing sample set"
| NonNumericInput(err) => `Found a non-number in input: ${err}`
| OperationError(err) => Operation.Error.toString(err)
}
@genType @genType
type pointsetConversionError = TooFewSamplesForConversionToPointSet type pointsetConversionError = TooFewSamplesForConversionToPointSet

View File

@ -5,7 +5,7 @@ let nameSpace = "" // no namespaced versions
type simpleDefinition = { type simpleDefinition = {
inputs: array<frType>, 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>) => let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
@ -22,7 +22,7 @@ let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
let makeFn = ( let makeFn = (
name: string, name: string,
inputs: array<frType>, 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}]) ) => makeFnMany(name, [{inputs: inputs, fn: fn}])
let library = [ let library = [

View File

@ -67,7 +67,7 @@ module Integration = {
let applyFunctionAtFloatToFloatOption = (point: float) => { let applyFunctionAtFloatToFloatOption = (point: float) => {
// Defined here so that it has access to environment, reducer // Defined here so that it has access to environment, reducer
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point) let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall( let resultAsInternalExpression = Reducer_Lambda.doLambdaCall(
aLambda, aLambda,
[pointAsInternalExpression], [pointAsInternalExpression],
environment, environment,
@ -77,7 +77,7 @@ module Integration = {
| Reducer_T.IEvNumber(x) => Ok(x) | Reducer_T.IEvNumber(x) => Ok(x)
| _ => | _ =>
Error( 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 result
@ -143,8 +143,8 @@ module Integration = {
| Error(b) => | 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." ++ ("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: " ++ "Original error: " ++
b->Reducer_ErrorValue.errorToString) b->SqError.Message.toString)
->Reducer_ErrorValue.REOther ->SqError.Message.REOther
->Error ->Error
} }
result result
@ -169,7 +169,7 @@ module Integration = {
let result = switch inputs { let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] => | [_, _, _, IEvNumber(0.0)] =>
"Integration error 4 in Danger.integrate: Increment can't be 0." "Integration error 4 in Danger.integrate: Increment can't be 0."
->Reducer_ErrorValue.REOther ->SqError.Message.REOther
->Error ->Error
| [ | [
IEvLambda(aLambda), IEvLambda(aLambda),
@ -187,7 +187,7 @@ module Integration = {
) )
| _ => | _ =>
Error( Error(
Reducer_ErrorValue.REOther( SqError.Message.REOther(
"Integration error 5 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))", "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 { let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] => | [_, _, _, IEvNumber(0.0)] =>
"Integration error in Danger.integrate: Increment can't be 0." "Integration error in Danger.integrate: Increment can't be 0."
->Reducer_ErrorValue.REOther ->SqError.Message.REOther
->Error ->Error
| [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] => | [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] =>
Helpers.integrateFunctionBetweenWithNumIntegrationPoints( Helpers.integrateFunctionBetweenWithNumIntegrationPoints(
@ -225,11 +225,11 @@ module Integration = {
reducer, reducer,
)->E.R2.errMap(b => )->E.R2.errMap(b =>
("Integration error 7 in Danger.integrate. Something went wrong along the way: " ++ ("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))" "Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))"
->Reducer_ErrorValue.REOther ->SqError.Message.REOther
->Error ->Error
} }
result result
@ -246,7 +246,7 @@ module DiminishingReturns = {
module Helpers = { module Helpers = {
type diminishingReturnsAccumulatorInner = { type diminishingReturnsAccumulatorInner = {
optimalAllocations: array<float>, optimalAllocations: array<float>,
currentMarginalReturns: result<array<float>, errorValue>, currentMarginalReturns: result<array<float>, errorMessage>,
} }
let findBiggestElementIndex = (xs: array<float>) => let findBiggestElementIndex = (xs: array<float>) =>
E.A.reducei(xs, 0, (acc, newElement, index) => { E.A.reducei(xs, 0, (acc, newElement, index) => {
@ -255,7 +255,7 @@ module DiminishingReturns = {
| false => acc | 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. // 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 The key idea for this function is that
@ -290,25 +290,25 @@ module DiminishingReturns = {
) { ) {
| (false, _, _, _) => | (false, _, _, _) =>
Error( 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, _, _) => | (_, false, _, _) =>
Error( 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, _) => | (_, _, false, _) =>
Error( 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) => | (_, _, _, false) =>
Error( 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) => { | (true, true, true, true) => {
let applyFunctionAtPoint = (lambda, point: float) => { let applyFunctionAtPoint = (lambda, point: float) => {
// Defined here so that it has access to environment, reducer // Defined here so that it has access to environment, reducer
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point) let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall( let resultAsInternalExpression = Reducer_Lambda.doLambdaCall(
lambda, lambda,
[pointAsInternalExpression], [pointAsInternalExpression],
environment, environment,
@ -318,7 +318,7 @@ module DiminishingReturns = {
| Reducer_T.IEvNumber(x) => Ok(x) | Reducer_T.IEvNumber(x) => Ok(x)
| _ => | _ =>
Error( 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) | Reducer_T.IEvLambda(lambda) => Ok(lambda)
| _ => | _ =>
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. A member of the array wasn't a function" "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. A member of the array wasn't a function"
->Reducer_ErrorValue.REOther ->SqError.Message.REOther
->Error ->Error
} }
}, innerlambdas) }, innerlambdas)
@ -433,7 +433,7 @@ module DiminishingReturns = {
} }
| _ => | _ =>
"Error in Danger.diminishingMarginalReturnsForTwoFunctions" "Error in Danger.diminishingMarginalReturnsForTwoFunctions"
->Reducer_ErrorValue.REOther ->SqError.Message.REOther
->Error ->Error
}, },
(), (),

View File

@ -4,7 +4,7 @@ open FunctionRegistry_Helpers
let makeFn = ( let makeFn = (
name: string, name: string,
inputs: array<frType>, 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( Function.make(
~name, ~name,
@ -66,7 +66,7 @@ let library = [
| [IEvNumber(year)] => | [IEvNumber(year)] =>
switch DateTime.Date.makeFromYear(year) { switch DateTime.Date.makeFromYear(year) {
| Ok(t) => IEvDate(t)->Ok | Ok(t) => IEvDate(t)->Ok
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error | Error(e) => SqError.Message.RETodo(e)->Error
} }
| _ => Error(impossibleError) | _ => Error(impossibleError)
} }

View File

@ -17,7 +17,7 @@ module Internals = {
->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value])) ->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value]))
->Wrappers.evArray ->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 items
->E.A2.fmap(item => { ->E.A2.fmap(item => {
switch (item: Reducer_T.value) { switch (item: Reducer_T.value) {
@ -76,7 +76,7 @@ let library = [
->Belt.Array.map(dictValue => ->Belt.Array.map(dictValue =>
switch dictValue { switch dictValue {
| IEvRecord(dict) => dict | IEvRecord(dict) => dict
| _ => impossibleError->Reducer_ErrorValue.toException | _ => impossibleError->SqError.Message.throw
} }
) )
->Internals.mergeMany ->Internals.mergeMany

View File

@ -16,13 +16,14 @@ module DistributionCreation = {
r r
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env)) ->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
->E.R2.fmap(Wrappers.evDistribution) ->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) => { let make = (name, fn) => {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~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( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])], ~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
~run=(inputs, env, _) => ~run=(inputs, context, _) =>
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))->process(~fn, ~env), inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))
->process(~fn, ~env=context.environment),
(), (),
) )
} }
@ -41,10 +44,10 @@ module DistributionCreation = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])], ~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
~run=(inputs, env, _) => ~run=(inputs, context, _) =>
inputs inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev")) ->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev"))
->process(~fn, ~env), ->process(~fn, ~env=context.environment),
(), (),
) )
} }
@ -55,13 +58,14 @@ module DistributionCreation = {
r r
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env)) ->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
->E.R2.fmap(Wrappers.evDistribution) ->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) => let make = (name, fn) =>
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeDistOrNumber], ~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< let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
Reducer_T.value, Reducer_T.value,
Reducer_ErrorValue.errorValue, SqError.Message.t,
> => > =>
switch o { switch o {
| Dist(d) => Ok(Reducer_T.IEvDistribution(d)) | Dist(d) => Ok(Reducer_T.IEvDistribution(d))
@ -311,9 +311,9 @@ module Old = {
switch dispatchToGenericOutput(call, environment) { switch dispatchToGenericOutput(call, environment) {
| Some(o) => genericOutputToReducerValue(o) | Some(o) => genericOutputToReducerValue(o)
| None => | None =>
Reducer_ErrorValue.REOther("Internal error in FR_GenericDist implementation") SqError.Message.REOther(
->Reducer_ErrorValue.ErrorException "Internal error in FR_GenericDist implementation",
->raise )->SqError.Message.throw
} }
} }
@ -326,7 +326,7 @@ let makeProxyFn = (name: string, inputs: array<frType>) => {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs, ~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; // FIXME - impossible to implement with FR due to arbitrary parameters length;
let mxLambda = Reducer_Expression_Lambda.makeFFILambda((inputs, env, _) => { let mxLambda = Reducer_Lambda.makeFFILambda("mx", (inputs, context, _) => {
switch Old.dispatch(("mx", inputs), env) { switch Old.dispatch(("mx", inputs), context.environment) {
| Ok(value) => value | Ok(value) => value
| Error(e) => e->Reducer_ErrorValue.ErrorException->raise | Error(e) => e->SqError.Message.throw
} }
}) })

View File

@ -5,6 +5,10 @@ let nameSpace = "List"
let requiresNamespace = true let requiresNamespace = true
module Internals = { module Internals = {
let length = (v: array<Reducer_T.value>): Reducer_T.value => IEvNumber(
Belt.Int.toFloat(Array.length(v)),
)
let makeFromNumber = (n: float, value: Reducer_T.value): Reducer_T.value => IEvArray( let makeFromNumber = (n: float, value: Reducer_T.value): Reducer_T.value => IEvArray(
Belt.Array.make(E.Float.toInt(n), value), Belt.Array.make(E.Float.toInt(n), value),
) )
@ -26,11 +30,11 @@ module Internals = {
let map = ( let map = (
array: array<Reducer_T.value>, array: array<Reducer_T.value>,
eLambdaValue, eLambdaValue,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
): Reducer_T.value => { ): Reducer_T.value => {
Belt.Array.map(array, elem => Belt.Array.map(array, elem =>
Reducer_Expression_Lambda.doLambdaCall(eLambdaValue, [elem], env, reducer) Reducer_Lambda.doLambdaCall(eLambdaValue, [elem], context, reducer)
)->Wrappers.evArray )->Wrappers.evArray
} }
@ -38,11 +42,11 @@ module Internals = {
aValueArray, aValueArray,
initialValue, initialValue,
aLambdaValue, aLambdaValue,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
) => { ) => {
aValueArray->E.A.reduce(initialValue, (acc, elem) => aValueArray->E.A.reduce(initialValue, (acc, elem) =>
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer) Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer)
) )
} }
@ -50,22 +54,22 @@ module Internals = {
aValueArray, aValueArray,
initialValue, initialValue,
aLambdaValue, aLambdaValue,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
) => { ) => {
aValueArray->Belt.Array.reduceReverse(initialValue, (acc, elem) => 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 = ( let filter = (
aValueArray, aValueArray,
aLambdaValue, aLambdaValue,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
) => { ) => {
Js.Array2.filter(aValueArray, elem => { 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 { switch result {
| IEvBool(true) => true | IEvBool(true) => true
| _ => false | _ => false
@ -75,6 +79,26 @@ module Internals = {
} }
let library = [ let library = [
Function.make(
~name="length",
~nameSpace,
~output=EvtNumber,
~requiresNamespace=true,
~examples=[`List.length([1,4,5])`],
~definitions=[
FnDefinition.make(
~name="length",
~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _) =>
switch inputs {
| [IEvArray(array)] => Internals.length(array)->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
),
Function.make( Function.make(
~name="make", ~name="make",
~nameSpace, ~nameSpace,

View File

@ -16,30 +16,30 @@ let inputsToDist = (inputs: array<Reducer_T.value>, xyShapeToPointSetDist) => {
let yValue = map->Belt.Map.String.get("y") let yValue = map->Belt.Map.String.get("y")
switch (xValue, yValue) { switch (xValue, yValue) {
| (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y) | (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 ->Ok
->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString)) ->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString))
->E.R2.fmap(r => Reducer_T.IEvDistribution(PointSet(r->xyShapeToPointSetDist))) ->E.R2.fmap(r => Reducer_T.IEvDistribution(PointSet(r->xyShapeToPointSetDist)))
| _ => impossibleError->Reducer_ErrorValue.toException | _ => impossibleError->SqError.Message.throw
} }
} }
module Internal = { module Internal = {
type t = PointSetDist.t 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 { switch r {
| Ok(r) => Ok(Wrappers.evDistribution(PointSet(r))) | Ok(r) => Ok(Wrappers.evDistribution(PointSet(r)))
| Error(err) => Error(REOperationError(err)) | Error(err) => Error(REOperationError(err))
} }
let doLambdaCall = (aLambdaValue, list, env, reducer) => 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) | Reducer_T.IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction) | _ => Error(Operation.SampleMapNeedsNtoNFunction)
} }
@ -61,18 +61,18 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="fromDist", ~name="fromDist",
~inputs=[FRTypeDist], ~inputs=[FRTypeDist],
~run=(inputs, env, _) => ~run=(inputs, context, _) =>
switch inputs { switch inputs {
| [IEvDistribution(dist)] => | [IEvDistribution(dist)] =>
GenericDist.toPointSet( GenericDist.toPointSet(
dist, dist,
~xyPointLength=env.xyPointLength, ~xyPointLength=context.environment.xyPointLength,
~sampleCount=env.sampleCount, ~sampleCount=context.environment.sampleCount,
(), (),
) )
->E.R2.fmap(Wrappers.pointSet) ->E.R2.fmap(Wrappers.pointSet)
->E.R2.fmap(Wrappers.evDistribution) ->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) ->E.R2.errMap(e => SqError.Message.REDistributionError(e))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),

View File

@ -10,41 +10,40 @@ module Internal = {
let doLambdaCall = ( let doLambdaCall = (
aLambdaValue, aLambdaValue,
list, list,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, 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) | IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction) | _ => 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 { switch r {
| Ok(r) => Ok(Wrappers.evDistribution(SampleSet(r))) | Ok(r) => Ok(Wrappers.evDistribution(SampleSet(r)))
| Error(r) => Error(REDistributionError(SampleSetError(r))) | Error(r) => Error(REDistributionError(SampleSetError(r)))
} }
//TODO: I don't know why this seems to need at least one input //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 fromFn = (aLambdaValue, context: Reducer_T.context, reducer: Reducer_T.reducerFn) => {
let sampleCount = environment.sampleCount let sampleCount = context.environment.sampleCount
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer) let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer)
Belt_Array.makeBy(sampleCount, r => fn(r->Js.Int.toFloat))->E.A.R.firstErrorOrOpen 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 map1 = (sampleSetDist: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer) let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer)
SampleSetDist.samplesMap(~fn, sampleSetDist)->toType SampleSetDist.samplesMap(~fn, sampleSetDist)->toType
} }
let map2 = (t1: t, t2: t, aLambdaValue, environment: Reducer_T.environment, reducer) => { let map2 = (t1: t, t2: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = (a, b) => let fn = (a, b) => doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], context, reducer)
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], environment, reducer)
SampleSetDist.map2(~fn, ~t1, ~t2)->toType 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) => 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 SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
} }
@ -60,7 +59,7 @@ module Internal = {
let mapN = ( let mapN = (
aValueArray: array<Reducer_T.value>, aValueArray: array<Reducer_T.value>,
aLambdaValue, aLambdaValue,
environment: Reducer_T.environment, context: Reducer_T.context,
reducer, reducer,
) => { ) => {
switch parseSampleSetArray(aValueArray) { switch parseSampleSetArray(aValueArray) {
@ -69,7 +68,7 @@ module Internal = {
doLambdaCall( doLambdaCall(
aLambdaValue, aLambdaValue,
[IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))], [IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))],
environment, context,
reducer, reducer,
) )
SampleSetDist.mapN(~fn, ~t1)->toType SampleSetDist.mapN(~fn, ~t1)->toType
@ -89,13 +88,13 @@ let libaryBase = [
FnDefinition.make( FnDefinition.make(
~name="fromDist", ~name="fromDist",
~inputs=[FRTypeDist], ~inputs=[FRTypeDist],
~run=(inputs, environment, _) => ~run=(inputs, context, _) =>
switch inputs { switch inputs {
| [IEvDistribution(dist)] => | [IEvDistribution(dist)] =>
GenericDist.toSampleSetDist(dist, environment.sampleCount) GenericDist.toSampleSetDist(dist, context.environment.sampleCount)
->E.R2.fmap(Wrappers.sampleSet) ->E.R2.fmap(Wrappers.sampleSet)
->E.R2.fmap(Wrappers.evDistribution) ->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e)) ->E.R2.errMap(e => SqError.Message.REDistributionError(e))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -116,7 +115,7 @@ let libaryBase = [
~run=(inputs, _, _) => { ~run=(inputs, _, _) => {
let sampleSet = let sampleSet =
inputs->Prepare.ToTypedArray.numbers inputs->Prepare.ToTypedArray.numbers
|> E.R2.bind(r => SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??")) |> E.R2.bind(r => SampleSetDist.make(r)->E.R2.errMap(SampleSetDist.Error.toString))
sampleSet sampleSet
->E.R2.fmap(Wrappers.sampleSet) ->E.R2.fmap(Wrappers.sampleSet)
->E.R2.fmap(Wrappers.evDistribution) ->E.R2.fmap(Wrappers.evDistribution)
@ -163,7 +162,7 @@ let libaryBase = [
| [IEvLambda(lambda)] => | [IEvLambda(lambda)] =>
switch Internal.fromFn(lambda, environment, reducer) { switch Internal.fromFn(lambda, environment, reducer) {
| Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution) | Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution)
| Error(e) => e->Reducer_ErrorValue.REOperationError->Error | Error(e) => e->SqError.Message.REOperationError->Error
} }
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
@ -290,7 +289,7 @@ module Comparison = {
r r
->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution) ->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution)
->E.R2.errMap(e => ->E.R2.errMap(e =>
e->DistributionTypes.Error.sampleErrorToDistErr->Reducer_ErrorValue.REDistributionError e->DistributionTypes.Error.sampleErrorToDistErr->SqError.Message.REDistributionError
) )
let mkBig = (name, withDist, withFloat) => let mkBig = (name, withDist, withFloat) =>

View File

@ -6,7 +6,7 @@ let requiresNamespace = true
let runScoring = (estimate, answer, prior, env) => { let runScoring = (estimate, answer, prior, env) => {
GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env) GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env)
->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber) ->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 = [ let library = [
@ -30,15 +30,15 @@ let library = [
("prior", FRTypeDist), ("prior", FRTypeDist),
]), ]),
], ],
~run=(inputs, environment, _) => { ~run=(inputs, context, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs( switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(
inputs, inputs,
("estimate", "answer", "prior"), ("estimate", "answer", "prior"),
) { ) {
| Ok([IEvDistribution(estimate), IEvDistribution(d), IEvDistribution(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)]) => | 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(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError) | _ => Error(FunctionRegistry_Helpers.impossibleError)
} }
@ -48,15 +48,15 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="logScore", ~name="logScore",
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])], ~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
~run=(inputs, environment, _) => { ~run=(inputs, context, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs( switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(
inputs, inputs,
("estimate", "answer"), ("estimate", "answer"),
) { ) {
| Ok([IEvDistribution(estimate), IEvDistribution(d)]) => | 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)]) => | 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(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError) | _ => Error(FunctionRegistry_Helpers.impossibleError)
} }
@ -76,10 +76,10 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="klDivergence", ~name="klDivergence",
~inputs=[FRTypeDist, FRTypeDist], ~inputs=[FRTypeDist, FRTypeDist],
~run=(inputs, environment, _) => { ~run=(inputs, context, _) => {
switch inputs { switch inputs {
| [IEvDistribution(estimate), IEvDistribution(d)] => | [IEvDistribution(estimate), IEvDistribution(d)] =>
runScoring(estimate, Score_Dist(d), None, environment) runScoring(estimate, Score_Dist(d), None, context.environment)
| _ => Error(FunctionRegistry_Helpers.impossibleError) | _ => Error(FunctionRegistry_Helpers.impossibleError)
} }
}, },

View File

@ -1,7 +1,8 @@
@genType type reducerProject = ReducerProject_T.project //re-export @genType type reducerProject = ReducerProject_T.project //re-export
@genType type reducerProjectJson = ReducerProject_T.projectJson //re-export @genType type reducerProjectJson = ReducerProject_T.projectJson //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 = ForTS_SquiggleValue.squiggleValue //use
type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use
@ -104,10 +105,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. To set the includes one first has to call "parseIncludes". The parsed includes or the parser error is returned.
*/ */
@genType @genType
let getIncludes = (project: reducerProject, sourceId: string): result< let getIncludes = (project: reducerProject, sourceId: string): result<array<string>, error> =>
array<string>, project->Private.getIncludes(sourceId)
reducerErrorValue,
> => project->Private.getIncludes(sourceId)
/* Other sources contributing to the global namespace of this source. */ /* Other sources contributing to the global namespace of this source. */
@genType @genType
@ -199,10 +198,8 @@ let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Rec
Get the result after running this source file or the project Get the result after running this source file or the project
*/ */
@genType @genType
let getResult = (project: reducerProject, sourceId: string): result< let getResult = (project: reducerProject, sourceId: string): result<squiggleValue, error> =>
squiggleValue, project->Private.getResult(sourceId)
reducerErrorValue,
> => project->Private.getResult(sourceId)
/* /*
This is a convenience function to get the result of a single source without creating a project. This is a convenience function to get the result of a single source without creating a project.
@ -210,10 +207,8 @@ However, without a project, you cannot handle include directives.
The source has to be include free The source has to be include free
*/ */
@genType @genType
let evaluate = (sourceCode: string): ( let evaluate = (sourceCode: string): (result<squiggleValue, error>, squiggleValue_Record) =>
result<squiggleValue, reducerErrorValue>, Private.evaluate(sourceCode)
squiggleValue_Record,
) => Private.evaluate(sourceCode)
@genType @genType
let setEnvironment = (project: reducerProject, environment: environment): unit => let setEnvironment = (project: reducerProject, environment: environment): unit =>

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 @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_Array = Reducer_T.arrayValue //re-export recursive type
@genType type squiggleValue_Record = Reducer_T.map //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. // This is a useful method for unit tests.
// Convert the result along with the error message to a string. // Convert the result along with the error message to a string.
@genType @genType
let toStringResult = (variantResult: result<squiggleValue, reducerErrorValue>) => let toStringResult = (variantResult: result<squiggleValue, error>) =>
Reducer_Value.toStringResult(variantResult) Reducer_Value.toStringResult(variantResult)
@genType @genType

View File

@ -1,9 +1,7 @@
@genType type squiggleValue_Lambda = Reducer_T.lambdaValue //re-export @genType type squiggleValue_Lambda = Reducer_T.lambdaValue //re-export
@genType @genType
let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringFunction(v) let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringLambda(v)
@genType @genType
let parameters = (v: squiggleValue_Lambda): array<string> => { let parameters = (v: squiggleValue_Lambda): array<string> => Reducer_Lambda.parameters(v)
v.parameters
}

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 reducerProject = ForTS_ReducerProject.reducerProject //re-export
@genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export @genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export
@genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //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 internalExpressionValueType = Reducer_Value.internalExpressionValueType
type errorValue = Reducer_ErrorValue.errorValue type errorMessage = SqError.Message.t
/* /*
Function Registry "Type". A type, without any other information. Function Registry "Type". A type, without any other information.
@ -30,9 +30,9 @@ type fnDefinition = {
inputs: array<frType>, inputs: array<frType>,
run: ( run: (
array<Reducer_T.value>, array<Reducer_T.value>,
Reducer_T.environment, Reducer_T.context,
Reducer_T.reducerFn, Reducer_T.reducerFn,
) => result<Reducer_T.value, errorValue>, ) => result<Reducer_T.value, errorMessage>,
} }
type function = { type function = {
@ -122,11 +122,11 @@ module FnDefinition = {
let run = ( let run = (
t: t, t: t,
args: array<Reducer_T.value>, args: array<Reducer_T.value>,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
) => { ) => {
switch t->isMatch(args) { switch t->isMatch(args) {
| true => t.run(args, env, reducer) | true => t.run(args, context, reducer)
| false => REOther("Incorrect Types")->Error | false => REOther("Incorrect Types")->Error
} }
} }
@ -164,7 +164,7 @@ module Function = {
nameSpace: nameSpace, nameSpace: nameSpace,
definitions: definitions, definitions: definitions,
output: output, output: output,
examples: examples |> E.O.default([]), examples: examples->E.O2.default([]),
isExperimental: isExperimental, isExperimental: isExperimental,
requiresNamespace: requiresNamespace, requiresNamespace: requiresNamespace,
description: description, description: description,
@ -225,9 +225,9 @@ module Registry = {
registry, registry,
fnName: string, fnName: string,
args: array<Reducer_T.value>, args: array<Reducer_T.value>,
env: Reducer_T.environment, context: Reducer_T.context,
reducer: Reducer_T.reducerFn, reducer: Reducer_T.reducerFn,
): result<Reducer_T.value, errorValue> => { ): result<Reducer_T.value, errorMessage> => {
switch Belt.Map.String.get(registry.fnNameDict, fnName) { switch Belt.Map.String.get(registry.fnNameDict, fnName) {
| Some(definitions) => { | Some(definitions) => {
let showNameMatchDefinitions = () => { let showNameMatchDefinitions = () => {
@ -241,7 +241,7 @@ module Registry = {
let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args)) let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args))
switch match { switch match {
| Some(def) => def->FnDefinition.run(args, env, reducer) | Some(def) => def->FnDefinition.run(args, context, reducer)
| None => REOther(showNameMatchDefinitions())->Error | None => REOther(showNameMatchDefinitions())->Error
} }
} }

View File

@ -2,8 +2,8 @@ open FunctionRegistry_Core
open Reducer_T open Reducer_T
let impossibleErrorString = "Wrong inputs / Logically impossible" let impossibleErrorString = "Wrong inputs / Logically impossible"
let impossibleError: errorValue = impossibleErrorString->Reducer_ErrorValue.REOther let impossibleError: errorMessage = impossibleErrorString->SqError.Message.REOther
let wrapError = e => Reducer_ErrorValue.REOther(e) let wrapError = e => SqError.Message.REOther(e)
module Wrappers = { module Wrappers = {
let symbolic = r => DistributionTypes.Symbolic(r) 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 => { let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environment): t => {
{ {
frameStack: list{},
bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend, bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend,
environment: environment, 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 Bindings = Reducer_Bindings
module Lambda = Reducer_Expression_Lambda
module Result = Belt.Result module Result = Belt.Result
module T = Reducer_T 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 Recursively evaluate the expression
*/ */
let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => { let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
// Js.log(`reduce: ${expression->Reducer_Expression_T.toString}`) // Js.log(`reduce: ${expression->Reducer_Expression_T.toString}`)
switch expression { switch expression.content {
| T.EBlock(statements) => { | T.EBlock(statements) => {
let innerContext = {...context, bindings: context.bindings->Bindings.extend} let innerContext = {...context, bindings: context.bindings->Bindings.extend}
let (value, _) = let (value, _) =
@ -49,7 +58,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
let (key, _) = eKey->evaluate(context) let (key, _) = eKey->evaluate(context)
let keyString = switch key { let keyString = switch key {
| IEvString(s) => s | 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) let (value, _) = eValue->evaluate(context)
(keyString, value) (keyString, value)
@ -73,7 +82,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
| T.ESymbol(name) => | T.ESymbol(name) =>
switch context.bindings->Bindings.get(name) { switch context.bindings->Bindings.get(name) {
| Some(v) => (v, context) | Some(v) => (v, context)
| None => Reducer_ErrorValue.RESymbolNotFound(name)->Reducer_ErrorValue.ErrorException->raise | None => RESymbolNotFound(name)->throwFrom(expression, context)
} }
| T.EValue(value) => (value, 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) let (predicateResult, _) = predicate->evaluate(context)
switch predicateResult { switch predicateResult {
| T.IEvBool(value) => (value ? trueCase : falseCase)->evaluate(context) | T.IEvBool(value) => (value ? trueCase : falseCase)->evaluate(context)
| _ => REExpectedType("Boolean", "")->Reducer_ErrorValue.ErrorException->raise | _ => REExpectedType("Boolean", "")->throwFrom(expression, context)
} }
} }
| T.ELambda(parameters, body) => ( | T.ELambda(parameters, body, name) => (
Lambda.makeLambda(parameters, context.bindings, body)->T.IEvLambda, Reducer_Lambda.makeLambda(
name,
parameters,
context.bindings,
body,
expression->toLocation,
)->T.IEvLambda,
context, context,
) )
| T.ECall(fn, args) => { | T.ECall(fn, args) => {
let (lambda, _) = fn->evaluate(context) let (lambda, _) = fn->evaluate(context)
let argValues = Js.Array2.map(args, arg => { let argValues = Belt.Array.map(args, arg => {
let (argValue, _) = arg->evaluate(context) let (argValue, _) = arg->evaluate(context)
argValue argValue
}) })
switch lambda { switch lambda {
| T.IEvLambda(lambda) => ( | T.IEvLambda(lambda) => {
Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate), let result = Reducer_Lambda.doLambdaCallFrom(
lambda,
argValues,
context, context,
evaluate,
Some(expression->toLocation), // we have to pass the location of a current expression here, to put it on frameStack
) )
| _ => (result, context)
RENotAFunction(lambda->Reducer_Value.toString)->Reducer_ErrorValue.ErrorException->raise }
| _ => 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 = { module BackCompatible = {
// Those methods are used to support the existing tests // Those methods are used to support the existing tests
// If they are used outside limited testing context, error location reporting will fail // If they are used outside limited testing context, error location reporting will fail
let parse = (peggyCode: string): result<T.expression, errorValue> => let parse = (peggyCode: string): result<T.expression, Reducer_Peggy_Parse.parseError> =>
peggyCode->Reducer_Peggy_Parse.parse->Result.map(Reducer_Peggy_ToExpression.fromNode) peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode)
let evaluate = (expression: T.expression): result<T.value, errorValue> => { let createDefaultContext = () =>
let context = Reducer_Context.createDefaultContext() Reducer_Context.createContext(SquiggleLibrary_StdLib.stdLib, Reducer_Context.defaultEnvironment)
let evaluate = (expression: T.expression): result<T.value, SqError.t> => {
let context = createDefaultContext()
try { try {
let (value, _) = expression->evaluate(context) let (value, _) = expression->evaluate(context)
value->Ok value->Ok
} catch { } catch {
| exn => Reducer_ErrorValue.fromException(exn)->Error | exn => exn->SqError.fromException->Error
} }
} }
let evaluateString = (peggyCode: string): result<T.value, errorValue> => let evaluateString = (peggyCode: string): result<T.value, SqError.t> =>
parse(peggyCode)->Result.flatMap(evaluate) 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 module T = Reducer_T
type errorValue = BErrorValue.errorValue
type expression = Reducer_T.expression 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 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 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 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, symbol,
valueExpression, valueExpression,
) )
@ -33,11 +36,8 @@ let eTernary = (
predicate: expression, predicate: expression,
trueCase: expression, trueCase: expression,
falseCase: 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 => let eVoid: expressionContent = T.IEvVoid->T.EValue
// name->T.IEvTypeIdentifier->T.EValue
let eVoid: expression = 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 Converts the expression to String
*/ */
let rec toString = (expression: t) => let rec toString = (expression: t) =>
switch expression { switch expression.content {
| EBlock(statements) => | EBlock(statements) =>
`{${Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin}}` `{${Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin}}`
| EProgram(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})` `${predicate->toString} ? (${trueCase->toString}) : (${falseCase->toString})`
| EAssign(name, value) => `${name} = ${value->toString}` | EAssign(name, value) => `${name} = ${value->toString}`
| ECall(fn, args) => `(${fn->toString})(${args->Js.Array2.map(toString)->commaJoin})` | 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) | EValue(aValue) => Reducer_Value.toString(aValue)
} }
let toStringResult = codeResult => let toStringResult = codeResult =>
switch codeResult { switch codeResult {
| Ok(a) => `Ok(${toString(a)})` | Ok(a) => `Ok(${toString(a)})`
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` | Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})`
} }
let toStringResultOkless = codeResult => let toStringResultOkless = codeResult =>
switch codeResult { switch codeResult {
| Ok(a) => toString(a) | Ok(a) => toString(a)
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})` | Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})`
} }
let inspect = (expr: t): t => { let inspect = (expr: t): t => {
Js.log(toString(expr)) Js.log(toString(expr))
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 start
= _nl start:outerBlock _nl finalComment? {return start} = _nl start:outerBlock _nl finalComment? {return start}
zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda zeroOMoreArgumentsBlockOrExpression = lambda / innerBlockOrExpression
outerBlock outerBlock
= statements:array_statements finalExpression: (statementSeparator @expression)? = statements:array_statements finalExpression: (statementSeparator @expression)?
{ if (finalExpression) statements.push(finalExpression) { if (finalExpression) statements.push(finalExpression)
return h.nodeProgram(statements) } return h.nodeProgram(statements, location()) }
/ finalExpression: expression / finalExpression: expression
{ return h.nodeProgram([finalExpression]) } { return h.nodeProgram([finalExpression], location()) }
innerBlockOrExpression innerBlockOrExpression
= quotedInnerBlock = quotedInnerBlock
/ finalExpression: expression / finalExpression: expression
{ return h.nodeBlock([finalExpression])} { return h.nodeBlock([finalExpression], location())}
quotedInnerBlock quotedInnerBlock
= '{' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' = '{' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}'
{ if (finalExpression) statements.push(finalExpression) { if (finalExpression) statements.push(finalExpression)
return h.nodeBlock(statements) } return h.nodeBlock(statements, location()) }
/ '{' _nl finalExpression: expression _nl '}' / '{' _nl finalExpression: expression _nl '}'
{ return h.nodeBlock([finalExpression]) } { return h.nodeBlock([finalExpression], location()) }
array_statements array_statements
= head:statement tail:(statementSeparator @array_statements ) = head:statement tail:(statementSeparator @array_statements )
@ -42,16 +42,16 @@ statement
voidStatement voidStatement
= "call" _nl value:zeroOMoreArgumentsBlockOrExpression = "call" _nl value:zeroOMoreArgumentsBlockOrExpression
{ var variable = h.nodeIdentifier("_", location()); { var variable = h.nodeIdentifier("_", location());
return h.nodeLetStatement(variable, value); } return h.nodeLetStatement(variable, value, location()); }
letStatement letStatement
= variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression = variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
{ return h.nodeLetStatement(variable, value) } { return h.nodeLetStatement(variable, value, location()) }
defunStatement defunStatement
= variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression = variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
{ var value = h.nodeLambda(args, body) { var value = h.nodeLambda(args, body, location(), variable)
return h.nodeLetStatement(variable, value) } return h.nodeLetStatement(variable, value, location()) }
assignmentOp "assignment" = '=' assignmentOp "assignment" = '='
@ -67,16 +67,16 @@ ifthenelse
= 'if' __nl condition:logicalAdditive = 'if' __nl condition:logicalAdditive
__nl 'then' __nl trueExpression:innerBlockOrExpression __nl 'then' __nl trueExpression:innerBlockOrExpression
__nl 'else' __nl falseExpression:(ifthenelse/innerBlockOrExpression) __nl 'else' __nl falseExpression:(ifthenelse/innerBlockOrExpression)
{ return h.nodeTernary(condition, trueExpression, falseExpression) } { return h.nodeTernary(condition, trueExpression, falseExpression, location()) }
ternary ternary
= condition:logicalAdditive _ '?' _nl trueExpression:logicalAdditive _ ':' _nl falseExpression:(ternary/logicalAdditive) = condition:logicalAdditive _ '?' _nl trueExpression:logicalAdditive _ ':' _nl falseExpression:(ternary/logicalAdditive)
{ return h.nodeTernary(condition, trueExpression, falseExpression) } { return h.nodeTernary(condition, trueExpression, falseExpression, location()) }
logicalAdditive logicalAdditive
= head:logicalMultiplicative tail:(_ operator:logicalAdditiveOp _nl arg:logicalMultiplicative {return {operator: operator, right: arg}})* = head:logicalMultiplicative tail:(_ operator:logicalAdditiveOp _nl arg:logicalMultiplicative {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) { { 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)} }, head)}
logicalAdditiveOp "operator" = '||' logicalAdditiveOp "operator" = '||'
@ -85,29 +85,37 @@ logicalAdditive
logicalMultiplicative logicalMultiplicative
= head:equality tail:(_ operator:logicalMultiplicativeOp _nl arg:equality {return {operator: operator, right: arg}})* = head:equality tail:(_ operator:logicalMultiplicativeOp _nl arg:equality {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) { { 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)} }, head)}
logicalMultiplicativeOp "operator" = '&&' logicalMultiplicativeOp "operator" = '&&'
equality equality
= left:relational _ operator:equalityOp _nl right:relational = 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 / relational
equalityOp "operator" = '=='/'!=' equalityOp "operator" = '=='/'!='
relational relational
= left:additive _ operator:relationalOp _nl right:additive = left:credibleInterval _ operator:relationalOp _nl right:credibleInterval
{ return h.makeFunctionCall(h.toFunction[operator], [left, right])} { return h.makeFunctionCall(h.toFunction[operator], [left, right], location())}
/ additive / credibleInterval
relationalOp "operator" = '<='/'<'/'>='/'>' relationalOp "operator" = '<='/'<'/'>='/'>'
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], location())
}, head)}
credibleIntervalOp "operator" = 'to'
additive additive
= head:multiplicative tail:(_ operator:additiveOp _nl arg:multiplicative {return {operator: operator, right: arg}})* = head:multiplicative tail:(_ operator:additiveOp _nl arg:multiplicative {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) { { 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)} }, head)}
additiveOp "operator" = '+' / '-' / '.+' / '.-' additiveOp "operator" = '+' / '-' / '.+' / '.-'
@ -115,31 +123,23 @@ additive
multiplicative multiplicative
= head:power tail:(_ operator:multiplicativeOp _nl arg:power {return {operator: operator, right: arg}})* = head:power tail:(_ operator:multiplicativeOp _nl arg:power {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) { { 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)} }, head)}
multiplicativeOp "operator" = '*' / '/' / '.*' / './' multiplicativeOp "operator" = '*' / '/' / '.*' / './'
power power
= head:credibleInterval tail:(_ operator:powerOp _nl arg:credibleInterval {return {operator: operator, right: arg}})* = head:chainFunctionCall tail:(_ operator:powerOp _nl arg:chainFunctionCall {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) { { 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)} }, head)}
powerOp "operator" = '^' / '.^' powerOp "operator" = '^' / '.^'
credibleInterval
= head:chainFunctionCall tail:(__ operator:credibleIntervalOp __nl arg:chainFunctionCall {return {operator: operator, right: arg}})*
{ return tail.reduce(function(result, element) {
return h.makeFunctionCall(h.toFunction[element.operator], [result, element.right])
}, head)}
credibleIntervalOp "operator" = 'to'
chainFunctionCall chainFunctionCall
= head:unary tail:(_ ('->'/'|>') _nl chained:chainedFunction {return chained})* = head:unary tail:(_ ('->'/'|>') _nl chained:chainedFunction {return chained})*
{ return tail.reduce(function(result, element) { { return tail.reduce(function(result, element) {
return h.makeFunctionCall(element.fnName, [result, ...element.args]) return h.makeFunctionCall(element.fnName, [result, ...element.args], location())
}, head)} }, head)}
chainedFunction chainedFunction
@ -154,7 +154,7 @@ chainFunctionCall
unary unary
= unaryOperator:unaryOperator _nl right:(unary/postOperator) = unaryOperator:unaryOperator _nl right:(unary/postOperator)
{ return h.makeFunctionCall(h.unaryToFunction[unaryOperator], [right])} { return h.makeFunctionCall(h.unaryToFunction[unaryOperator], [right], location())}
/ postOperator / postOperator
unaryOperator "unary operator" unaryOperator "unary operator"
@ -169,17 +169,17 @@ collectionElement
tail:( tail:(
_ '[' _nl arg:expression _nl ']' {return {fn: h.postOperatorToFunction['[]'], args: [arg]}} _ '[' _nl arg:expression _nl ']' {return {fn: h.postOperatorToFunction['[]'], args: [arg]}}
/ _ '(' _nl args:array_functionArguments _nl ')' {return {fn: h.postOperatorToFunction['()'], args: args}} / _ '(' _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 tail.reduce(function(result, element) {
return h.makeFunctionCall(element.fn, [result, ...element.args]) return h.makeFunctionCall(element.fn, [result, ...element.args], location())
}, head)} }, head)}
array_functionArguments array_functionArguments
= head:expression tail:(_ ',' _nl @expression)* = head:expression tail:(_ ',' _nl @expression)*
{ return [head, ...tail]; } { return [head, ...tail]; }
/ "" / ""
{return [h.nodeVoid()];} {return [h.nodeVoid(location())];}
atom atom
= '(' _nl expression:expression _nl ')' {return expression} = '(' _nl expression:expression _nl ')' {return expression}
@ -195,7 +195,7 @@ basicLiteral
/ voidLiteral / voidLiteral
voidLiteral 'void' voidLiteral 'void'
= "()" {return h.nodeVoid();} = "()" {return h.nodeVoid(location());}
variable = dollarIdentifierWithModule / dollarIdentifier variable = dollarIdentifierWithModule / dollarIdentifier
@ -221,36 +221,36 @@ dollarIdentifier '$identifier'
= ([\$_a-z]+[\$_a-z0-9]i*) {return h.nodeIdentifier(text(), location())} = ([\$_a-z]+[\$_a-z0-9]i*) {return h.nodeIdentifier(text(), location())}
moduleIdentifier 'identifier' 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' string 'string'
= characters:("'" @([^'])* "'") {return h.nodeString(characters.join(''))} = characters:("'" @([^'])* "'") {return h.nodeString(characters.join(''), location())}
/ characters:('"' @([^"])* '"') {return h.nodeString(characters.join(''))} / characters:('"' @([^"])* '"') {return h.nodeString(characters.join(''), location())}
number = number:(float / integer) unit:unitIdentifier? number = number:(float / integer) unit:unitIdentifier?
{ {
if (unit === null) if (unit === null)
{ return number } { return number }
else else
{ return h.makeFunctionCall('fromUnit_'+unit.value, [number]) { return h.makeFunctionCall('fromUnit_'+unit.value, [number], location())
} }
} }
integer 'integer' integer 'integer'
= d+ !"\." ![e]i = d+ !"\." ![e]i
{ return h.nodeInteger(parseInt(text()))} { return h.nodeInteger(parseInt(text()), location())}
float 'float' float 'float'
= $(((d+ "\." d*) / ("\." d+)) floatExponent? / d+ floatExponent) = $(((d+ "\." d*) / ("\." d+)) floatExponent? / d+ floatExponent)
{ return h.nodeFloat(parseFloat(text()))} { return h.nodeFloat(parseFloat(text()), location())}
floatExponent = [e]i '-'? d+ floatExponent = [e]i '-'? d+
d = [0-9] d = [0-9]
boolean 'boolean' boolean 'boolean'
= ('true'/'false') ! [a-z]i ! [_$] = ('true'/'false') ! [a-z]i ! [_$]
{ return h.nodeBoolean(text() === 'true')} { return h.nodeBoolean(text() === 'true', location())}
valueConstructor valueConstructor
= recordConstructor = recordConstructor
@ -261,15 +261,15 @@ valueConstructor
lambda lambda
= '{' _nl '|' _nl args:array_parameters _nl '|' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}' = '{' _nl '|' _nl args:array_parameters _nl '|' _nl statements:array_statements finalExpression: (statementSeparator @expression) _nl '}'
{ statements.push(finalExpression) { 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 '}' / '{' _nl '|' _nl args:array_parameters _nl '|' _nl finalExpression: expression _nl '}'
{ return h.nodeLambda(args, finalExpression) } { return h.nodeLambda(args, finalExpression, location(), undefined) }
arrayConstructor 'array' arrayConstructor 'array'
= '[' _nl ']' = '[' _nl ']'
{ return h.constructArray([]); } { return h.constructArray([], location()); }
/ '[' _nl args:array_elements _nl ']' / '[' _nl args:array_elements _nl ']'
{ return h.constructArray(args); } { return h.constructArray(args, location()); }
array_elements array_elements
= head:expression tail:(_ ',' _nl @expression)* = head:expression tail:(_ ',' _nl @expression)*
@ -277,7 +277,7 @@ arrayConstructor 'array'
recordConstructor 'record' recordConstructor 'record'
= '{' _nl args:array_recordArguments _nl end_of_record = '{' _nl args:array_recordArguments _nl end_of_record
{ return h.constructRecord(args); } { return h.constructRecord(args, location()); }
end_of_record end_of_record
= '}' = '}'
@ -289,7 +289,7 @@ recordConstructor 'record'
keyValuePair keyValuePair
= key:expression _ ':' _nl value:expression = key:expression _ ':' _nl value:expression
{ return h.nodeKeyValue(key, value)} { return h.nodeKeyValue(key, value, location())}
// Separators // Separators

View File

@ -1,22 +1,37 @@
module Extra = Reducer_Extra 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" external castWithLocation: Js.Exn.t => withLocation = "%identity"
let syntaxErrorToLocation = (error: Js.Exn.t): Reducer_ErrorValue.syntaxErrorLocation => let syntaxErrorToLocation = (error: Js.Exn.t): location => castWithLocation(error)["location"]
castWithLocation(error)["location"]
let parse = (expr: string): result<node, errorValue> => let parse = (expr: string, source: string): parseResult =>
try { try {
Ok(parse__(expr)) Ok(parse__(expr, {"grammarSource": source}))
} catch { } catch {
| Js.Exn.Error(obj) => | 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>} type nodeBlock = {...node, "statements": array<node>}
@ -29,32 +44,35 @@ type nodeIdentifier = {...node, "value": string}
type nodeInteger = {...node, "value": int} type nodeInteger = {...node, "value": int}
type nodeKeyValue = {...node, "key": node, "value": node} type nodeKeyValue = {...node, "key": node, "value": node}
type nodeRecord = {...node, "elements": array<nodeKeyValue>} 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 nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
type nodeModuleIdentifier = {...node, "value": string} type nodeModuleIdentifier = {...node, "value": string}
type nodeString = {...node, "value": string} type nodeString = {...node, "value": string}
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node} type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
// type nodeTypeIdentifier = {...node, "value": string}
type nodeVoid = node type nodeVoid = node
type peggyNode = type astContent =
| PgNodeBlock(nodeBlock) | ASTBlock(nodeBlock)
| PgNodeProgram(nodeProgram) | ASTProgram(nodeProgram)
| PgNodeArray(nodeArray) | ASTArray(nodeArray)
| PgNodeRecord(nodeRecord) | ASTRecord(nodeRecord)
| PgNodeBoolean(nodeBoolean) | ASTBoolean(nodeBoolean)
| PgNodeFloat(nodeFloat) | ASTFloat(nodeFloat)
| PgNodeCall(nodeCall) | ASTCall(nodeCall)
| PgNodeIdentifier(nodeIdentifier) | ASTIdentifier(nodeIdentifier)
| PgNodeInteger(nodeInteger) | ASTInteger(nodeInteger)
| PgNodeKeyValue(nodeKeyValue) | ASTKeyValue(nodeKeyValue)
| PgNodeLambda(nodeLambda) | ASTLambda(nodeLambda)
| PgNodeLetStatement(nodeLetStatement) | ASTLetStatement(nodeLetStatement)
| PgNodeModuleIdentifier(nodeModuleIdentifier) | ASTModuleIdentifier(nodeModuleIdentifier)
| PgNodeString(nodeString) | ASTString(nodeString)
| PgNodeTernary(nodeTernary) | ASTTernary(nodeTernary)
// | PgNodeTypeIdentifier(nodeTypeIdentifier) | ASTVoid(nodeVoid)
| PgNodeVoid(nodeVoid)
type ast = {
location: location,
content: astContent,
}
external castNodeBlock: node => nodeBlock = "%identity" external castNodeBlock: node => nodeBlock = "%identity"
external castNodeProgram: node => nodeProgram = "%identity" external castNodeProgram: node => nodeProgram = "%identity"
@ -71,80 +89,92 @@ external castNodeLetStatement: node => nodeLetStatement = "%identity"
external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity" external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity"
external castNodeString: node => nodeString = "%identity" external castNodeString: node => nodeString = "%identity"
external castNodeTernary: node => nodeTernary = "%identity" external castNodeTernary: node => nodeTernary = "%identity"
// external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity"
external castNodeVoid: node => nodeVoid = "%identity" external castNodeVoid: node => nodeVoid = "%identity"
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
let castNodeType = (node: node) => let nodeToAST = (node: node) => {
switch node["type"] { let content = switch node["type"] {
| "Block" => node->castNodeBlock->PgNodeBlock | "Block" => node->castNodeBlock->ASTBlock
| "Program" => node->castNodeBlock->PgNodeProgram | "Program" => node->castNodeBlock->ASTProgram
| "Array" => node->castNodeArray->PgNodeArray | "Array" => node->castNodeArray->ASTArray
| "Record" => node->castNodeRecord->PgNodeRecord | "Record" => node->castNodeRecord->ASTRecord
| "Boolean" => node->castNodeBoolean->PgNodeBoolean | "Boolean" => node->castNodeBoolean->ASTBoolean
| "Call" => node->castNodeCall->PgNodeCall | "Call" => node->castNodeCall->ASTCall
| "Float" => node->castNodeFloat->PgNodeFloat | "Float" => node->castNodeFloat->ASTFloat
| "Identifier" => node->castNodeIdentifier->PgNodeIdentifier | "Identifier" => node->castNodeIdentifier->ASTIdentifier
| "Integer" => node->castNodeInteger->PgNodeInteger | "Integer" => node->castNodeInteger->ASTInteger
| "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue | "KeyValue" => node->castNodeKeyValue->ASTKeyValue
| "Lambda" => node->castNodeLambda->PgNodeLambda | "Lambda" => node->castNodeLambda->ASTLambda
| "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement | "LetStatement" => node->castNodeLetStatement->ASTLetStatement
| "ModuleIdentifier" => node->castNodeModuleIdentifier->PgNodeModuleIdentifier | "ModuleIdentifier" => node->castNodeModuleIdentifier->ASTModuleIdentifier
| "String" => node->castNodeString->PgNodeString | "String" => node->castNodeString->ASTString
| "Ternary" => node->castNodeTernary->PgNodeTernary | "Ternary" => node->castNodeTernary->ASTTernary
// | "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier | "Void" => node->castNodeVoid->ASTVoid
| "Void" => node->castNodeVoid->PgNodeVoid
| _ => raise(UnsupportedPeggyNodeType(node["type"])) | _ => 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 => 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 => 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 => let pgNodesToStringUsingSeparator = (nodes: array<ast>, separator: string): string =>
nodes->Js.Array2.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") nodes->Belt.Array.map(pgToString)->Extra.Array.intersperse(separator)->Js.String.concatMany("")
switch peggyNode { switch ast.content {
| PgNodeBlock(node) | ASTBlock(node)
| PgNodeProgram(node) => | ASTProgram(node) =>
"{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}" "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"
| PgNodeArray(node) => "[" ++ node["elements"]->nodesToStringUsingSeparator("; ") ++ "]" | ASTArray(node) => "[" ++ node["elements"]->nodesToStringUsingSeparator("; ") ++ "]"
| PgNodeRecord(node) => | ASTRecord(node) =>
"{" ++ "{" ++
node["elements"] node["elements"]
->Js.Array2.map(element => PgNodeKeyValue(element)) ->Belt.Array.map(element => element->nodeKeyValueToAST)
->pgNodesToStringUsingSeparator(", ") ++ "}" ->pgNodesToStringUsingSeparator(", ") ++ "}"
| PgNodeBoolean(node) => node["value"]->Js.String.make | ASTBoolean(node) => node["value"]->Js.String.make
| PgNodeCall(node) => | ASTCall(node) =>
"(" ++ node["fn"]->toString ++ " " ++ node["args"]->nodesToStringUsingSeparator(" ") ++ ")" "(" ++ node["fn"]->toString ++ " " ++ node["args"]->nodesToStringUsingSeparator(" ") ++ ")"
| PgNodeFloat(node) => node["value"]->Js.String.make | ASTFloat(node) => node["value"]->Js.String.make
| PgNodeIdentifier(node) => `:${node["value"]}` | ASTIdentifier(node) => `:${node["value"]}`
| PgNodeInteger(node) => node["value"]->Js.String.make | ASTInteger(node) => node["value"]->Js.String.make
| PgNodeKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"]) | ASTKeyValue(node) => toString(node["key"]) ++ ": " ++ toString(node["value"])
| PgNodeLambda(node) => | ASTLambda(node) => "{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}"
"{|" ++ node["args"]->argsToString ++ "| " ++ node["body"]->toString ++ "}" | ASTLetStatement(node) =>
| PgNodeLetStatement(node) => pgToString(node["variable"]->nodeIdentifierToAST) ++ " = " ++ toString(node["value"])
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"]) | ASTModuleIdentifier(node) => `@${node["value"]}`
| PgNodeModuleIdentifier(node) => `@${node["value"]}` | ASTString(node) => `'${node["value"]->Js.String.make}'`
| PgNodeString(node) => `'${node["value"]->Js.String.make}'` | ASTTernary(node) =>
| PgNodeTernary(node) =>
"(::$$_ternary_$$ " ++ "(::$$_ternary_$$ " ++
toString(node["condition"]) ++ toString(node["condition"]) ++
" " ++ " " ++
toString(node["trueExpression"]) ++ toString(node["trueExpression"]) ++
" " ++ " " ++
toString(node["falseExpression"]) ++ ")" toString(node["falseExpression"]) ++ ")"
// | PgNodeTypeIdentifier(node) => `#${node["value"]}` | ASTVoid(_node) => "()"
| PgNodeVoid(_node) => "()"
} }
} }
and toString = (node: node): string => node->castNodeType->pgToString and toString = (node: node): string => node->nodeToAST->pgToString
let toStringResult = (rNode: result<node, errorValue>): string => let toStringError = (error: parseError): string => {
let SyntaxError(message, _) = error
`Syntax Error: ${message}}`
}
let toStringResult = (rNode: parseResult): string =>
switch rNode { switch rNode {
| Ok(node) => toString(node) | Ok(node) => node->toString
| Error(error) => `Error(${errorToString(error)})` | Error(error) => `Error(${error->toStringError})`
} }

View File

@ -3,23 +3,27 @@ module ExpressionT = Reducer_Expression_T
module Parse = Reducer_Peggy_Parse module Parse = Reducer_Peggy_Parse
type expression = Reducer_T.expression type expression = Reducer_T.expression
type expressionContent = Reducer_T.expressionContent
let rec fromNode = (node: Parse.node): expression => { let rec fromNode = (node: Parse.node): expression => {
let ast = Parse.nodeToAST(node)
let content: expressionContent = {
let caseBlock = nodeBlock => let caseBlock = nodeBlock =>
ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)) ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode))
let caseProgram = nodeProgram => let caseProgram = nodeProgram =>
ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode)) ExpressionBuilder.eProgram(nodeProgram["statements"]->Js.Array2.map(fromNode))
let caseLambda = (nodeLambda: Parse.nodeLambda): expression => { let caseLambda = (nodeLambda: Parse.nodeLambda): expressionContent => {
let args = let args =
nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"]) nodeLambda["args"]->Js.Array2.map((argNode: Parse.nodeIdentifier) => argNode["value"])
let body = nodeLambda["body"]->fromNode 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"] nodeRecord["elements"]
->Js.Array2.map(keyValueNode => ( ->Js.Array2.map(keyValueNode => (
keyValueNode["key"]->fromNode, keyValueNode["key"]->fromNode,
@ -28,30 +32,30 @@ let rec fromNode = (node: Parse.node): expression => {
->ExpressionBuilder.eRecord ->ExpressionBuilder.eRecord
} }
switch Parse.castNodeType(node) { switch ast.content {
| PgNodeBlock(nodeBlock) => caseBlock(nodeBlock) | ASTBlock(nodeBlock) => caseBlock(nodeBlock)
| PgNodeProgram(nodeProgram) => caseProgram(nodeProgram) | ASTProgram(nodeProgram) => caseProgram(nodeProgram)
| PgNodeArray(nodeArray) => | ASTArray(nodeArray) =>
ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode)) ExpressionBuilder.eArray(nodeArray["elements"]->Js.Array2.map(fromNode))
| PgNodeRecord(nodeRecord) => caseRecord(nodeRecord) | ASTRecord(nodeRecord) => caseRecord(nodeRecord)
| PgNodeBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"]) | ASTBoolean(nodeBoolean) => ExpressionBuilder.eBool(nodeBoolean["value"])
| PgNodeCall(nodeCall) => | ASTCall(nodeCall) =>
ExpressionBuilder.eCall(fromNode(nodeCall["fn"]), nodeCall["args"]->Js.Array2.map(fromNode)) ExpressionBuilder.eCall(fromNode(nodeCall["fn"]), nodeCall["args"]->Js.Array2.map(fromNode))
| PgNodeFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"]) | ASTFloat(nodeFloat) => ExpressionBuilder.eNumber(nodeFloat["value"])
| PgNodeIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"]) | ASTIdentifier(nodeIdentifier) => ExpressionBuilder.eSymbol(nodeIdentifier["value"])
| PgNodeInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"])) | ASTInteger(nodeInteger) => ExpressionBuilder.eNumber(Belt.Int.toFloat(nodeInteger["value"]))
| PgNodeKeyValue(nodeKeyValue) => | ASTKeyValue(nodeKeyValue) =>
ExpressionBuilder.eArray([fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])]) ExpressionBuilder.eArray([fromNode(nodeKeyValue["key"]), fromNode(nodeKeyValue["value"])])
| PgNodeLambda(nodeLambda) => caseLambda(nodeLambda) | ASTLambda(nodeLambda) => caseLambda(nodeLambda)
| PgNodeLetStatement(nodeLetStatement) => | ASTLetStatement(nodeLetStatement) =>
ExpressionBuilder.eLetStatement( ExpressionBuilder.eLetStatement(
nodeLetStatement["variable"]["value"], nodeLetStatement["variable"]["value"],
fromNode(nodeLetStatement["value"]), fromNode(nodeLetStatement["value"]),
) )
| PgNodeModuleIdentifier(nodeModuleIdentifier) => | ASTModuleIdentifier(nodeModuleIdentifier) =>
ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"]) ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"])
| PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"]) | ASTString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
| PgNodeTernary(nodeTernary) => | ASTTernary(nodeTernary) =>
ExpressionBuilder.eTernary( ExpressionBuilder.eTernary(
fromNode(nodeTernary["condition"]), fromNode(nodeTernary["condition"]),
fromNode(nodeTernary["trueExpression"]), fromNode(nodeTernary["trueExpression"]),
@ -59,6 +63,12 @@ let rec fromNode = (node: Parse.node): expression => {
) )
// | PgNodeTypeIdentifier(nodeTypeIdentifier) => // | PgNodeTypeIdentifier(nodeTypeIdentifier) =>
// ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"]) // ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"])
| PgNodeVoid(_) => ExpressionBuilder.eVoid | ASTVoid(_) => ExpressionBuilder.eVoid
}
}
{
ast: ast,
content: content,
} }
} }

View File

@ -34,83 +34,92 @@ export const postOperatorToFunction = {
"[]": "$_atIndex_$", "[]": "$_atIndex_$",
}; };
type NodeBlock = { type Node = {
location: LocationRange;
};
type NodeBlock = Node & {
type: "Block"; type: "Block";
statements: AnyPeggyNode[]; statements: AnyPeggyNode[];
}; };
type NodeProgram = { type NodeProgram = Node & {
type: "Program"; type: "Program";
statements: AnyPeggyNode[]; statements: AnyPeggyNode[];
}; };
type NodeArray = { type NodeArray = Node & {
type: "Array"; type: "Array";
elements: AnyPeggyNode[]; elements: AnyPeggyNode[];
}; };
type NodeRecord = { type NodeRecord = Node & {
type: "Record"; type: "Record";
elements: NodeKeyValue[]; elements: NodeKeyValue[];
}; };
type NodeCall = { type NodeCall = Node & {
type: "Call"; type: "Call";
fn: AnyPeggyNode; fn: AnyPeggyNode;
args: AnyPeggyNode[]; args: AnyPeggyNode[];
}; };
type NodeFloat = { type NodeFloat = Node & {
type: "Float"; type: "Float";
value: number; value: number;
}; };
type NodeInteger = { type NodeInteger = Node & {
type: "Integer"; type: "Integer";
value: number; value: number;
}; };
type NodeIdentifier = { type NodeIdentifier = Node & {
type: "Identifier"; type: "Identifier";
value: string; value: string;
}; };
type NodeLetStatement = { type NodeLetStatement = Node & {
type: "LetStatement"; type: "LetStatement";
variable: NodeIdentifier; variable: NodeIdentifier;
value: AnyPeggyNode; value: AnyPeggyNode;
}; };
type NodeLambda = { type NodeLambda = Node & {
type: "Lambda"; type: "Lambda";
args: AnyPeggyNode[]; args: AnyPeggyNode[];
body: AnyPeggyNode; body: AnyPeggyNode;
name?: string;
}; };
type NodeTernary = { type NodeTernary = Node & {
type: "Ternary"; type: "Ternary";
condition: AnyPeggyNode; condition: AnyPeggyNode;
trueExpression: AnyPeggyNode; trueExpression: AnyPeggyNode;
falseExpression: AnyPeggyNode; falseExpression: AnyPeggyNode;
}; };
type NodeKeyValue = { type NodeKeyValue = Node & {
type: "KeyValue"; type: "KeyValue";
key: AnyPeggyNode; key: AnyPeggyNode;
value: AnyPeggyNode; value: AnyPeggyNode;
}; };
type NodeString = { type NodeString = Node & {
type: "String"; type: "String";
value: string; value: string;
location?: LocationRange; location?: LocationRange;
}; };
type NodeBoolean = { type NodeBoolean = Node & {
type: "Boolean"; type: "Boolean";
value: boolean; value: boolean;
}; };
type NodeVoid = Node & {
type: "Void";
};
export type AnyPeggyNode = export type AnyPeggyNode =
| NodeArray | NodeArray
| NodeRecord | NodeRecord
@ -125,47 +134,78 @@ export type AnyPeggyNode =
| NodeTernary | NodeTernary
| NodeKeyValue | NodeKeyValue
| NodeString | NodeString
| NodeBoolean; | NodeBoolean
| NodeVoid;
export function makeFunctionCall(fn: string, args: AnyPeggyNode[]) { export function makeFunctionCall(
fn: string,
args: AnyPeggyNode[],
location: LocationRange
) {
if (fn === "$$_applyAll_$$") { if (fn === "$$_applyAll_$$") {
return nodeCall(args[0], args.splice(1)); return nodeCall(args[0], args.splice(1), location);
} else { } else {
return nodeCall(nodeIdentifier(fn), args); return nodeCall(nodeIdentifier(fn, location), args, location);
} }
} }
export function constructArray(elements: AnyPeggyNode[]) { export function constructArray(
return { type: "Array", elements }; elements: AnyPeggyNode[],
location: LocationRange
): NodeArray {
return { type: "Array", elements, location };
} }
export function constructRecord(elements: AnyPeggyNode[]) { export function constructRecord(
return { type: "Record", elements }; elements: NodeKeyValue[],
location: LocationRange
): NodeRecord {
return { type: "Record", elements, location };
} }
export function nodeBlock(statements: AnyPeggyNode[]): NodeBlock { export function nodeBlock(
return { type: "Block", statements }; statements: AnyPeggyNode[],
location: LocationRange
): NodeBlock {
return { type: "Block", statements, location };
} }
export function nodeProgram(statements: AnyPeggyNode[]): NodeProgram { export function nodeProgram(
return { type: "Program", statements }; statements: AnyPeggyNode[],
location: LocationRange
): NodeProgram {
return { type: "Program", statements, location };
} }
export function nodeBoolean(value: boolean): NodeBoolean { export function nodeBoolean(
return { type: "Boolean", value }; value: boolean,
location: LocationRange
): NodeBoolean {
return { type: "Boolean", value, location };
} }
export function nodeCall(fn: AnyPeggyNode, args: AnyPeggyNode[]): NodeCall { export function nodeCall(
return { type: "Call", fn, args }; fn: AnyPeggyNode,
args: AnyPeggyNode[],
location: LocationRange
): NodeCall {
return { type: "Call", fn, args, location };
} }
export function nodeFloat(value: number): NodeFloat { export function nodeFloat(value: number, location: LocationRange): NodeFloat {
return { type: "Float", value }; return { type: "Float", value, location };
} }
export function nodeIdentifier(value: string): NodeIdentifier { export function nodeIdentifier(
return { type: "Identifier", value }; value: string,
location: LocationRange
): NodeIdentifier {
return { type: "Identifier", value, location };
} }
export function nodeInteger(value: number): NodeInteger { export function nodeInteger(
return { type: "Integer", value }; value: number,
location: LocationRange
): NodeInteger {
return { type: "Integer", value, location };
} }
export function nodeKeyValue( export function nodeKeyValue(
key: AnyPeggyNode, key: AnyPeggyNode,
value: AnyPeggyNode value: AnyPeggyNode,
location: LocationRange
): NodeKeyValue { ): NodeKeyValue {
if (key.type === "Identifier") { if (key.type === "Identifier") {
key = { key = {
@ -173,43 +213,46 @@ export function nodeKeyValue(
type: "String", type: "String",
}; };
} }
return { type: "KeyValue", key, value }; return { type: "KeyValue", key, value, location };
} }
export function nodeLambda( export function nodeLambda(
args: AnyPeggyNode[], args: AnyPeggyNode[],
body: AnyPeggyNode body: AnyPeggyNode,
location: LocationRange,
name?: NodeIdentifier
): NodeLambda { ): NodeLambda {
return { type: "Lambda", args, body }; return { type: "Lambda", args, body, location, name: name?.value };
} }
export function nodeLetStatement( export function nodeLetStatement(
variable: NodeIdentifier, variable: NodeIdentifier,
value: AnyPeggyNode value: AnyPeggyNode,
location: LocationRange
): NodeLetStatement { ): 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) { export function nodeModuleIdentifier(value: string, location: LocationRange) {
return { type: "ModuleIdentifier", value }; return { type: "ModuleIdentifier", value, location };
} }
export function nodeString(value: string): NodeString { export function nodeString(value: string, location: LocationRange): NodeString {
return { type: "String", value }; return { type: "String", value, location };
} }
export function nodeTernary( export function nodeTernary(
condition: AnyPeggyNode, condition: AnyPeggyNode,
trueExpression: AnyPeggyNode, trueExpression: AnyPeggyNode,
falseExpression: AnyPeggyNode falseExpression: AnyPeggyNode,
location: LocationRange
): NodeTernary { ): NodeTernary {
return { return {
type: "Ternary", type: "Ternary",
condition, condition,
trueExpression, trueExpression,
falseExpression, falseExpression,
location,
}; };
} }
export function nodeTypeIdentifier(typeValue: string) { export function nodeVoid(location: LocationRange): NodeVoid {
return { type: "TypeIdentifier", value: typeValue }; return { type: "Void", location };
}
export function nodeVoid() {
return { type: "Void" };
} }

View File

@ -16,14 +16,18 @@ type rec value =
| IEvVoid | IEvVoid
@genType.opaque and arrayValue = array<value> @genType.opaque and arrayValue = array<value>
@genType.opaque and map = Belt.Map.String.t<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 @genType.opaque
and lambdaValue = { and lambdaValue =
| FnLambda({
parameters: array<string>, parameters: array<string>,
body: lambdaBody, body: lambdaBody,
} location: Reducer_Peggy_Parse.location,
name: option<string>,
})
| FnBuiltin({body: lambdaBody, name: string})
@genType.opaque and lambdaDeclaration = Declaration.declaration<lambdaValue> @genType.opaque and lambdaDeclaration = Declaration.declaration<lambdaValue>
and expression = and expressionContent =
| EBlock(array<expression>) | 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. // 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>) | EProgram(array<expression>)
@ -33,18 +37,34 @@ and expression =
| ETernary(expression, expression, expression) | ETernary(expression, expression, expression)
| EAssign(string, expression) | EAssign(string, expression)
| ECall(expression, array<expression>) | ECall(expression, array<expression>)
| ELambda(array<string>, expression) | ELambda(array<string>, expression, option<string>)
| EValue(value) | EValue(value)
and expression = {
ast: Reducer_Peggy_Parse.ast,
content: expressionContent,
}
and namespace = Belt.Map.String.t<value> and namespace = Belt.Map.String.t<value>
and bindings = { and bindings = {
namespace: namespace, namespace: namespace,
parent: option<bindings>, 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 = { and context = {
bindings: bindings, bindings: bindings,
environment: environment, environment: environment,
frameStack: frameStack,
inFunction: option<lambdaValue>,
} }
and reducerFn = (expression, context) => (value, context) 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 type environment = GenericDist.env
module T = Reducer_T module T = Reducer_T
@ -32,10 +28,12 @@ and toStringCall = fName => `:${fName}`
and toStringDate = date => DateTime.Date.toString(date) and toStringDate = date => DateTime.Date.toString(date)
and toStringDeclaration = d => Declaration.toString(d, r => toString(IEvLambda(r))) and toStringDeclaration = d => Declaration.toString(d, r => toString(IEvLambda(r)))
and toStringDistribution = dist => GenericDist.toString(dist) and toStringDistribution = dist => GenericDist.toString(dist)
and toStringLambda = (lambdaValue: T.lambdaValue) => and toStringLambda = (lambdaValue: T.lambdaValue) => {
`lambda(${Js.Array2.toString(lambdaValue.parameters)}=>internal code)` switch lambdaValue {
and toStringFunction = (lambdaValue: T.lambdaValue) => | FnLambda({parameters}) => `lambda(${Js.Array2.toString(parameters)}=>internal code)`
`function(${Js.Array2.toString(lambdaValue.parameters)})` | FnBuiltin(_) => "Builtin function"
}
}
and toStringNumber = aNumber => Js.String.make(aNumber) and toStringNumber = aNumber => Js.String.make(aNumber)
and toStringRecord = aMap => aMap->toStringMap and toStringRecord = aMap => aMap->toStringMap
and toStringString = aString => `'${aString}'` and toStringString = aString => `'${aString}'`
@ -76,25 +74,13 @@ let toStringFunctionCall = ((fn, args)): string => `${fn}(${argsToString(args)})
let toStringResult = x => let toStringResult = x =>
switch x { switch x {
| Ok(a) => `Ok(${toString(a)})` | Ok(a) => `Ok(${toString(a)})`
| Error(m) => `Error(${ErrorValue.errorToString(m)})` | Error(m) => `Error(${SqError.toString(m)})`
} }
let toStringOptionResult = x => let toStringResultOkless = (codeResult: result<t, SqError.t>): string =>
switch x {
| Some(a) => toStringResult(a)
| None => "None"
}
let toStringResultOkless = (codeResult: result<t, ErrorValue.errorValue>): string =>
switch codeResult { switch codeResult {
| Ok(a) => toString(a) | Ok(a) => toString(a)
| Error(m) => `Error(${ErrorValue.errorToString(m)})` | Error(m) => `Error(${SqError.toString(m)})`
}
let toStringResultRecord = x =>
switch x {
| Ok(a) => `Ok(${toStringMap(a)})`
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
} }
type internalExpressionValueType = type internalExpressionValueType =
@ -156,10 +142,10 @@ let functionCallSignatureToString = (functionCallSignature: functionCallSignatur
let arrayToValueArray = (arr: array<t>): array<t> => arr 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 { switch rExpression {
| Ok(expression) => expression | 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 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 // TODO: Auto clean project based on topology
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
module ErrorValue = Reducer_ErrorValue
module ProjectItem = ReducerProject_ProjectItem module ProjectItem = ReducerProject_ProjectItem
module T = ReducerProject_T module T = ReducerProject_T
module Topology = ReducerProject_Topology 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 => let getResult = (project: t, sourceId: string): ProjectItem.T.resultArgumentType =>
switch getResultOption(project, sourceId) { switch getResultOption(project, sourceId) {
| None => RENeedToRun->Error | None => RENeedToRun->SqError.fromMessage->Error
| Some(result) => result | Some(result) => result
} }
@ -197,7 +196,7 @@ let linkDependencies = (project: t, sourceId: string): Reducer_T.namespace => {
"__result__", "__result__",
switch project->getResult(id) { switch project->getResult(id) {
| Ok(result) => result | 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") @module("./ReducerProject_IncludeParser.js")
external parse__: string => array<array<string>> = "parse" external parse__: string => array<array<string>> = "parse"
let parseIncludes = (expr: string): result< let parseIncludes = (expr: string): result<array<(string, string)>, SqError.t> =>
array<(string, string)>,
Reducer_ErrorValue.errorValue,
> =>
try { try {
let answer = parse__(expr) let answer = parse__(expr)
// let logEntry = answer->Js.Array2.joinWith(",") // 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 Belt.Array.map(answer, item => (item[0], item[1]))->Ok
} catch { } catch {
| Js.Exn.Error(obj) => | Js.Exn.Error(obj) =>
RESyntaxError( RESyntaxError(Belt.Option.getExn(Js.Exn.message(obj)))
Belt.Option.getExn(Js.Exn.message(obj)), ->SqError.fromMessageWithFrameStack(
Reducer_Peggy_Parse.syntaxErrorToLocation(obj)->Some, Reducer_FrameStack.makeSingleFrameStack(Reducer_Peggy_Parse.syntaxErrorToLocation(obj)),
)->Error )
->Error
} }

View File

@ -4,8 +4,9 @@ module T = ReducerProject_ProjectItem_T
type projectItem = T.projectItem type projectItem = T.projectItem
type t = T.t type t = T.t
let emptyItem: projectItem = { let emptyItem = (sourceId: string): projectItem => {
source: "", source: "",
sourceId: sourceId,
rawParse: None, rawParse: None,
expression: None, expression: None,
continuation: Reducer_Namespace.make(), continuation: Reducer_Namespace.make(),
@ -18,6 +19,7 @@ let emptyItem: projectItem = {
// source -> rawParse -> includes -> expression -> continuation -> result // source -> rawParse -> includes -> expression -> continuation -> result
let getSource = (r: t): T.sourceType => r.source 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 getRawParse = (r: t): T.rawParseType => r.rawParse
let getExpression = (r: t): T.expressionType => r.expression let getExpression = (r: t): T.expressionType => r.expression
let getContinuation = (r: t): T.continuationArgumentType => r.continuation 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 getIncludesAsVariables = (r: t): T.importAsVariablesType => r.includeAsVariables
let touchSource = (this: t): t => { let touchSource = (this: t): t => {
let r = emptyItem let r = emptyItem(this->getSourceId)
{ {
...r, ...r,
source: getSource(this), source: getSource(this),
@ -41,8 +43,9 @@ let touchSource = (this: t): t => {
} }
let touchRawParse = (this: t): t => { let touchRawParse = (this: t): t => {
let r = emptyItem(this->getSourceId)
{ {
...emptyItem, ...r,
source: getSource(this), source: getSource(this),
continues: getContinues(this), continues: getContinues(this),
includes: getIncludes(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 => let rawParse = (this: t): t =>
this->getRawParse->E.O2.defaultFn(() => doRawParse(this))->setRawParse(this, _) 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()) this->setResult(e->Error)->setContinuation(Reducer_Namespace.make())
let doRun = (this: t, context: Reducer_T.context): t => let doRun = (this: t, context: Reducer_T.context): t =>
@ -181,11 +185,11 @@ let doRun = (this: t, context: Reducer_T.context): t =>
->setResult(result->Ok) ->setResult(result->Ok)
->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals) ->setContinuation(contextAfterEvaluation.bindings->Reducer_Bindings.locals)
} catch { } catch {
| Reducer_ErrorValue.ErrorException(e) => this->failRun(e) | e => this->failRun(e->SqError.fromException)
} }
| Error(e) => this->failRun(e) | 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 => { let run = (this: t, context: Reducer_T.context): t => {

View File

@ -1,26 +1,24 @@
module Parse = Reducer_Peggy_Parse module Parse = Reducer_Peggy_Parse
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
open Reducer_ErrorValue
type sourceArgumentType = string type sourceArgumentType = string
type sourceType = string type sourceType = string
type rawParseArgumentType = result<Parse.node, errorValue> type rawParseArgumentType = result<Parse.node, SqError.t>
type rawParseType = option<rawParseArgumentType> type rawParseType = option<rawParseArgumentType>
type expressionArgumentType = result<ExpressionT.t, errorValue> type expressionArgumentType = result<ExpressionT.t, SqError.t>
type expressionType = option<expressionArgumentType> type expressionType = option<expressionArgumentType>
type continuationArgumentType = Reducer_T.namespace type continuationArgumentType = Reducer_T.namespace
type continuationType = option<continuationArgumentType> type resultArgumentType = result<Reducer_T.value, SqError.t>
type continuationResultType = option<result<continuationArgumentType, errorValue>>
type resultArgumentType = result<Reducer_T.value, errorValue>
type resultType = option<resultArgumentType> type resultType = option<resultArgumentType>
type continuesArgumentType = array<string> type continuesArgumentType = array<string>
type continuesType = array<string> type continuesType = array<string>
type includesArgumentType = string type includesArgumentType = string
type includesType = result<array<string>, errorValue> type includesType = result<array<string>, SqError.t>
type importAsVariablesType = array<(string, string)> type importAsVariablesType = array<(string, string)>
type projectItem = { type projectItem = {
source: sourceType, source: sourceType,
sourceId: string,
rawParse: rawParseType, rawParse: rawParseType,
expression: expressionType, expression: expressionType,
continuation: continuationArgumentType, continuation: continuationArgumentType,

View File

@ -27,4 +27,4 @@ type projectJson = {
let getSourceIds = (project: t): array<string> => Belt.MutableMap.String.keysToArray(project.items) let getSourceIds = (project: t): array<string> => Belt.MutableMap.String.keysToArray(project.items)
let getItem = (project: t, sourceId: string) => 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 = { let stdLib: Reducer_T.namespace = {
// constants // constants
let res = let res =
@ -10,23 +8,23 @@ let stdLib: Reducer_T.namespace = {
// array and record lookups // array and record lookups
let res = res->Reducer_Namespace.set( let res = res->Reducer_Namespace.set(
"$_atIndex_$", "$_atIndex_$",
Reducer_Expression_Lambda.makeFFILambda((inputs, _, _) => { Reducer_Lambda.makeFFILambda("$_atIndex_$", (inputs, _, _) => {
switch inputs { switch inputs {
| [IEvArray(aValueArray), IEvNumber(fIndex)] => { | [IEvArray(aValueArray), IEvNumber(fIndex)] => {
let index = Belt.Int.fromFloat(fIndex) // TODO - fail on non-integer indices? let index = Belt.Int.fromFloat(fIndex) // TODO - fail on non-integer indices?
switch Belt.Array.get(aValueArray, index) { switch Belt.Array.get(aValueArray, index) {
| Some(value) => value | 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)] => | [IEvRecord(dict), IEvString(sIndex)] =>
switch Belt.Map.String.get(dict, sIndex) { switch Belt.Map.String.get(dict, sIndex) {
| Some(value) => value | Some(value) => value
| None => | 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, })->Reducer_T.IEvLambda,
) )
@ -46,10 +44,10 @@ let stdLib: Reducer_T.namespace = {
->Belt.Array.reduce(res, (cur, name) => { ->Belt.Array.reduce(res, (cur, name) => {
cur->Reducer_Namespace.set( cur->Reducer_Namespace.set(
name, name,
Reducer_Expression_Lambda.makeFFILambda((arguments, environment, reducer) => { Reducer_Lambda.makeFFILambda(name, (arguments, context, reducer) => {
switch FunctionRegistry_Library.call(name, arguments, environment, reducer) { switch FunctionRegistry_Library.call(name, arguments, context, reducer) {
| Ok(value) => value | Ok(value) => value
| Error(error) => error->Reducer_ErrorValue.ErrorException->raise | Error(error) => error->SqError.Message.throw
} }
})->Reducer_T.IEvLambda, })->Reducer_T.IEvLambda,
) )

View File

@ -3,3 +3,4 @@
/server/out /server/out
/*.vsix /*.vsix
/syntaxes/*.json /syntaxes/*.json
dist

View File

@ -1,7 +1,10 @@
.vscode/** .vscode/**
.vscode-test/** .vscode-test/**
src/** src/**
client
server
.gitignore .gitignore
node_modules
.yarnrc .yarnrc
vsc-extension-quickstart.md vsc-extension-quickstart.md
**/tsconfig.json **/tsconfig.json

View File

@ -1,12 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"lib": ["ES2020", "dom"],
"outDir": "out",
"rootDir": "src",
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}

View File

@ -22,7 +22,7 @@
"onCustomEditor:squiggle.wysiwyg", "onCustomEditor:squiggle.wysiwyg",
"onCommand:squiggle.preview" "onCommand:squiggle.preview"
], ],
"main": "./client/out/extension.js", "main": "./dist/src/client/extension.js",
"contributes": { "contributes": {
"languages": [ "languages": [
{ {
@ -114,30 +114,32 @@
} }
}, },
"scripts": { "scripts": {
"vscode:prepublish": "yarn run compile", "vscode:prepublish": "yarn run lint && yarn run build",
"compile:tsc": "tsc -b", "build:grammar": "js-yaml syntaxes/squiggle.tmLanguage.yaml >syntaxes/squiggle.tmLanguage.json",
"compile:grammar": "js-yaml syntaxes/squiggle.tmLanguage.yaml >syntaxes/squiggle.tmLanguage.json", "build:vendor": "mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/squiggle-logo.png media/vendor/icon.png",
"compile:vendor": "(cd ../squiggle-lang && yarn run build) && (cd ../components && yarn run bundle && yarn run build:css) && mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/squiggle-logo.png media/vendor/icon.png", "build:bundle": "esbuild ./src/client/extension.ts ./src/server/server.ts --format=cjs --platform=node --sourcemap --minify --bundle --external:vscode --outdir=./dist",
"compile": "yarn run compile:vendor && yarn run compile:grammar && yarn run compile:tsc", "build": "yarn run build:vendor && yarn run build:grammar && yarn run build:bundle",
"watch": "tsc -b -watch", "watch": "yarn run compile:bundle --watch",
"pretest": "yarn run compile && yarn run lint", "pretest": "yarn run build && yarn run lint",
"lint": "prettier --check .", "lint": "tsc -noEmit && prettier --check .",
"format": "prettier --write .", "format": "prettier --write .",
"package": "npx vsce package --yarn" "package": "vsce package --yarn"
}, },
"devDependencies": { "devDependencies": {
"@types/glob": "^8.0.0", "@types/glob": "^8.0.0",
"@types/node": "18.x", "@types/node": "18.x",
"@types/vscode": "^1.70.0", "@types/vscode": "^1.70.0",
"esbuild": "^0.15.10",
"glob": "^8.0.3", "glob": "^8.0.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"vsce-yarn-patch": "^1.66.2" "vsce": "^2.11.0"
}, },
"dependencies": { "dependencies": {
"vscode-languageclient": "^8.0.2", "vscode-languageclient": "^8.0.2",
"vscode-languageserver": "^8.0.2", "vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-textdocument": "^1.0.7",
"@quri/squiggle-lang": "^0.2.11" "@quri/squiggle-lang": "^0.2.11",
"@quri/squiggle-components": "*"
} }
} }

View File

@ -1,12 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"lib": ["ES2020", "dom"],
"outDir": "out",
"rootDir": "src",
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules"]
}

View File

@ -13,7 +13,7 @@ let client: LanguageClient;
export const startClient = (context: vscode.ExtensionContext) => { export const startClient = (context: vscode.ExtensionContext) => {
// The server is implemented in node // The server is implemented in node
let serverModule = context.asAbsolutePath( let serverModule = context.asAbsolutePath(
path.join("server", "out", "server.js") path.join("dist", "src", "server", "server.js")
); );
// The debug options for the server // The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging

View File

@ -7,7 +7,6 @@
"rootDir": "src", "rootDir": "src",
"sourceMap": true "sourceMap": true
}, },
"include": ["src"], "include": ["src/**/*"],
"exclude": ["node_modules", ".vscode-test"], "exclude": ["node_modules", ".vscode-test"]
"references": [{ "path": "./client" }, { "path": "./server" }]
} }

View File

@ -181,7 +181,7 @@ The `sample(distribution)` samples a given distribution.
## Converting between distribution formats ## Converting between distribution formats
Recall the [three formats of distributions](https://develop--squiggle-documentation.netlify.app/docs/Discussions/Three-Types-Of-Distributions). We can force any distribution into `SampleSet` format Recall the [three formats of distributions](/docs/Discussions/Three-Formats-Of-Distributions). We can force any distribution into `SampleSet` format
<SquiggleEditor defaultCode="SampleSet.fromDist(normal(5, 10))" /> <SquiggleEditor defaultCode="SampleSet.fromDist(normal(5, 10))" />

Some files were not shown because too many files have changed in this diff Show More