Merge pull request #1190 from quantified-uncertainty/develop

0.5.0
This commit is contained in:
Vyacheslav Matyukhin 2022-10-02 14:39:11 +03:00 committed by GitHub
commit b1639bf62c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
159 changed files with 3695 additions and 5912 deletions

2
.github/CODEOWNERS vendored
View File

@ -20,7 +20,7 @@
*.js @Hazelfire @berekuk @OAGr *.js @Hazelfire @berekuk @OAGr
# Any opsy files # Any opsy files
.github/** @quinn-doughert @berekuky @OAGr .github/** @quinn-dougherty @berekuk @OAGr
*.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

View File

@ -11,6 +11,7 @@ on:
- develop - develop
- reducer-dev - reducer-dev
- epic-reducer-project - epic-reducer-project
- epic-0.5.0
jobs: jobs:
pre_check: pre_check:
@ -25,27 +26,27 @@ jobs:
steps: steps:
- id: skip_lang_check - id: skip_lang_check
name: Check if the changes are about squiggle-lang src files name: Check if the changes are about squiggle-lang src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/squiggle-lang/**"]' paths: '["packages/squiggle-lang/**"]'
- id: skip_components_check - id: skip_components_check
name: Check if the changes are about components src files name: Check if the changes are about components src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/components/**"]' paths: '["packages/components/**"]'
- id: skip_website_check - id: skip_website_check
name: Check if the changes are about website src files name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/website/**"]' paths: '["packages/website/**"]'
- id: skip_vscodeext_check - id: skip_vscodeext_check
name: Check if the changes are about vscode extension src files name: Check if the changes are about vscode extension src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/vscode-ext/**"]' paths: '["packages/vscode-ext/**"]'
- id: skip_cli_check - id: skip_cli_check
name: Check if the changes are about cli src files name: Check if the changes are about cli src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/cli/**"]' paths: '["packages/cli/**"]'

View File

@ -18,27 +18,27 @@ jobs:
steps: steps:
- id: skip_lang_check - id: skip_lang_check
name: Check if the changes are about squiggle-lang src files name: Check if the changes are about squiggle-lang src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/squiggle-lang/**"]' paths: '["packages/squiggle-lang/**"]'
- id: skip_components_check - id: skip_components_check
name: Check if the changes are about components src files name: Check if the changes are about components src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/components/**"]' paths: '["packages/components/**"]'
- id: skip_website_check - id: skip_website_check
name: Check if the changes are about website src files name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/website/**"]' paths: '["packages/website/**"]'
- id: skip_vscodeext_check - id: skip_vscodeext_check
name: Check if the changes are about vscode extension src files name: Check if the changes are about vscode extension src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/vscode-ext/**"]' paths: '["packages/vscode-ext/**"]'
- id: skip_cli_check - id: skip_cli_check
name: Check if the changes are about cli src files name: Check if the changes are about cli src files
uses: fkirc/skip-duplicate-actions@v4.0.0 uses: fkirc/skip-duplicate-actions@v5.1.0
with: with:
paths: '["packages/cli/**"]' paths: '["packages/cli/**"]'

View File

@ -1,46 +1,46 @@
{ {
"name": "@quri/squiggle-components", "name": "@quri/squiggle-components",
"version": "0.4.2", "version": "0.5.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "^1.0.0", "@floating-ui/react-dom": "^1.0.0",
"@floating-ui/react-dom-interactions": "^0.9.3", "@floating-ui/react-dom-interactions": "^0.10.1",
"@headlessui/react": "^1.6.6", "@headlessui/react": "^1.7.2",
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^2.9.8", "@hookform/resolvers": "^2.9.8",
"@quri/squiggle-lang": "^0.4.2", "@quri/squiggle-lang": "^0.5.0",
"@react-hook/size": "^2.1.2", "@react-hook/size": "^2.1.2",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"framer-motion": "^7.3.2", "framer-motion": "^7.4.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^18.1.0", "react": "^18.1.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-hook-form": "^7.35.0", "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",
"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",
"vscode-uri": "^3.0.3", "vscode-uri": "^3.0.6",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.18.6",
"@storybook/addon-actions": "^6.5.9", "@storybook/addon-actions": "^6.5.12",
"@storybook/addon-essentials": "^6.5.10", "@storybook/addon-essentials": "^6.5.12",
"@storybook/addon-links": "^6.5.10", "@storybook/addon-links": "^6.5.12",
"@storybook/builder-webpack5": "^6.5.10", "@storybook/builder-webpack5": "^6.5.12",
"@storybook/manager-webpack5": "^6.5.10", "@storybook/manager-webpack5": "^6.5.12",
"@storybook/node-logger": "^6.5.9", "@storybook/node-logger": "^6.5.9",
"@storybook/preset-create-react-app": "^4.1.2", "@storybook/preset-create-react-app": "^4.1.2",
"@storybook/react": "^6.5.10", "@storybook/react": "^6.5.12",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"@types/jest": "^27.5.0", "@types/jest": "^27.5.0",
"@types/lodash": "^4.14.185", "@types/lodash": "^4.14.185",
"@types/node": "^18.7.16", "@types/node": "^18.7.22",
"@types/react": "^18.0.18", "@types/react": "^18.0.21",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -48,18 +48,18 @@
"postcss-cli": "^10.0.0", "postcss-cli": "^10.0.0",
"postcss-import": "^15.0.0", "postcss-import": "^15.0.0",
"postcss-loader": "^7.0.1", "postcss-loader": "^7.0.1",
"postcss-nesting": "^10.1.10", "postcss-nesting": "^10.2.0",
"react": "^18.1.0", "react": "^18.1.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"tailwindcss": "^3.1.8", "tailwindcss": "^3.1.8",
"ts-loader": "^9.3.0", "ts-loader": "^9.4.1",
"tsconfig-paths-webpack-plugin": "^4.0.0", "tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.8.3", "typescript": "^4.8.3",
"web-vitals": "^3.0.1", "web-vitals": "^3.0.2",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.0" "webpack-dev-server": "^4.11.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17 || ^18", "react": "^16.8.0 || ^17 || ^18",
@ -67,7 +67,7 @@
}, },
"scripts": { "scripts": {
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public", "start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
"build:cjs": "rm -rf dist/src && tsc -b", "build:cjs": "rm -rf dist/src && rm -f dist/tsconfig.tsbuildinfo && tsc -b",
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css", "build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
"build:storybook": "build-storybook -s public", "build:storybook": "build-storybook -s public",
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook", "build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",

View File

@ -135,29 +135,6 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
{() => value.value.toString()} {() => value.value.toString()}
</VariableBox> </VariableBox>
); );
case SqValueTag.Symbol:
return (
<VariableBox value={value} heading="Symbol">
{() => (
<>
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{value.value}</span>
</>
)}
</VariableBox>
);
case SqValueTag.Call:
return (
<VariableBox value={value} heading="Call">
{() => value.value}
</VariableBox>
);
case SqValueTag.ArrayString:
return (
<VariableBox value={value} heading="Array String">
{() => value.value.map((r) => `"${r}"`).join(", ")}
</VariableBox>
);
case SqValueTag.Date: case SqValueTag.Date:
return ( return (
<VariableBox value={value} heading="Date"> <VariableBox value={value} heading="Date">
@ -242,24 +219,6 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
</VariableBox> </VariableBox>
); );
} }
case SqValueTag.Module: {
return (
<VariableList value={value} heading="Module">
{(_) =>
value.value
.entries()
.filter(([key, _]) => !key.match(/^(__result__)$/))
.map(([key, r]) => (
<ExpressionViewer
key={key}
value={r}
width={width !== undefined ? width - 20 : width}
/>
))
}
</VariableList>
);
}
case SqValueTag.Record: case SqValueTag.Record:
const plot = makePlot(value.value); const plot = makePlot(value.value);
if (plot) { if (plot) {
@ -339,7 +298,9 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
{() => ( {() => (
<div> <div>
<span>No display for type: </span>{" "} <span>No display for type: </span>{" "}
<span className="font-semibold text-slate-600">{value.tag}</span> <span className="font-semibold text-slate-600">
{(value as { tag: string }).tag}
</span>
</div> </div>
)} )}
</VariableList> </VariableList>

View File

@ -1,5 +1,11 @@
import * as yup from "yup"; import * as yup from "yup";
import { SqDistribution, result, SqRecord } from "@quri/squiggle-lang"; import {
SqValue,
SqValueTag,
SqDistribution,
result,
SqRecord,
} from "@quri/squiggle-lang";
export type LabeledDistribution = { export type LabeledDistribution = {
name: string; name: string;
@ -21,48 +27,55 @@ function ok<a, b>(x: a): result<a, b> {
const schema = yup const schema = yup
.object() .object()
.strict()
.noUnknown() .noUnknown()
.strict()
.shape({ .shape({
distributions: yup.object().shape({ distributions: yup
tag: yup.mixed().oneOf(["array"]),
value: yup
.array() .array()
.required()
.of( .of(
yup.object().shape({ yup.object().required().shape({
tag: yup.mixed().oneOf(["record"]), name: yup.string().required(),
value: yup.object({ distribution: yup.mixed().required(),
name: yup.object().shape({
tag: yup.mixed().oneOf(["string"]),
value: yup.string().required(),
}),
// color: yup
// .object({
// tag: yup.mixed().oneOf(["string"]),
// value: yup.string().required(),
// })
// .default(undefined),
distribution: yup.object({
tag: yup.mixed().oneOf(["distribution"]),
value: yup.mixed(),
}),
}),
}) })
) ),
.required(),
}),
}); });
type JsonObject =
| string
| { [key: string]: JsonObject }
| JsonObject[]
| SqDistribution;
function toJson(val: SqValue): JsonObject {
if (val.tag === SqValueTag.String) {
return val.value;
} else if (val.tag === SqValueTag.Record) {
return toJsonRecord(val.value);
} else if (val.tag === SqValueTag.Array) {
return val.value.getValues().map(toJson);
} else if (val.tag === SqValueTag.Distribution) {
return val.value;
} else {
throw new Error("Could not parse object of type " + val.tag);
}
}
function toJsonRecord(val: SqRecord): JsonObject {
let recordObject: JsonObject = {};
val.entries().forEach(([key, value]) => (recordObject[key] = toJson(value)));
return recordObject;
}
export function parsePlot(record: SqRecord): result<Plot, string> { export function parsePlot(record: SqRecord): result<Plot, string> {
try { try {
const plotRecord = schema.validateSync(record); const plotRecord = schema.validateSync(toJsonRecord(record));
return ok({ if (plotRecord.distributions) {
distributions: plotRecord.distributions.value.map((x) => ({ return ok({ distributions: plotRecord.distributions.map((x) => x) });
name: x.value.name.value, } else {
// color: x.value.color?.value, // not supported yet // I have no idea why yup's typings thinks this is possible
distribution: x.value.distribution.value, return error("no distributions field. Should never get here");
})), }
});
} catch (e) { } catch (e) {
const message = e instanceof Error ? e.message : "Unknown error"; const message = e instanceof Error ? e.message : "Unknown error";
return error(message); return error(message);

View File

@ -1,51 +0,0 @@
import { SquigglePartial, SquiggleEditor } from "../components/SquiggleEditor";
import { useState } from "react";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/SquigglePartial" component={SquigglePartial} />
export const Template = (props) => <SquigglePartial {...props} />;
# Squiggle Partial
A Squiggle Partial is an editor that does not return a graph to the user, but
instead returns bindings that can be used by further Squiggle Editors.
<Canvas>
<Story
name="Standalone"
args={{
defaultCode: "x = normal(5,2)",
}}
>
{Template.bind({})}
</Story>
</Canvas>
<Canvas>
<Story
name="With Editor"
args={{
initialPartialString: "x = normal(5,2)",
initialEditorString: "x",
}}
>
{(props) => {
let [bindings, setBindings] = useState({});
return (
<>
<SquigglePartial
{...props}
defaultCode={props.initialPartialString}
onChange={setBindings}
/>
<SquiggleEditor
{...props}
defaultCode={props.initialEditorString}
bindings={bindings}
/>
</>
);
}}
</Story>
</Canvas>

View File

@ -9,22 +9,28 @@ let prepareInputs = (ar, minWeight) =>
describe("Continuous and discrete splits", () => { describe("Continuous and discrete splits", () => {
makeTest( makeTest(
"is empty, with no common elements", "is empty, with no common elements",
prepareInputs([1.432, 1.33455, 2.0], 2), prepareInputs([1.33455, 1.432, 2.0], 2),
([1.33455, 1.432, 2.0], []), ([1.33455, 1.432, 2.0], []),
) )
makeTest( makeTest(
"only stores 3.5 as discrete when minWeight is 3", "only stores 3.5 as discrete when minWeight is 3",
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3), prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]), ([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
) )
makeTest( makeTest(
"doesn't store 3.5 as discrete when minWeight is 5", "doesn't store 3.5 as discrete when minWeight is 5",
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5), prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []), ([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []),
) )
makeTest(
"more general test",
prepareInputs([10., 10., 11., 11., 11., 12., 13., 13., 13., 13., 13., 14.], 3),
([10., 10., 12., 14.], [(11., 3.), (13., 5.)]),
)
let makeDuplicatedArray = count => { let makeDuplicatedArray = count => {
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int) let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare) let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)

View File

@ -1,20 +0,0 @@
open Jest
open Expect
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1)->toEqual(item2))
: test(str, () => expect(item1)->toEqual(item2))
describe("Lodash", () =>
describe("Lodash", () => {
makeTest("min", Lodash.min([1, 3, 4]), 1)
makeTest("max", Lodash.max([1, 3, 4]), 4)
makeTest("uniq", Lodash.uniq([1, 3, 4, 4]), [1, 3, 4])
makeTest(
"countBy",
Lodash.countBy([1, 3, 4, 4], r => r),
Js.Dict.fromArray([("1", 1), ("3", 1), ("4", 2)]),
)
})
)

View File

@ -1,27 +1,44 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
module Namespace = Reducer_Namespace
open Jest open Jest
open Expect open Expect
open Expect.Operators open Expect.Operators
describe("Name Space", () => { describe("Bindings", () => {
let value = InternalExpressionValue.IEvNumber(1967.0) let value = Reducer_T.IEvNumber(1967.0)
let nameSpace = Bindings.emptyNameSpace->Bindings.set("value", value) let bindings = Bindings.make()->Bindings.set("value", value)
test("get", () => { test("get", () => {
expect(Bindings.get(nameSpace, "value")) == Some(value) expect(bindings->Bindings.get("value")) == Some(value)
}) })
test("chain and get", () => { test("get nonexisting value", () => {
let mainNameSpace = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace]) expect(bindings->Bindings.get("nosuchvalue")) == None
expect(Bindings.get(mainNameSpace, "value")) == Some(value)
}) })
test("chain and set", () => { test("get on extended", () => {
let mainNameSpace0 = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace]) expect(bindings->Bindings.extend->Bindings.get("value")) == Some(value)
let mainNameSpace = })
mainNameSpace0->Bindings.set("value", InternalExpressionValue.IEvNumber(1968.0))
expect(Bindings.get(mainNameSpace, "value")) == Some(InternalExpressionValue.IEvNumber(1968.0)) test("locals", () => {
expect(bindings->Bindings.locals->Namespace.get("value")) == Some(value)
})
test("locals on extendeed", () => {
expect(bindings->Bindings.extend->Bindings.locals->Namespace.get("value")) == None
})
describe("extend", () => {
let value2 = Reducer_T.IEvNumber(5.)
let extendedBindings = bindings->Bindings.extend->Bindings.set("value", value2)
test("get on extended", () => {
expect(extendedBindings->Bindings.get("value")) == Some(value2)
})
test("get on original", () => {
expect(bindings->Bindings.get("value")) == Some(value)
})
}) })
}) })

View File

@ -1,146 +0,0 @@
open Jest
// open Expect
open Reducer_Expression_ExpressionBuilder
open Reducer_TestMacroHelpers
module ExpressionT = Reducer_Expression_T
let exampleExpression = eNumber(1.)
let exampleExpressionY = eSymbol("y")
let exampleStatementY = eLetStatement("y", eNumber(1.))
let exampleStatementX = eLetStatement("y", eSymbol("x"))
let exampleStatementZ = eLetStatement("z", eSymbol("y"))
// If it is not a macro then it is not expanded
testMacro([], exampleExpression, "Ok(1)")
describe("bindStatement", () => {
// A statement is bound by the bindings created by the previous statement
testMacro(
[],
eBindStatement(eBindings([]), exampleStatementY),
"Ok((:$_setBindings_$ @{} :y 1) context: @{})",
)
// Then it answers the bindings for the next statement when reduced
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok(@{y: 1})")
// Now let's feed a binding to see what happens
testMacro(
[],
eBindStatement(eBindings([("x", IEvNumber(2.))]), exampleStatementX),
"Ok((:$_setBindings_$ @{x: 2} :y 2) context: @{x: 2})",
)
// An expression does not return a binding, thus error
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected")
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
testMacro(
[("z", IEvNumber(99.))],
eBindStatementDefault(exampleStatementY),
"Ok((:$_setBindings_$ @{z: 99} :y 1) context: @{z: 99})",
)
})
describe("bindExpression", () => {
// x is simply bound in the expression
testMacro(
[],
eBindExpression(eBindings([("x", IEvNumber(2.))]), eSymbol("x")),
"Ok(2 context: @{x: 2})",
)
// When an let statement is the end expression then bindings are returned
testMacro(
[],
eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
"Ok((:$_exportBindings_$ (:$_setBindings_$ @{x: 2} :y 1)) context: @{x: 2})",
)
// Now let's reduce that expression
testMacroEval(
[],
eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
"Ok(@{x: 2,y: 1})",
)
// When bindings are missing the context is injected. This must be the first and last statement of a block
testMacroEval(
[("z", IEvNumber(99.))],
eBindExpressionDefault(exampleStatementY),
"Ok(@{y: 1,z: 99})",
)
})
describe("block", () => {
// Block with a single expression
testMacro([], eBlock(list{exampleExpression}), "Ok((:$$_bindExpression_$$ 1))")
testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)")
// Block with a single statement
testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$_bindExpression_$$ (:$_let_$ :y 1)))")
testMacroEval([], eBlock(list{exampleStatementY}), "Ok(@{y: 1})")
// Block with a statement and an expression
testMacro(
[],
eBlock(list{exampleStatementY, exampleExpressionY}),
"Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) :y))",
)
testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)")
// Block with a statement and another statement
testMacro(
[],
eBlock(list{exampleStatementY, exampleStatementZ}),
"Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) (:$_let_$ :z :y)))",
)
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok(@{y: 1,z: 1})")
// Block inside a block
testMacro([], eBlock(list{eBlock(list{exampleExpression})}), "Ok((:$$_bindExpression_$$ {1}))")
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
// Block assigned to a variable
testMacro(
[],
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
"Ok((:$$_bindExpression_$$ (:$_let_$ :z {{:y}})))",
)
testMacroEval(
[],
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
"Ok(@{z: :y})",
)
// Empty block
testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error
// :$$_block_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)"
testMacro(
[],
eBlock(list{
eBlock(list{
eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
eSymbol("y"),
}),
}),
"Ok((:$$_bindExpression_$$ {(:$_let_$ :y (:add :x 1)); :y}))",
)
testMacroEval(
[("x", IEvNumber(1.))],
eBlock(list{
eBlock(list{
eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
eSymbol("y"),
}),
}),
"Ok(2)",
)
})
describe("lambda", () => {
// assign a lambda to a variable
let lambdaExpression = eFunction("$$_lambda_$$", list{eArrayString(["y"]), exampleExpressionY})
testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
// call a lambda
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
testMacro([], callLambdaExpression, "Ok(((:$$_lambda_$$ [y] :y) 1))")
testMacroEval([], callLambdaExpression, "Ok(1)")
// Parameters shadow the outer scope
testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(1)")
// When not shadowed by the parameters, the outer scope variables are available
let lambdaExpression = eFunction(
"$$_lambda_$$",
list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})},
)
let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)})
testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(667)")
})

View File

@ -1,41 +0,0 @@
module ExpressionValue = ReducerInterface.InternalExpressionValue
module Expression = Reducer_Expression
open Jest
open Expect
let expectEvalToBe = (sourceCode: string, answer: string) =>
Expression.BackCompatible.evaluateString(sourceCode)
->ExpressionValue.toStringResult
->expect
->toBe(answer)
let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
describe("builtin", () => {
// All MathJs operators and functions are available for string, number and boolean
// .e.g + - / * > >= < <= == /= not and or
// See https://mathjs.org/docs/expressions/syntax.html
// See https://mathjs.org/docs/reference/functions.html
testEval("-1", "Ok(-1)")
testEval("1-1", "Ok(0)")
testEval("2>1", "Ok(true)")
testEval("concat('a','b')", "Ok('ab')")
})
describe("builtin exception", () => {
//It's a pity that MathJs does not return error position
test("MathJs Exception", () =>
expectEvalToBe("testZadanga(1)", "Error(JS Exception: Error: Undefined function testZadanga)")
)
})
describe("error reporting from collection functions", () => {
testEval("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
testEval(
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
"Error(zarathsuzaWasHere is not defined)",
)
// FIXME: returns "Error(Function not found: map(Array,Symbol))"
// Actually this error is correct but not informative
})

View File

@ -1,17 +0,0 @@
// Reducer_Helpers
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Bindings = Reducer_Bindings
let removeDefaultsInternal = (iev: InternalExpressionValue.t) => {
switch iev {
| InternalExpressionValue.IEvBindings(nameSpace) =>
Bindings.removeOther(
nameSpace,
ReducerInterface.StdLib.internalStdLib,
)->InternalExpressionValue.IEvBindings
| value => value
}
}
let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal)

View File

@ -1,31 +0,0 @@
module MathJs = Reducer_MathJs
module ErrorValue = Reducer_ErrorValue
open Jest
open ExpectJs
describe("eval", () => {
test("Number", () => expect(MathJs.Eval.eval("1"))->toEqual(Ok(IEvNumber(1.))))
test("Number expr", () => expect(MathJs.Eval.eval("1-1"))->toEqual(Ok(IEvNumber(0.))))
test("String", () => expect(MathJs.Eval.eval("'hello'"))->toEqual(Ok(IEvString("hello"))))
test("String expr", () =>
expect(MathJs.Eval.eval("concat('hello ','world')"))->toEqual(Ok(IEvString("hello world")))
)
test("Boolean", () => expect(MathJs.Eval.eval("true"))->toEqual(Ok(IEvBool(true))))
test("Boolean expr", () => expect(MathJs.Eval.eval("2>1"))->toEqual(Ok(IEvBool(true))))
})
describe("errors", () => {
// All those errors propagete up and are returned by the resolver
test("unknown function", () =>
expect(MathJs.Eval.eval("testZadanga()"))->toEqual(
Error(ErrorValue.REJavaScriptExn(Some("Undefined function testZadanga"), Some("Error"))),
)
)
test("unknown answer type", () =>
expect(MathJs.Eval.eval("1+1i"))->toEqual(
Error(ErrorValue.RETodo("Unhandled MathJs literal type: object")),
)
)
})

View File

@ -0,0 +1,53 @@
@@warning("-44")
module Namespace = Reducer_Namespace
open Jest
open Expect
open Expect.Operators
let makeValue = (v: float) => v->Reducer_T.IEvNumber
describe("Namespace", () => {
let value = makeValue(5.)
let v2 = makeValue(2.)
let ns = Namespace.make()->Namespace.set("value", value)
test("get", () => {
expect(ns->Namespace.get("value")) == Some(value)
})
test("get nonexisting value", () => {
expect(ns->Namespace.get("nosuchvalue")) == None
})
test("set", () => {
let ns2 = ns->Namespace.set("v2", v2)
expect(ns2->Namespace.get("v2")) == Some(v2)
})
test("immutable", () => {
let _ = ns->Namespace.set("v2", Reducer_T.IEvNumber(2.))
expect(ns->Namespace.get("v2")) == None
})
describe("merge many", () => {
let x1 = makeValue(10.)
let x2 = makeValue(20.)
let x3 = makeValue(30.)
let x4 = makeValue(40.)
let ns1 = Namespace.make()->Namespace.set("x1", x1)->Namespace.set("x2", x2)
let ns2 = Namespace.make()->Namespace.set("x3", x3)->Namespace.set("x4", x4)
let nsMerged = Namespace.mergeMany([ns, ns1, ns2])
test("merge many 1", () => {
expect(nsMerged->Namespace.get("x1")) == Some(x1)
})
test("merge many 2", () => {
expect(nsMerged->Namespace.get("x4")) == Some(x4)
})
test("merge many 3", () => {
expect(nsMerged->Namespace.get("value")) == Some(value)
})
})
})

View File

@ -3,346 +3,231 @@ open Reducer_Peggy_TestHelpers
describe("Peggy parse", () => { describe("Peggy parse", () => {
describe("float", () => { describe("float", () => {
testParse("1.", "{(::$_endOfOuterBlock_$ () 1)}") testParse("1.", "{1}")
testParse("1.1", "{(::$_endOfOuterBlock_$ () 1.1)}") testParse("1.1", "{1.1}")
testParse(".1", "{(::$_endOfOuterBlock_$ () 0.1)}") testParse(".1", "{0.1}")
testParse("0.1", "{(::$_endOfOuterBlock_$ () 0.1)}") testParse("0.1", "{0.1}")
testParse("1e1", "{(::$_endOfOuterBlock_$ () 10)}") testParse("1e1", "{10}")
testParse("1e-1", "{(::$_endOfOuterBlock_$ () 0.1)}") testParse("1e-1", "{0.1}")
testParse(".1e1", "{(::$_endOfOuterBlock_$ () 1)}") testParse(".1e1", "{1}")
testParse("0.1e1", "{(::$_endOfOuterBlock_$ () 1)}") testParse("0.1e1", "{1}")
}) })
describe("literals operators parenthesis", () => { describe("literals operators parenthesis", () => {
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement testParse("1", "{1}")
testParse("1", "{(::$_endOfOuterBlock_$ () 1)}") testParse("'hello'", "{'hello'}")
testParse("'hello'", "{(::$_endOfOuterBlock_$ () 'hello')}") testParse("true", "{true}")
testParse("true", "{(::$_endOfOuterBlock_$ () true)}") testParse("1+2", "{(:add 1 2)}")
testParse("1+2", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") testParse("add(1,2)", "{(:add 1 2)}")
testParse("add(1,2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") testParse("(1)", "{1}")
testParse("(1)", "{(::$_endOfOuterBlock_$ () 1)}") testParse("(1+2)", "{(:add 1 2)}")
testParse("(1+2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}")
}) })
describe("unary", () => { describe("unary", () => {
testParse("-1", "{(::$_endOfOuterBlock_$ () (::unaryMinus 1))}") testParse("-1", "{(:unaryMinus 1)}")
testParse("!true", "{(::$_endOfOuterBlock_$ () (::not true))}") testParse("!true", "{(:not true)}")
testParse("1 + -1", "{(::$_endOfOuterBlock_$ () (::add 1 (::unaryMinus 1)))}") testParse("1 + -1", "{(:add 1 (:unaryMinus 1))}")
testParse("-a[0]", "{(::$_endOfOuterBlock_$ () (::unaryMinus (::$_atIndex_$ :a 0)))}") testParse("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}")
testParse("!a[0]", "{(::$_endOfOuterBlock_$ () (::not (::$_atIndex_$ :a 0)))}") testParse("!a[0]", "{(:not (:$_atIndex_$ :a 0))}")
}) })
describe("multiplicative", () => { describe("multiplicative", () => {
testParse("1 * 2", "{(::$_endOfOuterBlock_$ () (::multiply 1 2))}") testParse("1 * 2", "{(:multiply 1 2)}")
testParse("1 / 2", "{(::$_endOfOuterBlock_$ () (::divide 1 2))}") testParse("1 / 2", "{(:divide 1 2)}")
testParse("1 * 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::multiply 1 2) 3))}") testParse("1 * 2 * 3", "{(:multiply (:multiply 1 2) 3)}")
testParse("1 * 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::multiply 1 2) 3))}") testParse("1 * 2 / 3", "{(:divide (:multiply 1 2) 3)}")
testParse("1 / 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::divide 1 2) 3))}") testParse("1 / 2 * 3", "{(:multiply (:divide 1 2) 3)}")
testParse("1 / 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::divide 1 2) 3))}") testParse("1 / 2 / 3", "{(:divide (:divide 1 2) 3)}")
testParse( testParse("1 * 2 + 3 * 4", "{(:add (:multiply 1 2) (:multiply 3 4))}")
"1 * 2 + 3 * 4", testParse("1 * 2 - 3 * 4", "{(:subtract (:multiply 1 2) (:multiply 3 4))}")
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::multiply 3 4)))}", testParse("1 * 2 .+ 3 * 4", "{(:dotAdd (:multiply 1 2) (:multiply 3 4))}")
) testParse("1 * 2 .- 3 * 4", "{(:dotSubtract (:multiply 1 2) (:multiply 3 4))}")
testParse( testParse("1 * 2 + 3 .* 4", "{(:add (:multiply 1 2) (:dotMultiply 3 4))}")
"1 * 2 - 3 * 4", testParse("1 * 2 + 3 / 4", "{(:add (:multiply 1 2) (:divide 3 4))}")
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 4)))}", testParse("1 * 2 + 3 ./ 4", "{(:add (:multiply 1 2) (:dotDivide 3 4))}")
) testParse("1 * 2 - 3 .* 4", "{(:subtract (:multiply 1 2) (:dotMultiply 3 4))}")
testParse( testParse("1 * 2 - 3 / 4", "{(:subtract (:multiply 1 2) (:divide 3 4))}")
"1 * 2 .+ 3 * 4", testParse("1 * 2 - 3 ./ 4", "{(:subtract (:multiply 1 2) (:dotDivide 3 4))}")
"{(::$_endOfOuterBlock_$ () (::dotAdd (::multiply 1 2) (::multiply 3 4)))}", testParse("1 * 2 - 3 * 4^5", "{(:subtract (:multiply 1 2) (:multiply 3 (:pow 4 5)))}")
)
testParse(
"1 * 2 .- 3 * 4",
"{(::$_endOfOuterBlock_$ () (::dotSubtract (::multiply 1 2) (::multiply 3 4)))}",
)
testParse(
"1 * 2 + 3 .* 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::dotMultiply 3 4)))}",
)
testParse(
"1 * 2 + 3 / 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::divide 3 4)))}",
)
testParse(
"1 * 2 + 3 ./ 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::dotDivide 3 4)))}",
)
testParse(
"1 * 2 - 3 .* 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::dotMultiply 3 4)))}",
)
testParse(
"1 * 2 - 3 / 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::divide 3 4)))}",
)
testParse(
"1 * 2 - 3 ./ 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::dotDivide 3 4)))}",
)
testParse(
"1 * 2 - 3 * 4^5",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5))))}",
)
testParse( testParse(
"1 * 2 - 3 * 4^5^6", "1 * 2 - 3 * 4^5^6",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6))))}", "{(:subtract (:multiply 1 2) (:multiply 3 (:pow (:pow 4 5) 6)))}",
)
testParse(
"1 * -a[-2]",
"{(::$_endOfOuterBlock_$ () (::multiply 1 (::unaryMinus (::$_atIndex_$ :a (::unaryMinus 2)))))}",
) )
testParse("1 * -a[-2]", "{(:multiply 1 (:unaryMinus (:$_atIndex_$ :a (:unaryMinus 2))))}")
}) })
describe("multi-line", () => { describe("multi-line", () => {
testParse("x=1; 2", "{:x = {1}; (::$_endOfOuterBlock_$ () 2)}") testParse("x=1; 2", "{:x = {1}; 2}")
testParse("x=1; y=2", "{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}") testParse("x=1; y=2", "{:x = {1}; :y = {2}}")
}) })
describe("variables", () => { describe("variables", () => {
testParse("x = 1", "{:x = {1}; (::$_endOfOuterBlock_$ () ())}") testParse("x = 1", "{:x = {1}}")
testParse("x", "{(::$_endOfOuterBlock_$ () :x)}") testParse("x", "{:x}")
testParse("x = 1; x", "{:x = {1}; (::$_endOfOuterBlock_$ () :x)}") testParse("x = 1; x", "{:x = {1}; :x}")
}) })
describe("functions", () => { describe("functions", () => {
testParse("identity(x) = x", "{:identity = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions become lambda assignments testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments
testParse("identity(x)", "{(::$_endOfOuterBlock_$ () (::identity :x))}") testParse("identity(x)", "{(:identity :x)}")
}) })
describe("arrays", () => { describe("arrays", () => {
testParse("[]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$))}") testParse("[]", "{[]}")
testParse("[0, 1, 2]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ 0 1 2))}") testParse("[0, 1, 2]", "{[0; 1; 2]}")
testParse( testParse("['hello', 'world']", "{['hello'; 'world']}")
"['hello', 'world']", testParse("([0,1,2])[1]", "{(:$_atIndex_$ [0; 1; 2] 1)}")
"{(::$_endOfOuterBlock_$ () (::$_constructArray_$ 'hello' 'world'))}",
)
testParse(
"([0,1,2])[1]",
"{(::$_endOfOuterBlock_$ () (::$_atIndex_$ (::$_constructArray_$ 0 1 2) 1))}",
)
}) })
describe("records", () => { describe("records", () => {
testParse( testParse("{a: 1, b: 2}", "{{'a': 1, 'b': 2}}")
"{a: 1, b: 2}", testParse("{1+0: 1, 2+0: 2}", "{{(:add 1 0): 1, (:add 2 0): 2}}") // key can be any expression
"{(::$_endOfOuterBlock_$ () (::$_constructRecord_$ ('a': 1 'b': 2)))}", testParse("record.property", "{(:$_atIndex_$ :record 'property')}")
)
testParse(
"{1+0: 1, 2+0: 2}",
"{(::$_endOfOuterBlock_$ () (::$_constructRecord_$ ((::add 1 0): 1 (::add 2 0): 2)))}",
) // key can be any expression
testParse("record.property", "{(::$_endOfOuterBlock_$ () (::$_atIndex_$ :record 'property'))}")
}) })
describe("post operators", () => { describe("post operators", () => {
//function call, array and record access are post operators with higher priority than unary operators //function call, array and record access are post operators with higher priority than unary operators
testParse("a==!b(1)", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::b 1))))}") testParse("a==!b(1)", "{(:equal :a (:not (:b 1)))}")
testParse("a==!b[1]", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 1))))}") testParse("a==!b[1]", "{(:equal :a (:not (:$_atIndex_$ :b 1)))}")
testParse( testParse("a==!b.one", "{(:equal :a (:not (:$_atIndex_$ :b 'one')))}")
"a==!b.one",
"{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 'one'))))}",
)
}) })
describe("comments", () => { describe("comments", () => {
testParse("1 # This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}") testParse("1 # This is a line comment", "{1}")
testParse("1 // This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}") testParse("1 // This is a line comment", "{1}")
testParse("1 /* This is a multi line comment */", "{(::$_endOfOuterBlock_$ () 1)}") testParse("1 /* This is a multi line comment */", "{1}")
testParse("/* This is a multi line comment */ 1", "{(::$_endOfOuterBlock_$ () 1)}") testParse("/* This is a multi line comment */ 1", "{1}")
testParse( testParse(
` `
/* This is /* This is
a multi line a multi line
comment */ comment */
1`, 1`,
"{(::$_endOfOuterBlock_$ () 1)}", "{1}",
) )
}) })
describe("ternary operator", () => { describe("ternary operator", () => {
testParse("true ? 2 : 3", "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true 2 3))}") testParse("true ? 2 : 3", "{(::$$_ternary_$$ true 2 3)}")
testParse( testParse(
"false ? 2 : false ? 4 : 5", "false ? 2 : false ? 4 : 5",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5)))}", "{(::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5))}",
) // nested ternary ) // nested ternary
}) })
describe("if then else", () => { describe("if then else", () => {
testParse( testParse("if true then 2 else 3", "{(::$$_ternary_$$ true {2} {3})}")
"if true then 2 else 3", testParse("if false then {2} else {3}", "{(::$$_ternary_$$ false {2} {3})}")
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true {2} {3}))}",
)
testParse(
"if false then {2} else {3}",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} {3}))}",
)
testParse( testParse(
"if false then {2} else if false then {4} else {5}", "if false then {2} else if false then {4} else {5}",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5})))}", "{(::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5}))}",
) //nested if ) //nested if
}) })
describe("logical", () => { describe("logical", () => {
testParse("true || false", "{(::$_endOfOuterBlock_$ () (::or true false))}") testParse("true || false", "{(:or true false)}")
testParse("true && false", "{(::$_endOfOuterBlock_$ () (::and true false))}") testParse("true && false", "{(:and true false)}")
testParse("a * b + c", "{(::$_endOfOuterBlock_$ () (::add (::multiply :a :b) :c))}") // for comparison testParse("a * b + c", "{(:add (:multiply :a :b) :c)}") // for comparison
testParse("a && b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) :c))}") testParse("a && b || c", "{(:or (:and :a :b) :c)}")
testParse("a && b || c && d", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) (::and :c :d)))}") testParse("a && b || c && d", "{(:or (:and :a :b) (:and :c :d))}")
testParse("a && !b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::not :b)) :c))}") testParse("a && !b || c", "{(:or (:and :a (:not :b)) :c)}")
testParse("a && b==c || d", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::equal :b :c)) :d))}") testParse("a && b==c || d", "{(:or (:and :a (:equal :b :c)) :d)}")
testParse( testParse("a && b!=c || d", "{(:or (:and :a (:unequal :b :c)) :d)}")
"a && b!=c || d", testParse("a && !(b==c) || d", "{(:or (:and :a (:not (:equal :b :c))) :d)}")
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::unequal :b :c)) :d))}", testParse("a && b>=c || d", "{(:or (:and :a (:largerEq :b :c)) :d)}")
) testParse("a && !(b>=c) || d", "{(:or (:and :a (:not (:largerEq :b :c))) :d)}")
testParse( testParse("a && b<=c || d", "{(:or (:and :a (:smallerEq :b :c)) :d)}")
"a && !(b==c) || d", testParse("a && b>c || d", "{(:or (:and :a (:larger :b :c)) :d)}")
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::not (::equal :b :c))) :d))}", testParse("a && b<c || d", "{(:or (:and :a (:smaller :b :c)) :d)}")
) testParse("a && b<c[i] || d", "{(:or (:and :a (:smaller :b (:$_atIndex_$ :c :i))) :d)}")
testParse( testParse("a && b<c.i || d", "{(:or (:and :a (:smaller :b (:$_atIndex_$ :c 'i'))) :d)}")
"a && b>=c || d", testParse("a && b<c(i) || d", "{(:or (:and :a (:smaller :b (:c :i))) :d)}")
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::largerEq :b :c)) :d))}", testParse("a && b<1+2 || d", "{(:or (:and :a (:smaller :b (:add 1 2))) :d)}")
) testParse("a && b<1+2*3 || d", "{(:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d)}")
testParse(
"a && !(b>=c) || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::not (::largerEq :b :c))) :d))}",
)
testParse(
"a && b<=c || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smallerEq :b :c)) :d))}",
)
testParse("a && b>c || d", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::larger :b :c)) :d))}")
testParse(
"a && b<c || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b :c)) :d))}",
)
testParse(
"a && b<c[i] || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::$_atIndex_$ :c :i))) :d))}",
)
testParse(
"a && b<c.i || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::$_atIndex_$ :c 'i'))) :d))}",
)
testParse(
"a && b<c(i) || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::c :i))) :d))}",
)
testParse(
"a && b<1+2 || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add 1 2))) :d))}",
)
testParse(
"a && b<1+2*3 || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d))}",
)
testParse( testParse(
"a && b<1+2*-3+4 || d", "a && b<1+2*-3+4 || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4))) :d))}", "{(:or (:and :a (:smaller :b (:add (:add 1 (:multiply 2 (:unaryMinus 3))) 4))) :d)}",
) )
testParse( testParse(
"a && b<1+2*3 || d ? true : false", "a && b<1+2*3 || d ? true : false",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d) true false))}", "{(::$$_ternary_$$ (:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d) true false)}",
) )
}) })
describe("pipe", () => { describe("pipe", () => {
testParse("1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") testParse("1 -> add(2)", "{(:add 1 2)}")
testParse("-1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus 1) 2))}") testParse("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}")
testParse( testParse("-a[1] -> add(2)", "{(:add (:unaryMinus (:$_atIndex_$ :a 1)) 2)}")
"-a[1] -> add(2)", testParse("-f(1) -> add(2)", "{(:add (:unaryMinus (:f 1)) 2)}")
"{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::$_atIndex_$ :a 1)) 2))}", testParse("1 + 2 -> add(3)", "{(:add 1 (:add 2 3))}")
) testParse("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}")
testParse("-f(1) -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::f 1)) 2))}") testParse("1 -> subtract(2)", "{(:subtract 1 2)}")
testParse("1 + 2 -> add(3)", "{(::$_endOfOuterBlock_$ () (::add 1 (::add 2 3)))}") testParse("-1 -> subtract(2)", "{(:subtract (:unaryMinus 1) 2)}")
testParse("1 -> add(2) * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::add 1 2) 3))}") testParse("1 -> subtract(2) * 3", "{(:multiply (:subtract 1 2) 3)}")
testParse("1 -> subtract(2)", "{(::$_endOfOuterBlock_$ () (::subtract 1 2))}")
testParse("-1 -> subtract(2)", "{(::$_endOfOuterBlock_$ () (::subtract (::unaryMinus 1) 2))}")
testParse(
"1 -> subtract(2) * 3",
"{(::$_endOfOuterBlock_$ () (::multiply (::subtract 1 2) 3))}",
)
}) })
describe("elixir pipe", () => { describe("elixir pipe", () => {
//handled together with -> so there is no need for seperate tests //handled together with -> so there is no need for seperate tests
testParse("1 |> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") testParse("1 |> add(2)", "{(:add 1 2)}")
}) })
describe("to", () => { describe("to", () => {
testParse("1 to 2", "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution 1 2))}") testParse("1 to 2", "{(:credibleIntervalToDistribution 1 2)}")
testParse( testParse("-1 to -2", "{(:credibleIntervalToDistribution (:unaryMinus 1) (:unaryMinus 2))}") // lower than unary
"-1 to -2",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2)))}",
) // lower than unary
testParse( testParse(
"a[1] to a[2]", "a[1] to a[2]",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2)))}", "{(:credibleIntervalToDistribution (:$_atIndex_$ :a 1) (:$_atIndex_$ :a 2))}",
) // lower than post ) // lower than post
testParse( testParse(
"a.p1 to a.p2", "a.p1 to a.p2",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2')))}", "{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}",
) // lower than post ) // lower than post
testParse( testParse("1 to 2 + 3", "{(:add (:credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators
"1 to 2 + 3",
"{(::$_endOfOuterBlock_$ () (::add (::credibleIntervalToDistribution 1 2) 3))}",
) // higher than binary operators
testParse( testParse(
"1->add(2) to 3->add(4) -> add(4)", "1->add(2) to 3->add(4) -> add(4)",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4)))}", "{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}",
) // lower than chain ) // lower than chain
}) })
describe("inner block", () => { describe("inner block", () => {
// inner blocks are 0 argument lambdas. They can be used whenever a value is required. // inner blocks are 0 argument lambdas. They can be used whenever a value is required.
// Like lambdas they have a local scope. // Like lambdas they have a local scope.
testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; (::$_endOfOuterBlock_$ () :x)}") testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; :x}")
}) })
describe("lambda", () => { describe("lambda", () => {
testParse("{|x| x}", "{(::$_endOfOuterBlock_$ () {|:x| {:x}})}") testParse("{|x| x}", "{{|:x| :x}}")
testParse("f={|x| x}", "{:f = {{|:x| {:x}}}; (::$_endOfOuterBlock_$ () ())}") testParse("f={|x| x}", "{:f = {{|:x| :x}}}")
testParse("f(x)=x", "{:f = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions are lambda assignments testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments
testParse( testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments
"f(x)=x ? 1 : 0",
"{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}; (::$_endOfOuterBlock_$ () ())}",
) // Function definitions are lambda assignments
}) })
describe("Using lambda as value", () => { describe("Using lambda as value", () => {
testParse( testParse(
"myadd(x,y)=x+y; z=myadd; z", "myadd(x,y)=x+y; z=myadd; z",
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; (::$_endOfOuterBlock_$ () :z)}", "{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {:myadd}; :z}",
) )
testParse( testParse(
"myadd(x,y)=x+y; z=[myadd]; z", "myadd(x,y)=x+y; z=[myadd]; z",
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ :myadd)}; (::$_endOfOuterBlock_$ () :z)}", "{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {[:myadd]}; :z}",
) )
testParse( testParse(
"myaddd(x,y)=x+y; z={x: myaddd}; z", "myaddd(x,y)=x+y; z={x: myaddd}; z",
"{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; (::$_endOfOuterBlock_$ () :z)}", "{:myaddd = {|:x,:y| {(:add :x :y)}}; :z = {{'x': :myaddd}}; :z}",
)
testParse("f({|x| x+1})", "{(::$_endOfOuterBlock_$ () (::f {|:x| {(::add :x 1)}}))}")
testParse(
"map(arr, {|x| x+1})",
"{(::$_endOfOuterBlock_$ () (::map :arr {|:x| {(::add :x 1)}}))}",
)
testParse(
"map([1,2,3], {|x| x+1})",
"{(::$_endOfOuterBlock_$ () (::map (::$_constructArray_$ 1 2 3) {|:x| {(::add :x 1)}}))}",
)
testParse(
"[1,2,3]->map({|x| x+1})",
"{(::$_endOfOuterBlock_$ () (::map (::$_constructArray_$ 1 2 3) {|:x| {(::add :x 1)}}))}",
) )
testParse("f({|x| x+1})", "{(:f {|:x| (:add :x 1)})}")
testParse("map(arr, {|x| x+1})", "{(:map :arr {|:x| (:add :x 1)})}")
testParse("map([1,2,3], {|x| x+1})", "{(:map [1; 2; 3] {|:x| (:add :x 1)})}")
testParse("[1,2,3]->map({|x| x+1})", "{(:map [1; 2; 3] {|:x| (:add :x 1)})}")
}) })
describe("unit", () => { describe("unit", () => {
testParse("1m", "{(::$_endOfOuterBlock_$ () (::fromUnit_m 1))}") testParse("1m", "{(:fromUnit_m 1)}")
testParse("1M", "{(::$_endOfOuterBlock_$ () (::fromUnit_M 1))}") testParse("1M", "{(:fromUnit_M 1)}")
testParse("1m+2cm", "{(::$_endOfOuterBlock_$ () (::add (::fromUnit_m 1) (::fromUnit_cm 2)))}") testParse("1m+2cm", "{(:add (:fromUnit_m 1) (:fromUnit_cm 2))}")
}) })
describe("Module", () => { describe("Module", () => {
testParse("x", "{(::$_endOfOuterBlock_$ () :x)}") testParse("x", "{:x}")
testParse("Math.pi", "{(::$_endOfOuterBlock_$ () :Math.pi)}") testParse("Math.pi", "{:Math.pi}")
}) })
}) })
@ -351,19 +236,19 @@ describe("parsing new line", () => {
` `
a + a +
b`, b`,
"{(::$_endOfOuterBlock_$ () (::add :a :b))}", "{(:add :a :b)}",
) )
testParse( testParse(
` `
x= x=
1`, 1`,
"{:x = {1}; (::$_endOfOuterBlock_$ () ())}", "{:x = {1}}",
) )
testParse( testParse(
` `
x=1 x=1
y=2`, y=2`,
"{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}", "{:x = {1}; :y = {2}}",
) )
testParse( testParse(
` `
@ -371,7 +256,7 @@ describe("parsing new line", () => {
y=2; y=2;
y } y }
x`, x`,
"{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", "{:x = {:y = {2}; :y}; :x}",
) )
testParse( testParse(
` `
@ -379,7 +264,7 @@ describe("parsing new line", () => {
y=2 y=2
y } y }
x`, x`,
"{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", "{:x = {:y = {2}; :y}; :x}",
) )
testParse( testParse(
` `
@ -388,7 +273,7 @@ describe("parsing new line", () => {
y y
} }
x`, x`,
"{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", "{:x = {:y = {2}; :y}; :x}",
) )
testParse( testParse(
` `
@ -396,7 +281,7 @@ describe("parsing new line", () => {
y=2 y=2
z=3 z=3
`, `,
"{:x = {1}; :y = {2}; :z = {3}; (::$_endOfOuterBlock_$ () ())}", "{:x = {1}; :y = {2}; :z = {3}}",
) )
testParse( testParse(
` `
@ -407,7 +292,7 @@ describe("parsing new line", () => {
x+y+z x+y+z
} }
`, `,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; (::$_endOfOuterBlock_$ () ())}", "{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}}",
) )
testParse( testParse(
` `
@ -420,7 +305,7 @@ describe("parsing new line", () => {
g=f+4 g=f+4
g g
`, `,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () :g)}", "{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; :g}",
) )
testParse( testParse(
` `
@ -442,7 +327,7 @@ describe("parsing new line", () => {
p -> p ->
q q
`, `,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () (::q (::p (::h :g))))}", "{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; (:q (:p (:h :g)))}",
) )
testParse( testParse(
` `
@ -451,7 +336,7 @@ describe("parsing new line", () => {
c |> c |>
d d
`, `,
"{(::$_endOfOuterBlock_$ () (::d (::c (::b :a))))}", "{(:d (:c (:b :a)))}",
) )
testParse( testParse(
` `
@ -461,6 +346,6 @@ describe("parsing new line", () => {
d + d +
e e
`, `,
"{(::$_endOfOuterBlock_$ () (::add (::d (::c (::b :a))) :e))}", "{(:add (:d (:c (:b :a))) :e)}",
) )
}) })

View File

@ -1,6 +1,5 @@
module Expression = Reducer_Expression module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface.InternalExpressionValue
module Parse = Reducer_Peggy_Parse module Parse = Reducer_Peggy_Parse
module Result = Belt.Result module Result = Belt.Result
module ToExpression = Reducer_Peggy_ToExpression module ToExpression = Reducer_Peggy_ToExpression
@ -24,8 +23,7 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
let a2 = let a2 =
rExpr rExpr
->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr))
->Reducer_Helpers.rRemoveDefaultsInternal ->Reducer_Value.toStringResultOkless
->ExpressionValue.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v)) (a1, a2)->expect->toEqual((answer, v))
} }
} }

View File

@ -1,23 +1,12 @@
module Bindings = Reducer_Bindings
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest open Jest
open Reducer_Peggy_TestHelpers open Reducer_Peggy_TestHelpers
// Note: these tests aren't useful anymore since outer block macro got deleted.
// Probably can be removed or folded into other Peggy tests.
describe("Peggy Outer Block", () => { describe("Peggy Outer Block", () => {
testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) testToExpression("1", "1", ~v="1", ())
testToExpression("x=1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ~v="()", ()) testToExpression("x=1", "x = {1}", ~v="()", ())
testToExpression( testToExpression("x=1; y=2", "x = {1}; y = {2}", ~v="()", ())
"x=1; y=2", testToExpression("x=1; 2", "x = {1}; 2", ~v="2", ())
"{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}", testToExpression("x={a=1; a}; x", "x = {a = {1}; a}; x", ~v="1", ())
~v="()",
(),
)
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ())
testToExpression(
"x={a=1; a}; x",
"{(:$_let_$ :x {(:$_let_$ :a {1}); :a}); (:$_endOfOuterBlock_$ () :x)}",
~v="1",
(),
)
}) })

View File

@ -1,5 +1,4 @@
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest open Jest
open Reducer_Peggy_TestHelpers open Reducer_Peggy_TestHelpers
@ -7,160 +6,94 @@ open Reducer_Peggy_TestHelpers
describe("Peggy to Expression", () => { describe("Peggy to Expression", () => {
describe("literals operators parenthesis", () => { describe("literals operators parenthesis", () => {
// Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement
testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) testToExpression("1", "1", ~v="1", ())
testToExpression("'hello'", "{(:$_endOfOuterBlock_$ () 'hello')}", ~v="'hello'", ()) testToExpression("'hello'", "'hello'", ~v="'hello'", ())
testToExpression("true", "{(:$_endOfOuterBlock_$ () true)}", ~v="true", ()) testToExpression("true", "true", ~v="true", ())
testToExpression("1+2", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) testToExpression("1+2", "(add)(1, 2)", ~v="3", ())
testToExpression("add(1,2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) testToExpression("add(1,2)", "(add)(1, 2)", ~v="3", ())
testToExpression("(1)", "{(:$_endOfOuterBlock_$ () 1)}", ()) testToExpression("(1)", "1", ())
testToExpression("(1+2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ()) testToExpression("(1+2)", "(add)(1, 2)", ())
}) })
describe("unary", () => { describe("unary", () => {
testToExpression("-1", "{(:$_endOfOuterBlock_$ () (:unaryMinus 1))}", ~v="-1", ()) testToExpression("-1", "(unaryMinus)(1)", ~v="-1", ())
testToExpression("!true", "{(:$_endOfOuterBlock_$ () (:not true))}", ~v="false", ()) testToExpression("!true", "(not)(true)", ~v="false", ())
testToExpression("1 + -1", "{(:$_endOfOuterBlock_$ () (:add 1 (:unaryMinus 1)))}", ~v="0", ()) testToExpression("1 + -1", "(add)(1, (unaryMinus)(1))", ~v="0", ())
testToExpression("-a[0]", "{(:$_endOfOuterBlock_$ () (:unaryMinus (:$_atIndex_$ :a 0)))}", ()) testToExpression("-a[0]", "(unaryMinus)(($_atIndex_$)(a, 0))", ())
}) })
describe("multi-line", () => { describe("multi-line", () => {
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ()) testToExpression("x=1; 2", "x = {1}; 2", ~v="2", ())
testToExpression( testToExpression("x=1; y=2", "x = {1}; y = {2}", ())
"x=1; y=2",
"{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}",
(),
)
}) })
describe("variables", () => { describe("variables", () => {
testToExpression("x = 1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ()) testToExpression("x = 1", "x = {1}", ())
testToExpression("x", "{(:$_endOfOuterBlock_$ () :x)}", ~v="Error(x is not defined)", ()) //TODO: value should return error testToExpression("x", "x", ~v="Error(x is not defined)", ()) //TODO: value should return error
testToExpression("x = 1; x", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () :x)}", ~v="1", ()) testToExpression("x = 1; x", "x = {1}; x", ~v="1", ())
}) })
describe("functions", () => { describe("functions", () => {
testToExpression( testToExpression("identity(x) = x", "identity = {|x| {x}}", ()) // Function definitions become lambda assignments
"identity(x) = x", testToExpression("identity(x)", "(identity)(x)", ()) // Note value returns error properly
"{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}",
(),
) // Function definitions become lambda assignments
testToExpression("identity(x)", "{(:$_endOfOuterBlock_$ () (:identity :x))}", ()) // Note value returns error properly
testToExpression( testToExpression(
"f(x) = x> 2 ? 0 : 1; f(3)", "f(x) = x> 2 ? 0 : 1; f(3)",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:$_endOfOuterBlock_$ () (:f 3))}", "f = {|x| {(larger)(x, 2) ? (0) : (1)}}; (f)(3)",
~v="0", ~v="0",
(), (),
) )
}) })
describe("arrays", () => { describe("arrays", () => {
testToExpression("[]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$))}", ~v="[]", ()) testToExpression("[]", "[]", ~v="[]", ())
testToExpression( testToExpression("[0, 1, 2]", "[0, 1, 2]", ~v="[0,1,2]", ())
"[0, 1, 2]", testToExpression("['hello', 'world']", "['hello', 'world']", ~v="['hello','world']", ())
"{(:$_endOfOuterBlock_$ () (:$_constructArray_$ 0 1 2))}", testToExpression("([0,1,2])[1]", "($_atIndex_$)([0, 1, 2], 1)", ~v="1", ())
~v="[0,1,2]",
(),
)
testToExpression(
"['hello', 'world']",
"{(:$_endOfOuterBlock_$ () (:$_constructArray_$ 'hello' 'world'))}",
~v="['hello','world']",
(),
)
testToExpression(
"([0,1,2])[1]",
"{(:$_endOfOuterBlock_$ () (:$_atIndex_$ (:$_constructArray_$ 0 1 2) 1))}",
~v="1",
(),
)
}) })
describe("records", () => { describe("records", () => {
testToExpression( testToExpression("{a: 1, b: 2}", "{'a': 1, 'b': 2}", ~v="{a: 1,b: 2}", ())
"{a: 1, b: 2}", testToExpression("{1+0: 1, 2+0: 2}", "{(add)(1, 0): 1, (add)(2, 0): 2}", ()) // key can be any expression
"{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (('a' 1) ('b' 2))))}", testToExpression("record.property", "($_atIndex_$)(record, 'property')", ())
~v="{a: 1,b: 2}",
(),
)
testToExpression(
"{1+0: 1, 2+0: 2}",
"{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2))))}",
(),
) // key can be any expression
testToExpression(
"record.property",
"{(:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}",
(),
)
testToExpression( testToExpression(
"record={property: 1}; record.property", "record={property: 1}; record.property",
"{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}", "record = {{'property': 1}}; ($_atIndex_$)(record, 'property')",
~v="1", ~v="1",
(), (),
) )
}) })
describe("comments", () => { describe("comments", () => {
testToExpression("1 # This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) testToExpression("1 # This is a line comment", "1", ~v="1", ())
testToExpression("1 // This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) testToExpression("1 // This is a line comment", "1", ~v="1", ())
testToExpression( testToExpression("1 /* This is a multi line comment */", "1", ~v="1", ())
"1 /* This is a multi line comment */", testToExpression("/* This is a multi line comment */ 1", "1", ~v="1", ())
"{(:$_endOfOuterBlock_$ () 1)}",
~v="1",
(),
)
testToExpression(
"/* This is a multi line comment */ 1",
"{(:$_endOfOuterBlock_$ () 1)}",
~v="1",
(),
)
}) })
describe("ternary operator", () => { describe("ternary operator", () => {
testToExpression( testToExpression("true ? 1 : 0", "true ? (1) : (0)", ~v="1", ())
"true ? 1 : 0", testToExpression("false ? 1 : 0", "false ? (1) : (0)", ~v="0", ())
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 0))}", testToExpression("true ? 1 : false ? 2 : 0", "true ? (1) : (false ? (2) : (0))", ~v="1", ()) // nested ternary
~v="1", testToExpression("false ? 1 : false ? 2 : 0", "false ? (1) : (false ? (2) : (0))", ~v="0", ()) // nested ternary
(),
)
testToExpression(
"false ? 1 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 0))}",
~v="0",
(),
)
testToExpression(
"true ? 1 : false ? 2 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0)))}",
~v="1",
(),
) // nested ternary
testToExpression(
"false ? 1 : false ? 2 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0)))}",
~v="0",
(),
) // nested ternary
describe("ternary bindings", () => { describe("ternary bindings", () => {
testToExpression( testToExpression(
// expression binding // expression binding
"f(a) = a > 5 ? 1 : 0; f(6)", "f(a) = a > 5 ? 1 : 0; f(6)",
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:$_endOfOuterBlock_$ () (:f 6))}", "f = {|a| {(larger)(a, 5) ? (1) : (0)}}; (f)(6)",
~v="1", ~v="1",
(), (),
) )
testToExpression( testToExpression(
// when true binding // when true binding
"f(a) = a > 5 ? a : 0; f(6)", "f(a) = a > 5 ? a : 0; f(6)",
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:$_endOfOuterBlock_$ () (:f 6))}", "f = {|a| {(larger)(a, 5) ? (a) : (0)}}; (f)(6)",
~v="6", ~v="6",
(), (),
) )
testToExpression( testToExpression(
// when false binding // when false binding
"f(a) = a < 5 ? 1 : a; f(6)", "f(a) = a < 5 ? 1 : a; f(6)",
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:$_endOfOuterBlock_$ () (:f 6))}", "f = {|a| {(smaller)(a, 5) ? (1) : (a)}}; (f)(6)",
~v="6", ~v="6",
(), (),
) )
@ -168,41 +101,23 @@ describe("Peggy to Expression", () => {
}) })
describe("if then else", () => { describe("if then else", () => {
testToExpression( testToExpression("if true then 2 else 3", "true ? ({2}) : ({3})", ())
"if true then 2 else 3", testToExpression("if true then {2} else {3}", "true ? ({2}) : ({3})", ())
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}",
(),
)
testToExpression(
"if true then {2} else {3}",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}",
(),
)
testToExpression( testToExpression(
"if false then {2} else if false then {4} else {5}", "if false then {2} else if false then {4} else {5}",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5})))}", "false ? ({2}) : (false ? ({4}) : ({5}))",
(), (),
) //nested if ) //nested if
}) })
describe("pipe", () => { describe("pipe", () => {
testToExpression("1 -> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) testToExpression("1 -> add(2)", "(add)(1, 2)", ~v="3", ())
testToExpression( testToExpression("-1 -> add(2)", "(add)((unaryMinus)(1), 2)", ~v="1", ()) // note that unary has higher priority naturally
"-1 -> add(2)", testToExpression("1 -> add(2) * 3", "(multiply)((add)(1, 2), 3)", ~v="9", ())
"{(:$_endOfOuterBlock_$ () (:add (:unaryMinus 1) 2))}",
~v="1",
(),
) // note that unary has higher priority naturally
testToExpression(
"1 -> add(2) * 3",
"{(:$_endOfOuterBlock_$ () (:multiply (:add 1 2) 3))}",
~v="9",
(),
)
}) })
describe("elixir pipe", () => { describe("elixir pipe", () => {
testToExpression("1 |> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) testToExpression("1 |> add(2)", "(add)(1, 2)", ~v="3", ())
}) })
// see testParse for priorities of to and credibleIntervalToDistribution // see testParse for priorities of to and credibleIntervalToDistribution
@ -212,44 +127,28 @@ describe("Peggy to Expression", () => {
// Like lambdas they have a local scope. // Like lambdas they have a local scope.
testToExpression( testToExpression(
"y=99; x={y=1; y}", "y=99; x={y=1; y}",
"{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}", "y = {99}; x = {y = {1}; y}",
// "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}",
(), (),
) )
}) })
describe("lambda", () => { describe("lambda", () => {
testToExpression( testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ())
"{|x| x}", testToExpression("f={|x| x}", "f = {{|x| x}}", ())
"{(:$_endOfOuterBlock_$ () (:$$_lambda_$$ [x] {:x}))}", testToExpression("f(x)=x", "f = {|x| {x}}", ()) // Function definitions are lambda assignments
~v="lambda(x=>internal code)", testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ())
(),
)
testToExpression(
"f={|x| x}",
"{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})}); (:$_endOfOuterBlock_$ () ())}",
(),
)
testToExpression(
"f(x)=x",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}",
(),
) // Function definitions are lambda assignments
testToExpression(
"f(x)=x ? 1 : 0",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)})); (:$_endOfOuterBlock_$ () ())}",
(),
)
}) })
describe("module", () => { describe("module", () => {
// testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ()) // testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ())
// Only.test("stdlibrary", () => { // Only.test("stdlibrary", () => {
// ReducerInterface_StdLib.internalStdLib // SquiggleLibrary_StdLib.stdLib
// ->IEvBindings // ->IEvBindings
// ->InternalExpressionValue.toString // ->Reducer_Value.toString
// ->expect // ->expect
// ->toBe("") // ->toBe("")
// }) // })
testToExpression("Math.pi", "{(:$_endOfOuterBlock_$ () :Math.pi)}", ~v="3.141592653589793", ()) testToExpression("Math.pi", "Math.pi", ~v="3.141592653589793", ())
}) })
}) })

View File

@ -3,22 +3,17 @@ open Reducer_Peggy_TestHelpers
describe("Peggy void", () => { describe("Peggy void", () => {
//literal //literal
testToExpression("()", "{(:$_endOfOuterBlock_$ () ())}", ~v="()", ()) testToExpression("()", "()", ~v="()", ())
testToExpression( testToExpression(
"fn()=1", "fn()=1",
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () ())}", "fn = {|_| {1}}",
// ~v="@{fn: lambda(_=>internal code)}", // ~v="@{fn: lambda(_=>internal code)}",
(), (),
) )
testToExpression( testToExpression("fn()=1; fn()", "fn = {|_| {1}}; (fn)(())", ~v="1", ())
"fn()=1; fn()",
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () (:fn ()))}",
~v="1",
(),
)
testToExpression( testToExpression(
"fn(a)=(); call fn(1)", "fn(a)=(); call fn(1)",
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)}); (:$_endOfOuterBlock_$ () ())}", "fn = {|a| {()}}; _ = {(fn)(1)}",
// ~v="@{_: (),fn: lambda(a=>internal code)}", // ~v="@{_: (),fn: lambda(a=>internal code)}",
(), (),
) )

View File

@ -1,7 +1,6 @@
module ErrorValue = Reducer_ErrorValue module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
open Jest open Jest
open Expect open Expect
@ -9,7 +8,7 @@ open Expect
let unwrapRecord = rValue => let unwrapRecord = rValue =>
rValue->Belt.Result.flatMap(value => rValue->Belt.Result.flatMap(value =>
switch value { switch value {
| InternalExpressionValue.IEvRecord(aRecord) => Ok(aRecord) | Reducer_T.IEvRecord(aRecord) => Ok(aRecord)
| _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error | _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error
} }
) )
@ -18,15 +17,11 @@ let expectParseToBe = (code: string, answer: string) =>
Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer) Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer)
let expectEvalToBe = (code: string, answer: string) => let expectEvalToBe = (code: string, answer: string) =>
Expression.BackCompatible.evaluateString(code) Expression.BackCompatible.evaluateString(code)->Reducer_Value.toStringResult->expect->toBe(answer)
->Reducer_Helpers.rRemoveDefaultsInternal
->InternalExpressionValue.toStringResult
->expect
->toBe(answer)
let expectEvalError = (code: string) => let expectEvalError = (code: string) =>
Expression.BackCompatible.evaluateString(code) Expression.BackCompatible.evaluateString(code)
->InternalExpressionValue.toStringResult ->Reducer_Value.toStringResult
->expect ->expect
->toMatch("Error\(") ->toMatch("Error\(")

View File

@ -1,89 +0,0 @@
open Jest
open Expect
module Bindings = Reducer_Bindings
module BindingsReplacer = Reducer_Expression_BindingsReplacer
module Expression = Reducer_Expression
module ExpressionWithContext = Reducer_ExpressionWithContext
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Macro = Reducer_Expression_Macro
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Expression_T
let testMacro_ = (
tester,
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedCode: string,
) => {
let bindings = Bindings.fromArray(bindArray)
tester(expr->T.toString, () =>
expr
->Macro.expandMacroCallRs(
bindings,
ProjectAccessorsT.identityAccessors,
Expression.reduceExpressionInProject,
)
->ExpressionWithContext.toStringResult
->expect
->toEqual(expectedCode)
)
}
let testMacroEval_ = (
tester,
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => {
let bindings = Bindings.fromArray(bindArray)
tester(expr->T.toString, () =>
expr
->Macro.doMacroCall(
bindings,
ProjectAccessorsT.identityAccessors,
Expression.reduceExpressionInProject,
)
->Ok
->InternalExpressionValue.toStringResult
->expect
->toEqual(expectedValue)
)
}
let testMacro = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedExpr: string,
) => testMacro_(test, bindArray, expr, expectedExpr)
let testMacroEval = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => testMacroEval_(test, bindArray, expr, expectedValue)
module MySkip = {
let testMacro = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedExpr: string,
) => testMacro_(Skip.test, bindArray, expr, expectedExpr)
let testMacroEval = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => testMacroEval_(Skip.test, bindArray, expr, expectedValue)
}
module MyOnly = {
let testMacro = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedExpr: string,
) => testMacro_(Only.test, bindArray, expr, expectedExpr)
let testMacroEval = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => testMacroEval_(Only.test, bindArray, expr, expectedValue)
}

View File

@ -1,52 +0,0 @@
module Expression = Reducer_Expression
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeCompile = Reducer_Type_Compile
open Jest
open Expect
let myIevEval = (aTypeSourceCode: string) =>
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject)
let myIevEvalToString = (aTypeSourceCode: string) =>
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
let myIevExpectEqual = (aTypeSourceCode, answer) =>
expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer)
let myIevTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
let myTypeEval = (aTypeSourceCode: string) =>
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject)
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
let myTypeExpectEqual = (aTypeSourceCode, answer) =>
expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer)
let myTypeTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer))
// | ItTypeIdentifier(string)
myTypeTest(test, "number", "number")
myTypeTest(test, "(number)", "number")
// | ItModifiedType({modifiedType: iType})
myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})")
myTypeTest(test, "number<-min(0)", "number<-min(0)")
// | ItTypeOr({typeOr: array<iType>})
myTypeTest(test, "number | string", "(number | string)")
// | ItTypeFunction({inputs: array<iType>, output: iType})
myTypeTest(test, "number => number => number", "(number => number => number)")
// | ItTypeArray({element: iType})
myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})")
myTypeTest(test, "[number]", "[number]")
// | ItTypeTuple({elements: array<iType>})
myTypeTest(test, "[number, string]", "[number, string]")
// | ItTypeRecord({properties: Belt.Map.String.t<iType>})
myIevTest(
test,
"{age: number, name: string}",
"Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})",
)
myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}")

View File

@ -1,42 +0,0 @@
module Bindings = Reducer_Bindings
module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
open Jest
open Expect
let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpressionInProject
let rResult =
Expression.BackCompatible.parse(sourceCode)->Belt.Result.map(expr =>
reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors)
)
rResult->Belt.Result.flatMap(result =>
switch result {
| IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn)
| _ => Js.Exn.raiseError("Arguments has to be an array")
}
)
}
let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string =>
switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer)
let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer))
myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok")

View File

@ -1,73 +0,0 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
open Jest
open Expect
// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn).
// isTypeOfSourceCode is written to use strings instead of expression values.
let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpressionInProject
let rResult =
Expression.BackCompatible.parse(sourceCode)->Belt.Result.map(expr =>
reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors)
)
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
}
let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string =>
switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer)
let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer))
myTypeCheckTest(test, "number", "1", "Ok")
myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'")
myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3")
myTypeCheckTest(test, "string", "'a'", "Ok")
myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok")
myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok")
myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2")
myTypeCheckTest(
test,
"[number, string, string]",
"[1,'a']",
"Expected type: [number, string, string] but got: [1,'a']",
)
myTypeCheckTest(
test,
"[number, string]",
"[1,'a', 3]",
"Expected type: [number, string] but got: [1,'a',3]",
)
myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok")
myTypeCheckTest(
test,
"{age: number, name: string}",
"{age: 1, name: 'a', job: 'IT'}",
"Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}",
)
myTypeCheckTest(test, "number | string", "1", "Ok")
myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1")
myTypeCheckTest(test, "number<-min(10)", "10", "Ok")
myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0")
myTypeCheckTest(test, "any", "0", "Ok")
myTypeCheckTest(test, "any", "'a'", "Ok")

View File

@ -1,127 +0,0 @@
open Jest
open Expect
module DispatchT = Reducer_Dispatch_T
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module ProjectReducerFnT = ReducerProject_ReducerFn_T
module TypeChecker = Reducer_Type_TypeChecker
module TypeCompile = Reducer_Type_Compile
open ReducerInterface_InternalExpressionValue
type errorValue = Reducer_ErrorValue.errorValue
// Let's build a function to replace switch statements
// In dispatchChainPiece, we execute an return the result of execution if there is a type match.
// Otherwise we return None so that the call chain can continue.
// So we want to build a function like
// dispatchChainPiece = (call: functionCall, accessors): option<result<internalExpressionValue, errorValue>>
// Use accessors.environment to get the environment finally.
// Now lets make the dispatchChainPiece itself.
// Note that I am not passing the reducer to the dispatchChainPiece as an argument because it is in the context anyway.
// Keep in mind that reducerFn is necessary for map/reduce so dispatchChainPiece should have a reducerFn in context.
let makeMyDispatchChainPiece = (reducer: ProjectReducerFnT.t): DispatchT.dispatchChainPiece => {
// Let's have a pure implementations
module Implementation = {
let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b)
let arrayConcat = (
a: Js.Array2.t<internalExpressionValue>,
b: Js.Array2.t<internalExpressionValue>,
): Js.Array2.t<internalExpressionValue> => Js.Array2.concat(a, b)
let plot = _r => "yey, plotted"
}
let extractStringString = args =>
switch args {
| [IEvString(a), IEvString(b)] => (a, b)
| _ => raise(Reducer_Exception.ImpossibleException("extractStringString developer error"))
}
let extractArrayArray = args =>
switch args {
| [IEvArray(a), IEvArray(b)] => (a, b)
| _ => raise(Reducer_Exception.ImpossibleException("extractArrayArray developer error"))
}
// Let's bridge the pure implementation to expression values
module Bridge = {
let stringConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
let (a, b) = extractStringString(args)
Implementation.stringConcat(a, b)->IEvString->Ok
}
let arrayConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
let (a, b) = extractArrayArray(args)
Implementation.arrayConcat(a, b)->IEvArray->Ok
}
let plot: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
switch args {
// Just assume that we are doing the business of extracting and converting the deep record
| [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok
| _ => raise(Reducer_Exception.ImpossibleException("plot developer error"))
}
}
}
// concat functions are to illustrate polymoprhism. And the plot function is to illustrate complex types
let jumpTable = [
(
"concat",
TypeCompile.fromTypeExpressionExn("string=>string=>string", reducer),
Bridge.stringConcat,
),
(
"concat",
TypeCompile.fromTypeExpressionExn("[any]=>[any]=>[any]", reducer),
Bridge.arrayConcat,
),
(
"plot",
TypeCompile.fromTypeExpressionExn(
// Nested complex types are available
// records {property: type}
// arrays [type]
// tuples [type, type]
// <- type contracts are available naturally and they become part of dispatching
// Here we are not enumerating the possibilities because type checking has a dedicated test
"{title: string, line: {width: number, color: string}}=>string",
reducer,
),
Bridge.plot,
),
]
//Here we are creating a dispatchChainPiece function that will do the actual dispatch from the jumpTable
Reducer_Dispatch_ChainPiece.makeFromTypes(jumpTable)
}
// And finally, let's write a library dispatch for our external library
// Exactly the same as the one used in real life
let _dispatch = (
call: functionCall,
accessors: ProjectAccessorsT.t,
reducer: ProjectReducerFnT.t,
chain,
): result<internalExpressionValue, 'e> => {
let dispatchChainPiece = makeMyDispatchChainPiece(reducer)
dispatchChainPiece(call, accessors)->E.O2.defaultFn(() => chain(call, accessors, reducer))
}
// What is important about this implementation?
// A) Exactly the same function jump table can be used to create type guarded lambda functions
// Guarded lambda functions will be the basis of the next version of Squiggle
// B) Complicated recursive record types are not a problem.
describe("Type Dispatch", () => {
let reducerFn = Expression.reduceExpressionInProject
let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn)
test("stringConcat", () => {
let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")])
let result = dispatchChainPiece(call, ProjectAccessorsT.identityAccessors)
expect(result)->toEqual(Some(Ok(IEvString("helloworld"))))
})
})

View File

@ -0,0 +1,14 @@
open Jest
open Expect
describe("ExpressionValue", () => {
test("argsToString", () =>
expect([IEvNumber(1.), IEvString("a")]->Reducer_Value.argsToString)->toBe("1,'a'")
)
test("toStringFunctionCall", () =>
expect(("fn", [IEvNumber(1.), IEvString("a")])->Reducer_Value.toStringFunctionCall)->toBe(
"fn(1,'a')",
)
)
})

View File

@ -2,11 +2,11 @@ open Jest
open Reducer_Peggy_TestHelpers open Reducer_Peggy_TestHelpers
describe("Construct Array", () => { describe("Construct Array", () => {
testToExpression("[1,2]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ 1 2))}", ~v="[1,2]", ()) testToExpression("[1,2]", "[1, 2]", ~v="[1,2]", ())
testToExpression("[]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$))}", ~v="[]", ()) testToExpression("[]", "[]", ~v="[]", ())
testToExpression( testToExpression(
"f(x)=x; g(x)=x; [f, g]", "f(x)=x; g(x)=x; [f, g]",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_let_$ :g (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () (:$_constructArray_$ :f :g))}", "f = {|x| {x}}; g = {|x| {x}}; [f, g]",
~v="[lambda(x=>internal code),lambda(x=>internal code)]", ~v="[lambda(x=>internal code),lambda(x=>internal code)]",
(), (),
) )

View File

@ -2,17 +2,15 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("Parse function assignment", () => { describe("Parse function assignment", () => {
testParseToBe( testParseToBe("f(x)=x", "Ok(f = {|x| {x}})")
"f(x)=x", testParseToBe("f(x)=2*x", "Ok(f = {|x| {(multiply)(2, x)}})")
"Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())})",
)
testParseToBe(
"f(x)=2*x",
"Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)})); (:$_endOfOuterBlock_$ () ())})",
)
//MathJs does not allow blocks in function definitions //MathJs does not allow blocks in function definitions
}) })
describe("Evaluate function assignment", () => { describe("Evaluate function assignment", () => {
testEvalToBe("f(x)=x; f(1)", "Ok(1)") testEvalToBe("f(x)=x; f(1)", "Ok(1)")
}) })
describe("Shadowing", () => {
testEvalToBe("x = 5; f(y) = x*y; x = 6; f(2)", "Ok(10)")
})

View File

@ -34,7 +34,7 @@ describe("symbol not defined", () => {
testEvalToBe("f(x)=x(y); f(f)", "Error(y is not defined)") testEvalToBe("f(x)=x(y); f(f)", "Error(y is not defined)")
testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))") testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))")
testEvalToBe("f(x)=x(y); f(z)", "Error(z is not defined)") testEvalToBe("f(x)=x(y); f(z)", "Error(z is not defined)")
testEvalToBe("f(x)=x(y); f(2)", "Error(2 is not a function)") testEvalToBe("f(x)=x(y); f(2)", "Error(y is not defined)")
testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)") testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)")
}) })
@ -46,10 +46,7 @@ describe("call and bindings", () => {
testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)") testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)")
testEvalToBe("f(x)=x+1; y=f(1); z=f(1); z", "Ok(2)") testEvalToBe("f(x)=x+1; y=f(1); z=f(1); z", "Ok(2)")
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)") testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)")
testParseToBe( testParseToBe("f=99; g(x)=f; g(2)", "Ok(f = {99}; g = {|x| {f}}; (g)(2))")
"f=99; g(x)=f; g(2)",
"Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [x] {:f})); (:$_endOfOuterBlock_$ () (:g 2))})",
)
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)") testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)") testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)") testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)")

View File

@ -5,3 +5,7 @@ Skip.describe("map reduce (sam)", () => {
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???") testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???") testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???")
}) })
describe("map", () => {
testEvalToBe("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
})

View File

@ -2,10 +2,7 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("Parse ternary operator", () => { describe("Parse ternary operator", () => {
testParseToBe( testParseToBe("true ? 'YES' : 'NO'", "Ok(true ? ('YES') : ('NO'))")
"true ? 'YES' : 'NO'",
"Ok({(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 'YES' 'NO'))})",
)
}) })
describe("Evaluate ternary operator", () => { describe("Evaluate ternary operator", () => {

View File

@ -2,19 +2,29 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("eval", () => { describe("eval", () => {
// All MathJs operators and functions are builtin for string, float and boolean
// .e.g + - / * > >= < <= == /= not and or
// See https://mathjs.org/docs/reference/functions.html
describe("expressions", () => { describe("expressions", () => {
testEvalToBe("1", "Ok(1)") testEvalToBe("1", "Ok(1)")
testEvalToBe("-1", "Ok(-1)")
testEvalToBe("1-1", "Ok(0)")
testEvalToBe("1+2", "Ok(3)") testEvalToBe("1+2", "Ok(3)")
testEvalToBe("(1+2)*3", "Ok(9)") testEvalToBe("(1+2)*3", "Ok(9)")
testEvalToBe("2>1", "Ok(true)") testEvalToBe("2>1", "Ok(true)")
testEvalToBe("concat('a ', 'b')", "Ok('a b')") testEvalToBe("concat('a ', 'b')", "Ok('a b')")
testEvalToBe("concat([3,4], [5,6,7])", "Ok([3,4,5,6,7])")
testEvalToBe("log(10)", "Ok(2.302585092994046)") testEvalToBe("log(10)", "Ok(2.302585092994046)")
testEvalToBe("cos(10)", "Ok(-0.8390715290764524)") testEvalToBe("Math.cos(10)", "Ok(-0.8390715290764524)")
// TODO more built ins // TODO more built ins
}) })
describe("missing function", () => {
testEvalToBe("testZadanga(1)", "Error(testZadanga is not defined)")
testEvalToBe(
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
"Error(zarathsuzaWasHere is not defined)",
)
})
describe("arrays", () => { describe("arrays", () => {
test("empty array", () => expectEvalToBe("[]", "Ok([])")) test("empty array", () => expectEvalToBe("[]", "Ok([])"))
testEvalToBe("[1, 2, 3]", "Ok([1,2,3])") testEvalToBe("[1, 2, 3]", "Ok([1,2,3])")
@ -50,6 +60,10 @@ describe("eval", () => {
testEvalError("1; 1") testEvalError("1; 1")
testEvalToBe("x=1; x=1; x", "Ok(1)") testEvalToBe("x=1; x=1; x", "Ok(1)")
}) })
describe("blocks", () => {
testEvalToBe("x = { y = { z = 5; z * 2 }; y + 3 }; x", "Ok(13)")
})
}) })
describe("test exceptions", () => { describe("test exceptions", () => {

View File

@ -1,11 +0,0 @@
open ReducerInterface.InternalExpressionValue
open Jest
open Expect
describe("ExpressionValue", () => {
test("argsToString", () => expect([IEvNumber(1.), IEvString("a")]->argsToString)->toBe("1,'a'"))
test("toStringFunctionCall", () =>
expect(("fn", [IEvNumber(1.), IEvString("a")])->toStringFunctionCall)->toBe("fn(1,'a')")
)
})

View File

@ -1,7 +1,5 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
open Jest open Jest
open Expect open Expect
@ -30,12 +28,11 @@ x=1`,
| Error(error) => fail(error->Reducer_ErrorValue.errorToString) | Error(error) => fail(error->Reducer_ErrorValue.errorToString)
} }
}) })
let internalProject = project->Project.T.Private.castToInternalProject
test("past chain", () => { test("past chain", () => {
expect(Project.Private.getPastChain(internalProject, "main")) == ["common"] expect(project->Project.getPastChain("main")) == ["common"]
}) })
test("import as variables", () => { test("import as variables", () => {
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [] expect(project->Project.Private.getIncludesAsVariables("main")) == []
}) })
}) })
@ -67,20 +64,16 @@ x=1`,
} }
}) })
let internalProject = project->Project.T.Private.castToInternalProject
test("direct past chain", () => { test("direct past chain", () => {
expect(Project.Private.getPastChain(internalProject, "main")) == ["common"] expect(project->Project.Private.getPastChain("main")) == ["common"]
}) })
test("direct includes", () => { test("direct includes", () => {
expect(Project.Private.getDirectIncludes(internalProject, "main")) == ["common"] expect(project->Project.Private.getDirectIncludes("main")) == ["common"]
}) })
test("include as variables", () => { test("include as variables", () => {
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [ expect(project->Project.Private.getIncludesAsVariables("main")) == [("myVariable", "myModule")]
("myVariable", "myModule"),
]
}) })
}) })
@ -109,13 +102,10 @@ x=1`,
| Error(error) => fail(error->Reducer_ErrorValue.errorToString) | Error(error) => fail(error->Reducer_ErrorValue.errorToString)
} }
}) })
let internalProject = project->Project.T.Private.castToInternalProject
test("direct past chain", () => { test("direct past chain", () => {
expect(Project.getPastChain(project, "main")) == ["common", "common2"] expect(Project.getPastChain(project, "main")) == ["common", "common2"]
}) })
test("include as variables", () => { test("include as variables", () => {
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [ expect(project->Project.Private.getIncludesAsVariables("main")) == [("myVariable", "myModule")]
("myVariable", "myModule"),
]
}) })
}) })

View File

@ -1,5 +1,4 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
@ -7,29 +6,16 @@ open Jest
open Expect open Expect
open Expect.Operators open Expect.Operators
// test("", () => expect(1)->toBe(1))
let runFetchResult = (project, sourceId) => { let runFetchResult = (project, sourceId) => {
Project.run(project, sourceId) Project.run(project, sourceId)
Project.getResult(project, sourceId)->InternalExpressionValue.toStringResult Project.getResult(project, sourceId)->Reducer_Value.toStringResult
} }
let runFetchFlatBindings = (project, sourceId) => { let runFetchFlatBindings = (project, sourceId) => {
Project.run(project, sourceId) Project.run(project, sourceId)
Project.getBindings(project, sourceId) Project.getBindings(project, sourceId)->Reducer_Value.toStringRecord
->Bindings.removeResult
->InternalExpressionValue.toStringBindings
} }
test("setting continuation", () => {
let project = Project.createProject()
let privateProject = project->Project.T.Private.castToInternalProject
let sampleBindings = Bindings.emptyBindings->Bindings.set("test", IEvVoid)
Project.Private.setContinuation(privateProject, "main", sampleBindings)
let answer = Project.Private.getContinuation(privateProject, "main")
expect(answer)->toBe(sampleBindings)
})
test("test result true", () => { test("test result true", () => {
let project = Project.createProject() let project = Project.createProject()
Project.setSource(project, "main", "true") Project.setSource(project, "main", "true")
@ -51,7 +37,7 @@ test("test library", () => {
test("test bindings", () => { test("test bindings", () => {
let project = Project.createProject() let project = Project.createProject()
Project.setSource(project, "variables", "myVariable=666") Project.setSource(project, "variables", "myVariable=666")
runFetchFlatBindings(project, "variables")->expect->toBe("@{myVariable: 666}") runFetchFlatBindings(project, "variables")->expect->toBe("{myVariable: 666}")
}) })
describe("project1", () => { describe("project1", () => {
@ -59,7 +45,6 @@ describe("project1", () => {
Project.setSource(project, "first", "x=1") Project.setSource(project, "first", "x=1")
Project.setSource(project, "main", "x") Project.setSource(project, "main", "x")
Project.setContinues(project, "main", ["first"]) Project.setContinues(project, "main", ["first"])
let internalProject = project->Project.T.Private.castToInternalProject
test("runOrder", () => { test("runOrder", () => {
expect(Project.getRunOrder(project)) == ["first", "main"] expect(Project.getRunOrder(project)) == ["first", "main"]
@ -78,17 +63,17 @@ describe("project1", () => {
}) })
test("past chain first", () => { test("past chain first", () => {
expect(Project.Private.getPastChain(internalProject, "first")) == [] expect(ReducerProject.getPastChain(project, "first")) == []
}) })
test("past chain main", () => { test("past chain main", () => {
expect(Project.Private.getPastChain(internalProject, "main")) == ["first"] expect(ReducerProject.getPastChain(project, "main")) == ["first"]
}) })
test("test result", () => { test("test result", () => {
runFetchResult(project, "main")->expect->toBe("Ok(1)") runFetchResult(project, "main")->expect->toBe("Ok(1)")
}) })
test("test bindings", () => { test("test bindings", () => {
runFetchFlatBindings(project, "first")->expect->toBe("@{x: 1}") runFetchFlatBindings(project, "first")->expect->toBe("{x: 1}")
}) })
}) })
@ -98,7 +83,7 @@ describe("project2", () => {
Project.setContinues(project, "second", ["first"]) Project.setContinues(project, "second", ["first"])
Project.setSource(project, "first", "x=1") Project.setSource(project, "first", "x=1")
Project.setSource(project, "second", "y=2") Project.setSource(project, "second", "y=2")
Project.setSource(project, "main", "y") Project.setSource(project, "main", "z=3;y")
test("runOrder", () => { test("runOrder", () => {
expect(Project.getRunOrder(project)) == ["first", "second", "main"] expect(Project.getRunOrder(project)) == ["first", "second", "main"]
@ -122,7 +107,8 @@ describe("project2", () => {
runFetchResult(project, "main")->expect->toBe("Ok(2)") runFetchResult(project, "main")->expect->toBe("Ok(2)")
}) })
test("test bindings", () => { test("test bindings", () => {
runFetchFlatBindings(project, "main")->expect->toBe("@{x: 1,y: 2}") // bindings from continues are not exposed!
runFetchFlatBindings(project, "main")->expect->toBe("{z: 3}")
}) })
}) })
@ -152,7 +138,7 @@ describe("project with include", () => {
) )
Project.parseIncludes(project, "second") //The only way of setting includes Project.parseIncludes(project, "second") //The only way of setting includes
Project.setSource(project, "main", "y") Project.setSource(project, "main", "z=3; y")
test("runOrder", () => { test("runOrder", () => {
expect(Project.getRunOrder(project)) == ["common", "first", "second", "main"] expect(Project.getRunOrder(project)) == ["common", "first", "second", "main"]
@ -178,7 +164,8 @@ describe("project with include", () => {
runFetchResult(project, "main")->expect->toBe("Ok(2)") runFetchResult(project, "main")->expect->toBe("Ok(2)")
}) })
test("test bindings", () => { test("test bindings", () => {
runFetchFlatBindings(project, "main")->expect->toBe("@{common: 0,x: 1,y: 2}") // bindings from continues are not exposed!
runFetchFlatBindings(project, "main")->expect->toBe("{z: 3}")
}) })
}) })

View File

@ -1,5 +1,4 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
@ -16,13 +15,13 @@ Case "Running a single source".
/* Let's start with running a single source and getting Result as well as the Bindings /* Let's start with running a single source and getting Result as well as the Bindings
First you need to create a project. A project is a collection of sources. First you need to create a project. A project is a collection of sources.
Project takes care of the dependencies between the sources, correct compilation and run order. Project takes care of the dependencies between the sources, correct compilation and run order.
You can run any source in the project. It will be compiled and run if it is not already done else already existing results will be presented. You can run any source in the project. It will be compiled and run if it hasn't happened already; otherwise already existing results will be presented.
The dependencies will be automatically compiled and run. So you don't need to worry about that in a multi source project. The dependencies will be automatically compiled and run. So you don't need to worry about that in a multi source project.
In summary you issue a run command on the whole project or on a specific source to ensure that there is a result for that source. In summary you issue a run command on the whole project or on a specific source to ensure that there is a result for that source.
*/ */
let project = Project.createProject() let project = Project.createProject()
/* Every source has a name. This is used for debugging, dependencies and error messages. */ /* Every source has a name. This is used for debugging, dependencies and error messages. */
Project.setSource(project, "main", "1 + 2") project->Project.setSource("main", "1 + 2")
/* Let's run "main" source. */ /* Let's run "main" source. */
project->Project.run("main") project->Project.run("main")
/* Now you have a result for "main" source. /* Now you have a result for "main" source.
@ -46,27 +45,25 @@ Case "Running a single source".
Getting None means you have forgotten to run the source. Getting None means you have forgotten to run the source.
*/ */
let result = project->Project.getResult("main") let result = project->Project.getResult("main")
let bindings = project->Project.getBindings("main")->Bindings.removeResult let bindings = project->Project.getBindings("main")
/* Let's display the result and bindings */ /* Let's display the result and bindings */
( (result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
result->InternalExpressionValue.toStringResult, ("Ok(3)", "{}")
bindings->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(3)", "@{}")
/* You've got 3 with empty bindings. */ /* You've got 3 with empty bindings. */
}) })
test("run summary", () => { test("run summary", () => {
let project = Project.createProject() let project = Project.createProject()
Project.setSource(project, "main", "1 + 2") project->Project.setSource("main", "1 + 2")
Project.runAll(project) project->Project.runAll
let result = Project.getResult(project, "main") let result = project->Project.getResult("main")
let bindings = Project.getBindings(project, "main")->Bindings.removeResult let bindings = project->Project.getBindings("main")
/* Now you have external bindings and external result. */ /* Now you have external bindings and external result. */
( (
result->InternalExpressionValue.toStringResult, result->Reducer_Value.toStringResult,
bindings->InternalExpressionValue.IEvBindings->InternalExpressionValue.toString, bindings->Reducer_T.IEvRecord->Reducer_Value.toString,
)->expect == ("Ok(3)", "@{}") )->expect == ("Ok(3)", "{}")
}) })
test("run with an environment", () => { test("run with an environment", () => {
@ -74,23 +71,21 @@ Case "Running a single source".
let project = Project.createProject() let project = Project.createProject()
/* Optional. Set your custom environment anytime before running */ /* Optional. Set your custom environment anytime before running */
Project.setEnvironment(project, InternalExpressionValue.defaultEnvironment) project->Project.setEnvironment(Reducer_Context.defaultEnvironment)
Project.setSource(project, "main", "1 + 2") project->Project.setSource("main", "1 + 2")
Project.runAll(project) project->Project.runAll
let result = Project.getResult(project, "main") let result = project->Project.getResult("main")
let _bindings = Project.getBindings(project, "main") let _bindings = project->Project.getBindings("main")
result->InternalExpressionValue.toStringResult->expect == "Ok(3)" result->Reducer_Value.toStringResult->expect == "Ok(3)"
}) })
test("shortcut", () => { test("shortcut", () => {
/* If you are running single source without includes and you don't need a custom environment, you can use the shortcut. */ /* If you are running single source without includes and you don't need a custom environment, you can use the shortcut. */
/* Examples above was to prepare you for the multi source tutorial. */ /* Examples above was to prepare you for the multi source tutorial. */
let (result, bindings) = Project.evaluate("1+2") let (result, bindings) = Project.evaluate("1+2")
( (result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
result->InternalExpressionValue.toStringResult, ("Ok(3)", "{}")
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(3)", "@{}")
}) })
}) })
}) })

View File

@ -1,5 +1,4 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
@ -14,27 +13,25 @@ describe("ReducerProject Tutorial", () => {
test("Chaining", () => { test("Chaining", () => {
let project = Project.createProject() let project = Project.createProject()
/* This time let's add 3 sources and chain them together */ /* This time let's add 3 sources and chain them together */
Project.setSource(project, "source1", "x=1") project->Project.setSource("source1", "x=1")
Project.setSource(project, "source2", "y=2") project->Project.setSource("source2", "y=x+1")
/* To run, source2 depends on source1 */ /* To run, source2 depends on source1 */
Project.setContinues(project, "source2", ["source1"]) project->Project.setContinues("source2", ["source1"])
Project.setSource(project, "source3", "z=3") project->Project.setSource("source3", "z=y+1")
/* To run, source3 depends on source2 */ /* To run, source3 depends on source2 */
Project.setContinues(project, "source3", ["source2"]) project->Project.setContinues("source3", ["source2"])
/* Now we can run the project */ /* Now we can run the project */
Project.runAll(project) project->Project.runAll
/* And let's check the result and bindings of source3 */ /* And let's check the result and bindings of source3 */
let result3 = Project.getResult(project, "source3") let result3 = project->Project.getResult("source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult let bindings3 = project->Project.getBindings("source3")
( (result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
result3->InternalExpressionValue.toStringResult, ("Ok(())", "{z: 3}")
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
}) })
test("Depending", () => { test("Depending", () => {
@ -43,24 +40,22 @@ describe("ReducerProject Tutorial", () => {
let project = Project.createProject() let project = Project.createProject()
/* This time source1 and source2 are not depending on anything */ /* This time source1 and source2 are not depending on anything */
Project.setSource(project, "source1", "x=1") project->Project.setSource("source1", "x=1")
Project.setSource(project, "source2", "y=2") project->Project.setSource("source2", "y=2")
Project.setSource(project, "source3", "z=3") project->Project.setSource("source3", "z=x+y")
/* To run, source3 depends on source1 and source3 together */ /* To run, source3 depends on source1 and source3 together */
Project.setContinues(project, "source3", ["source1", "source2"]) project->Project.setContinues("source3", ["source1", "source2"])
/* Now we can run the project */ /* Now we can run the project */
Project.runAll(project) project->Project.runAll
/* And let's check the result and bindings of source3 */ /* And let's check the result and bindings of source3 */
let result3 = Project.getResult(project, "source3") let result3 = project->Project.getResult("source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult let bindings3 = project->Project.getBindings("source3")
( (result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
result3->InternalExpressionValue.toStringResult, ("Ok(())", "{z: 3}")
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
}) })
test("Intro to including", () => { test("Intro to including", () => {
@ -70,33 +65,30 @@ describe("ReducerProject Tutorial", () => {
let project = Project.createProject() let project = Project.createProject()
/* This time source1 and source2 are not depending on anything */ /* This time source1 and source2 are not depending on anything */
Project.setSource(project, "source1", "x=1") project->Project.setSource("source1", "x=1")
Project.setSource(project, "source2", "y=2") project->Project.setSource("source2", "y=2")
Project.setSource( project->Project.setSource(
project,
"source3", "source3",
` `
#include "source1" #include "source1"
#include "source2" #include "source2"
z=3`, z=x+y`,
) )
/* We need to parse the includes to set the dependencies */ /* We need to parse the includes to set the dependencies */
Project.parseIncludes(project, "source3") project->Project.parseIncludes("source3")
/* Now we can run the project */ /* Now we can run the project */
Project.runAll(project) project->Project.runAll
/* And let's check the result and bindings of source3 /* And let's check the result and bindings of source3
This time you are getting all the variables because we are including the other sources This time you are getting all the variables because we are including the other sources
Behind the scenes parseIncludes is setting the dependencies */ Behind the scenes parseIncludes is setting the dependencies */
let result3 = Project.getResult(project, "source3") let result3 = project->Project.getResult("source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult let bindings3 = project->Project.getBindings("source3")
( (result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
result3->InternalExpressionValue.toStringResult, ("Ok(())", "{z: 3}")
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
/* /*
Doing it like this is too verbose for a storybook Doing it like this is too verbose for a storybook
But I hope you have seen the relation of setContinues and parseIncludes */ But I hope you have seen the relation of setContinues and parseIncludes */

View File

@ -1,5 +1,4 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
@ -16,8 +15,7 @@ Here we will finally proceed to a real life scenario. */
/* Here we investigate the details about parseIncludes, before setting up a real life scenario in the next section. */ /* Here we investigate the details about parseIncludes, before setting up a real life scenario in the next section. */
/* Everything happens inside a project, so let's have a project */ /* Everything happens inside a project, so let's have a project */
let project = Project.createProject() let project = Project.createProject()
Project.setSource( project->Project.setSource(
project,
"main", "main",
` `
#include "common" #include "common"
@ -25,10 +23,10 @@ Here we will finally proceed to a real life scenario. */
`, `,
) )
/* We need to parse includes after changing the source */ /* We need to parse includes after changing the source */
Project.parseIncludes(project, "main") project->Project.parseIncludes("main")
test("getDependencies", () => { test("getDependencies", () => {
/* Parse includes has set the dependencies */ /* Parse includes has set the dependencies */
Project.getDependencies(project, "main")->expect == ["common"] project->Project.getDependencies("main")->expect == ["common"]
/* If there were no includes than there would be no dependencies */ /* If there were no includes than there would be no dependencies */
/* However if there was a syntax error at includes then would be no dependencies also */ /* However if there was a syntax error at includes then would be no dependencies also */
/* Therefore looking at dependencies is not the right way to load includes */ /* Therefore looking at dependencies is not the right way to load includes */
@ -36,7 +34,7 @@ Here we will finally proceed to a real life scenario. */
}) })
test("getIncludes", () => { test("getIncludes", () => {
/* Parse includes has set the includes */ /* Parse includes has set the includes */
switch Project.getIncludes(project, "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->Reducer_ErrorValue.errorToString->fail
} }
@ -50,7 +48,7 @@ Here we will finally proceed to a real life scenario. */
include or depend on the current source. include or depend on the current source.
But you don't need to use this to execute the projects. But you don't need to use this to execute the projects.
It is provided for completeness of information. */ It is provided for completeness of information. */
Project.getDependents(project, "main")->expect == [] project->Project.getDependents("main")->expect == []
/* Nothing is depending on or including main */ /* Nothing is depending on or including main */
}) })
@ -76,29 +74,29 @@ Here we will finally proceed to a real life scenario. */
/* let's recursively load the sources */ /* let's recursively load the sources */
let rec loadIncludesRecursively = (project, sourceName, visited) => { let rec loadIncludesRecursively = (project, sourceName, visited) => {
if Js.Array2.includes(visited, sourceName) { if visited->Js.Array2.includes(sourceName) {
/* Oh we have already visited this source. There is an include cycle */ /* Oh we have already visited this source. There is an include cycle */
"Cyclic include ${sourceName}"->Js.Exn.raiseError "Cyclic include ${sourceName}"->Js.Exn.raiseError
} else { } else {
let newVisited = Js.Array2.copy(visited) let newVisited = Js.Array2.copy(visited)
let _ = Js.Array2.push(newVisited, sourceName) let _ = newVisited->Js.Array2.push(sourceName)
/* Let's parse the includes and dive into them */ /* Let's parse the includes and dive into them */
Project.parseIncludes(project, sourceName) Project.parseIncludes(project, sourceName)
let rIncludes = Project.getIncludes(project, 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->Reducer_ErrorValue.errorToString->Js.Exn.raiseError
| Ok(includes) => | Ok(includes) =>
Belt.Array.forEach(includes, newIncludeName => { includes->Belt.Array.forEach(newIncludeName => {
/* We have got one of the new includes. /* We have got one of the new includes.
Let's load it and add it to the project */ Let's load it and add it to the project */
let newSource = loadSource(newIncludeName) let newSource = loadSource(newIncludeName)
Project.setSource(project, newIncludeName, newSource) project->Project.setSource(newIncludeName, newSource)
/* The new source is loaded and added to the project. */ /* The new source is loaded and added to the project. */
/* Of course the new source might have includes too. */ /* Of course the new source might have includes too. */
/* Let's recursively load them */ /* Let's recursively load them */
loadIncludesRecursively(project, newIncludeName, newVisited) project->loadIncludesRecursively(newIncludeName, newVisited)
}) })
} }
} }
@ -110,45 +108,44 @@ Here we will finally proceed to a real life scenario. */
let project = Project.createProject() let project = Project.createProject()
/* main includes source3 which includes source2 which includes source1 */ project->Project.setSource(
Project.setSource(
project,
"main", "main",
` `
#include "source1"
#include "source2"
#include "source3" #include "source3"
x+y+z a = x+y+z
b = doubleX
a
`, `,
) )
/* Setting source requires parsing and loading the includes recursively */ /* Setting source requires parsing and loading the includes recursively */
loadIncludesRecursively(project, "main", []) //No visited yet project->loadIncludesRecursively("main", []) // Not visited yet
/* Let's salt it more. Let's have another source in the project which also has includes */ /* Let's salt it more. Let's have another source in the project which also has includes */
/* doubleX includes source1 which is eventually included by main as well */ /* doubleX includes source1 which is eventually included by main as well */
Project.setSource( project->Project.setSource(
project,
"doubleX", "doubleX",
` `
#include "source1" #include "source1"
doubleX = x * 2 doubleX = x * 2
`, `,
) )
loadIncludesRecursively(project, "doubleX", []) project->loadIncludesRecursively("doubleX", [])
/* Remember, any time you set a source, you need to load includes recursively */ /* Remember, any time you set a source, you need to load includes recursively */
/* As doubleX is not included by main, it is not loaded recursively. /* As doubleX is not included by main, it is not loaded recursively.
So we link it to the project as a dependency */ So we link it to the project as a dependency */
Project.setContinues(project, "main", ["doubleX"]) project->Project.setContinues("main", ["doubleX"])
/* Let's run the project */ /* Let's run the project */
Project.runAll(project) project->Project.runAll
let result = Project.getResult(project, "main") let result = project->Project.getResult("main")
let bindings = Project.getBindings(project, "main") let bindings = project->Project.getBindings("main")
/* And see the result and bindings.. */ /* And see the result and bindings.. */
test("recursive includes", () => { test("recursive includes", () => {
( (result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
result->InternalExpressionValue.toStringResult, ("Ok(6)", "{a: 6,b: 2}")
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(6)", "@{doubleX: 2,x: 1,y: 2,z: 3}")
/* Everything as expected */ /* Everything as expected */
}) })
}) })

View File

@ -1,7 +1,5 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
open Jest open Jest
open Expect open Expect
@ -30,7 +28,7 @@ describe("ReducerProject Tutorial", () => {
/* We can now run the project */ /* We can now run the project */
Project.runAll(project) Project.runAll(project)
let result = Project.getResult(project, "main") let result = Project.getResult(project, "main")
result->InternalExpressionValue.toStringResult->expect == "Ok(6)" result->Reducer_Value.toStringResult->expect == "Ok(6)"
}) })
}) })

View File

@ -1,5 +1,4 @@
@@warning("-44") @@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
@ -32,7 +31,7 @@ describe("ReducerProject Tutorial", () => {
test("userResults", () => { test("userResults", () => {
let userResultsAsString = Belt.Array.map(userResults, aResult => let userResultsAsString = Belt.Array.map(userResults, aResult =>
aResult->InternalExpressionValue.toStringResult aResult->Reducer_Value.toStringResult
) )
userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"] userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"]
}) })

View File

@ -23,7 +23,7 @@ describe("eval on distribution functions", () => {
testEval("-normal(5,2)", "Ok(Normal(-5,2))") testEval("-normal(5,2)", "Ok(Normal(-5,2))")
}) })
describe("to", () => { describe("to", () => {
testEval("5 to 2", "Error(TODO: Low value must be less than high value.)") testEval("5 to 2", "Error(Error: Low value must be less than high value.)")
testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))") testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))")
testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))") testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))")
}) })
@ -98,6 +98,7 @@ describe("eval on distribution functions", () => {
"log(normal(5,2), normal(10,1))", "log(normal(5,2), normal(10,1))",
"Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)", "Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)",
) )
testEval("log(2, SampleSet.fromDist(0.0001 to 5))", "Ok(Sample Set Distribution)") // log with low values, see https://github.com/quantified-uncertainty/squiggle/issues/1098
testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)") testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)")
testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)") testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)")
}) })
@ -119,40 +120,28 @@ describe("eval on distribution functions", () => {
describe("parse on distribution functions", () => { describe("parse on distribution functions", () => {
describe("power", () => { describe("power", () => {
testParse( testParse("normal(5,2) ^ normal(5,1)", "Ok((pow)((normal)(5, 2), (normal)(5, 1)))")
"normal(5,2) ^ normal(5,1)", testParse("3 ^ normal(5,1)", "Ok((pow)(3, (normal)(5, 1)))")
"Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) (:normal 5 1)))})", testParse("normal(5,2) ^ 3", "Ok((pow)((normal)(5, 2), 3))")
)
testParse("3 ^ normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:pow 3 (:normal 5 1)))})")
testParse("normal(5,2) ^ 3", "Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) 3))})")
}) })
describe("subtraction", () => { describe("subtraction", () => {
testParse("10 - normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:subtract 10 (:normal 5 1)))})") testParse("10 - normal(5,1)", "Ok((subtract)(10, (normal)(5, 1)))")
testParse("normal(5,1) - 10", "Ok({(:$_endOfOuterBlock_$ () (:subtract (:normal 5 1) 10))})") testParse("normal(5,1) - 10", "Ok((subtract)((normal)(5, 1), 10))")
}) })
describe("pointwise arithmetic expressions", () => { describe("pointwise arithmetic expressions", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
testParse( testParse(
~skip=true, ~skip=true,
"normal(5,2) .- normal(5,1)", "normal(5,2) .- normal(5,1)",
"Ok((:$_endOfOuterBlock_$ () (:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1)))))", "Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))",
// TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})" // TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})"
) )
testParse( testParse("normal(5,2) .* normal(5,1)", "Ok((dotMultiply)((normal)(5, 2), (normal)(5, 1)))")
"normal(5,2) .* normal(5,1)", testParse("normal(5,2) ./ normal(5,1)", "Ok((dotDivide)((normal)(5, 2), (normal)(5, 1)))")
"Ok({(:$_endOfOuterBlock_$ () (:dotMultiply (:normal 5 2) (:normal 5 1)))})", testParse("normal(5,2) .^ normal(5,1)", "Ok((dotPow)((normal)(5, 2), (normal)(5, 1)))")
)
testParse(
"normal(5,2) ./ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotDivide (:normal 5 2) (:normal 5 1)))})",
)
testParse(
"normal(5,2) .^ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotPow (:normal 5 2) (:normal 5 1)))})",
)
}) })
describe("equality", () => { describe("equality", () => {
testParse("5 == normal(5,2)", "Ok({(:$_endOfOuterBlock_$ () (:equal 5 (:normal 5 2)))})") testParse("5 == normal(5,2)", "Ok((equal)(5, (normal)(5, 2)))")
}) })
describe("pointwise adding two normals", () => { describe("pointwise adding two normals", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")

View File

@ -3,11 +3,7 @@ open Expect
open Reducer_TestHelpers open Reducer_TestHelpers
let expectEvalToBeOk = (code: string) => let expectEvalToBeOk = (code: string) =>
Reducer_Expression.BackCompatible.evaluateString(code) Reducer_Expression.BackCompatible.evaluateString(code)->E.R.isOk->expect->toBe(true)
->Reducer_Helpers.rRemoveDefaultsInternal
->E.R.isOk
->expect
->toBe(true)
let registry = FunctionRegistry_Library.registry let registry = FunctionRegistry_Library.registry
let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry)) let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry))
@ -15,7 +11,7 @@ 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.make(3, 'HI')", "Ok(['HI','HI','HI'])") testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])")
testEvalToBe("make(3, 'HI')", "Error(Function not found: make(Number,String))") 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])")
testEvalToBe("List.first([3,5,8])", "Ok(3)") testEvalToBe("List.first([3,5,8])", "Ok(3)")
testEvalToBe("List.last([3,5,8])", "Ok(8)") testEvalToBe("List.last([3,5,8])", "Ok(8)")
@ -84,6 +80,16 @@ 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("Dict.merge({a: 1, b: 2}, {b: 3, c: 4, d: 5})", "Ok({a: 1,b: 3,c: 4,d: 5})")
testEvalToBe(
"Dict.mergeMany([{a: 1, b: 2}, {c: 3, d: 4}, {c: 5, e: 6}])",
"Ok({a: 1,b: 2,c: 5,d: 4,e: 6})",
)
testEvalToBe("Dict.keys({a: 1, b: 2})", "Ok(['a','b'])")
testEvalToBe("Dict.values({a: 1, b: 2})", "Ok([1,2])")
testEvalToBe("Dict.toList({a: 1, b: 2})", "Ok([['a',1],['b',2]])")
testEvalToBe("Dict.fromList([['a', 1], ['b', 2]])", "Ok({a: 1,b: 2})")
}) })
describe("Fn auto-testing", () => { describe("Fn auto-testing", () => {
@ -102,7 +108,7 @@ describe("FunctionRegistry Library", () => {
let responseType = let responseType =
example example
->Reducer_Expression.BackCompatible.evaluateString ->Reducer_Expression.BackCompatible.evaluateString
->E.R2.fmap(ReducerInterface_InternalExpressionValue.valueToValueType) ->E.R2.fmap(Reducer_Value.valueToValueType)
let expectedOutputType = fn.output |> E.O.toExn("") let expectedOutputType = fn.output |> E.O.toExn("")
expect(responseType)->toEqual(Ok(expectedOutputType)) expect(responseType)->toEqual(Ok(expectedOutputType))
}, },

View File

@ -0,0 +1,80 @@
module Map: Benchmark_Helpers.BenchmarkTopic = {
let arraySize = 1000
let iterations = 300_000
let beltArray = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Belt.Array.map(v => v)
})
}
let jsArray2 = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Js.Array2.map(v => v)
})
}
let ocamlArray = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Array.map(v => v, _)
})
}
let runAll = () => {
Js.log(
`Mapping identity function over arrays of size ${arraySize->Js.Int.toString} (${iterations->Js.Int.toString} iterations)`,
)
Benchmark_Helpers.measure("Belt.Array.map", beltArray)
Benchmark_Helpers.measure("Js.Array2.map", jsArray2)
Benchmark_Helpers.measure("Array.map", ocamlArray)
}
}
module Sort: Benchmark_Helpers.BenchmarkTopic = {
let arraySize = 1000
let iterations = 30000
let jsArray2 = () => {
let x = Belt.Array.make(arraySize, 0.)
let compare = (a: float, b: float) => {
if a < b {
-1
} else {
1
}
}
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Js.Array2.sortInPlaceWith(compare)
})
}
let jsArray2withOcamlCompare = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Js.Array2.sortInPlaceWith(Pervasives.compare)
})
}
let ocamlArray = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Array.fast_sort(compare, _)
})
}
let runAll = () => {
Js.log(
`Sorting arrays of size ${arraySize->Js.Int.toString} (${iterations->Js.Int.toString} iterations)`,
)
Benchmark_Helpers.measure("Js.Array2.sort", jsArray2)
Benchmark_Helpers.measure("Js.Array2.sort with Ocaml compare", jsArray2withOcamlCompare)
Benchmark_Helpers.measure("Array.fast_sort", ocamlArray)
}
}
Map.runAll()
Sort.runAll()

View File

@ -0,0 +1,11 @@
module type BenchmarkTopic = {
let runAll: unit => unit
}
let measure = (name: string, f: unit => unit) => {
let start = Js.Date.make()->Js.Date.valueOf
f()
let end = Js.Date.make()->Js.Date.valueOf
let duration = (end -. start) /. 1000.
Js.log2(duration, name)
}

View File

@ -0,0 +1,63 @@
module StringMap: Benchmark_Helpers.BenchmarkTopic = {
let size = 1000
let iterations = 10_000
let kv = Belt.Array.range(1, size)->Belt.Array.map(v => ("key" ++ v->Belt.Int.toString, v))
let beltMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Belt.Map.String.empty
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => acc->Belt.Map.String.set(k, v))
})
}
let beltMutableMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Belt.MutableMap.String.make()
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => {
acc->Belt.MutableMap.String.set(k, v)
acc
})
})
}
let beltHashMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Belt.HashMap.String.make(~hintSize=100)
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => {
acc->Belt.HashMap.String.set(k, v)
acc
})
})
}
let jsDict = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Js.Dict.empty()
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => {
acc->Js.Dict.set(k, v)
acc
})
})
}
let jsMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Js_map.make()
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => acc->Js_map.set(k, v))
})
}
let runAll = () => {
Js.log(
`Filling a map with ("key{i}" => "i") key-value pairs, size ${size->Js.Int.toString} (${iterations->Js.Int.toString} iterations)`,
)
Benchmark_Helpers.measure("Belt.Map.String", beltMap)
Benchmark_Helpers.measure("Belt.MutableMap.String", beltMutableMap)
Benchmark_Helpers.measure("Belt.HashMap.String", beltHashMap)
Benchmark_Helpers.measure("Js.Dict", jsDict)
Benchmark_Helpers.measure("Js.Map", jsMap)
}
}
let runAll = StringMap.runAll()

View File

@ -9,6 +9,11 @@
"dir": "__tests__", "dir": "__tests__",
"type": "dev", "type": "dev",
"subdirs": true "subdirs": true
},
{
"dir": "benchmark",
"type": "dev",
"subdirs": true
} }
], ],
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header", "-bs-g"], "bsc-flags": ["-bs-super-errors", "-bs-no-version-header", "-bs-g"],
@ -21,7 +26,12 @@
"suffix": ".bs.js", "suffix": ".bs.js",
"namespace": true, "namespace": true,
"bs-dependencies": ["bisect_ppx"], "bs-dependencies": ["bisect_ppx"],
"bs-dev-dependencies": ["@glennsl/rescript-jest", "rescript-fast-check"], "bs-dev-dependencies": [
"@glennsl/rescript-jest",
"rescript-fast-check",
"rescript-js-map",
"rescript-js-iterator"
],
"gentypeconfig": { "gentypeconfig": {
"language": "typescript", "language": "typescript",
"module": "commonjs", "module": "commonjs",

View File

@ -10,5 +10,7 @@ module.exports = {
"/node_modules/", "/node_modules/",
".*Helpers.bs.js", ".*Helpers.bs.js",
".*Helpers.ts", ".*Helpers.ts",
".*Reducer_Type.*",
".*_type_test.bs.js",
], ],
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "@quri/squiggle-lang", "name": "@quri/squiggle-lang",
"version": "0.4.2", "version": "0.5.0",
"homepage": "https://squiggle-language.com", "homepage": "https://squiggle-language.com",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -45,7 +45,7 @@
"@stdlib/stats": "^0.0.13", "@stdlib/stats": "^0.0.13",
"jstat": "^1.9.5", "jstat": "^1.9.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mathjs": "^11.2.0", "mathjs": "^11.2.1",
"pdfast": "^0.2.0" "pdfast": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
@ -56,7 +56,7 @@
"bisect_ppx": "^2.7.1", "bisect_ppx": "^2.7.1",
"chalk": "^5.0.1", "chalk": "^5.0.1",
"codecov": "^3.8.3", "codecov": "^3.8.3",
"fast-check": "^3.1.3", "fast-check": "^3.1.4",
"gentype": "^4.5.0", "gentype": "^4.5.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"moduleserve": "^0.9.1", "moduleserve": "^0.9.1",
@ -66,8 +66,9 @@
"reanalyze": "^2.23.0", "reanalyze": "^2.23.0",
"rescript": "^9.1.4", "rescript": "^9.1.4",
"rescript-fast-check": "^1.1.1", "rescript-fast-check": "^1.1.1",
"rescript-js-map": "^1.1.0",
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"ts-loader": "^9.3.0", "ts-loader": "^9.4.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.8.3", "typescript": "^4.8.3",
"webpack": "^5.74.0", "webpack": "^5.74.0",

View File

@ -0,0 +1,25 @@
Various scripts used for development, benchmarking and testing.
None of these are bundled in the NPM package yet.
# run.mjs
`scripts/run.mjs` allows quick testing of Squiggle programs:
```
$ ./scripts/run.mjs '2+2'
Running 2+2
Ok 4
@{__result__: 4}
```
# run-file.mjs
`scripts/run-file.mjs` can be used to run and benchmark squiggle scripts stored in files:
```
$ ./scripts/run-file.mjs ./path/to/file.squiggle
Time: 3.18 Ok
```
To see the result and bindings, add the `-o` or `--output` flag.

View File

@ -0,0 +1,18 @@
#!/usr/bin/env node
import { SqProject } from "@quri/squiggle-lang";
import { measure } from "./lib.mjs";
const maxP = 5;
for (let p = 0; p <= maxP; p++) {
const size = Math.pow(10, p);
const project = SqProject.create();
project.setSource(
"main",
`List.upTo(1, ${size}) |> map({|x| List.upTo(1, 100) |> reduce(0, {|a,b|a+b})})`
);
const time = measure(() => {
project.run("main");
});
console.log(`1e${p}`, "\t", time);
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env node
import { SqProject } from "@quri/squiggle-lang";
import { measure } from "./lib.mjs";
const maxP = 7;
for (let p = 0; p <= maxP; p++) {
const size = Math.pow(10, p);
const project = SqProject.create();
project.setSource("list", `l = List.upTo(1,${size})`);
project.run("list");
project.setSource("map", "l |> map({|x| x})");
project.setContinues("map", ["list"]);
const time = measure(() => {
project.run("map");
});
console.log(`1e${p}`, "\t", time);
}

View File

@ -0,0 +1,34 @@
#!/usr/bin/env node
import { SqProject } from "@quri/squiggle-lang";
import { measure } from "./lib.mjs";
const maxP = 3;
const sampleCount = process.env.SAMPLE_COUNT;
for (let p = 0; p <= maxP; p++) {
const size = Math.pow(10, p);
const project = SqProject.create();
if (sampleCount) {
project.setEnvironment({
sampleCount: Number(sampleCount),
xyPointLength: Number(sampleCount),
});
}
project.setSource(
"main",
`
List.upTo(1, ${size}) -> map(
{ |x| normal(x,2) -> SampleSet.fromDist -> PointSet.fromDist }
)->List.last
`
);
const time = measure(() => {
project.run("main");
});
const result = project.getResult("main");
if (result.tag != "Ok") {
throw new Error("Code failed: " + result.value.toString());
}
console.log(`1e${p}`, "\t", time);
}

View File

@ -0,0 +1,41 @@
import { SqProject } from "@quri/squiggle-lang";
export const measure = (cb, times = 1) => {
const t1 = new Date();
for (let i = 1; i <= times; i++) {
cb();
}
const t2 = new Date();
return (t2 - t1) / 1000;
};
export const red = (str) => `\x1b[31m${str}\x1b[0m`;
export const green = (str) => `\x1b[32m${str}\x1b[0m`;
export const run = (src, { output, sampleCount } = {}) => {
const project = SqProject.create();
if (sampleCount) {
project.setEnvironment({
sampleCount: Number(sampleCount),
xyPointLength: Number(sampleCount),
});
}
project.setSource("main", src);
const time = measure(() => project.run("main"));
const bindings = project.getBindings("main");
const result = project.getResult("main");
if (output) {
console.log("Result:", result.tag, result.value.toString());
console.log("Bindings:", bindings.toString());
}
console.log(
"Time:",
String(time),
result.tag === "Error" ? red(result.tag) : green(result.tag),
result.tag === "Error" ? result.value.toString() : ""
);
};

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
import fs from "fs";
import { Command } from "commander";
import { run } from "./lib.mjs";
const program = new Command();
program.option("-o, --output");
program.arguments("<string>");
const options = program.parse(process.argv);
const sampleCount = process.env.SAMPLE_COUNT;
const src = fs.readFileSync(program.args[0], "utf-8");
if (!src) {
throw new Error("Expected src");
}
run(src, { output: options.output, sampleCount });

View File

@ -0,0 +1,12 @@
#!/usr/bin/env node
import { run } from "./lib.mjs";
const src = process.argv[2];
if (!src) {
throw new Error("Expected src");
}
console.log(`Running ${src}`);
const sampleCount = process.env.SAMPLE_COUNT;
run(src, { output: true, sampleCount });

View File

@ -1,30 +0,0 @@
import * as RSModuleValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Module.gen";
import { SqModuleValue, wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
export class SqModule {
constructor(
private _value: RSModuleValue.squiggleValue_Module,
public location: SqValueLocation
) {}
entries() {
return RSModuleValue.getKeyValuePairs(this._value).map(
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
);
}
asValue() {
return new SqModuleValue(
RSModuleValue.toSquiggleValue(this._value),
this.location
);
}
get(k: string) {
const v = RSModuleValue.get(this._value, k);
return v === undefined || v === null
? undefined
: wrapValue(v, this.location.extend(k));
}
}

View File

@ -1,4 +1,4 @@
import * as _ from "lodash"; import zipWith from "lodash/zipWith";
import { wrapDistribution } from "./SqDistribution"; import { wrapDistribution } from "./SqDistribution";
import * as RSPointSetDist from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution.gen"; import * as RSPointSetDist from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution.gen";
import { pointSetDistributionTag as Tag } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution_tag"; import { pointSetDistributionTag as Tag } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution_tag";
@ -16,7 +16,7 @@ const shapePoints = (
): SqPoint[] => { ): SqPoint[] => {
let xs = x.xyShape.xs; let xs = x.xyShape.xs;
let ys = x.xyShape.ys; let ys = x.xyShape.ys;
return _.zipWith(xs, ys, (x, y) => ({ x, y })); return zipWith(xs, ys, (x, y) => ({ x, y }));
}; };
export const wrapPointSetDist = (value: T) => { export const wrapPointSetDist = (value: T) => {

View File

@ -2,7 +2,7 @@ import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen";
import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.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 { SqModule } from "./SqModule"; import { SqRecord } from "./SqRecord";
import { wrapValue } from "./SqValue"; import { wrapValue } from "./SqValue";
import { resultMap2 } from "./types"; import { resultMap2 } from "./types";
import { SqValueLocation } from "./SqValueLocation"; import { SqValueLocation } from "./SqValueLocation";
@ -83,7 +83,7 @@ export class SqProject {
} }
getBindings(sourceId: string) { getBindings(sourceId: string) {
return new SqModule( return new SqRecord(
RSProject.getBindings(this._value, sourceId), RSProject.getBindings(this._value, sourceId),
new SqValueLocation(this, sourceId, { new SqValueLocation(this, sourceId, {
root: "bindings", root: "bindings",
@ -111,4 +111,8 @@ export class SqProject {
setEnvironment(environment: environment) { setEnvironment(environment: environment) {
RSProject.setEnvironment(this._value, environment); RSProject.setEnvironment(this._value, environment);
} }
getEnvironment(): environment {
return RSProject.getEnvironment(this._value);
}
} }

View File

@ -1,5 +1,5 @@
import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen"; import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen";
import { wrapValue } from "./SqValue"; import { SqRecordValue, wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation"; import { SqValueLocation } from "./SqValueLocation";
type T = RSRecord.squiggleValue_Record; type T = RSRecord.squiggleValue_Record;
@ -12,4 +12,15 @@ export class SqRecord {
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const ([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
); );
} }
toString() {
return RSRecord.toString(this._value);
}
asValue() {
return new SqRecordValue(
RSRecord.toSquiggleValue(this._value),
this.location
);
}
} }

View File

@ -1,7 +0,0 @@
import * as RSType from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Type.gen";
type T = RSType.squiggleValue_Type;
export class SqType {
constructor(private _value: T) {}
}

View File

@ -3,11 +3,8 @@ import { squiggleValueTag as Tag } from "../rescript/ForTS/ForTS_SquiggleValue/F
import { wrapDistribution } from "./SqDistribution"; import { wrapDistribution } from "./SqDistribution";
import { SqLambda } from "./SqLambda"; import { SqLambda } from "./SqLambda";
import { SqLambdaDeclaration } from "./SqLambdaDeclaration"; import { SqLambdaDeclaration } from "./SqLambdaDeclaration";
import { SqModule } from "./SqModule";
import { SqRecord } from "./SqRecord"; import { SqRecord } from "./SqRecord";
import { SqArray } from "./SqArray"; import { SqArray } from "./SqArray";
import { SqType } from "./SqType";
import { SqProject } from "./SqProject";
import { SqValueLocation } from "./SqValueLocation"; import { SqValueLocation } from "./SqValueLocation";
export { Tag as SqValueTag }; export { Tag as SqValueTag };
@ -46,14 +43,6 @@ export class SqArrayValue extends SqAbstractValue {
} }
} }
export class SqArrayStringValue extends SqAbstractValue {
tag = Tag.ArrayString as const;
get value() {
return this.valueMethod(RSValue.getArrayString);
}
}
export class SqBoolValue extends SqAbstractValue { export class SqBoolValue extends SqAbstractValue {
tag = Tag.Bool as const; tag = Tag.Bool as const;
@ -62,14 +51,6 @@ export class SqBoolValue extends SqAbstractValue {
} }
} }
export class SqCallValue extends SqAbstractValue {
tag = Tag.Call as const;
get value() {
return this.valueMethod(RSValue.getCall);
}
}
export class SqDateValue extends SqAbstractValue { export class SqDateValue extends SqAbstractValue {
tag = Tag.Date as const; tag = Tag.Date as const;
@ -102,14 +83,6 @@ export class SqLambdaValue extends SqAbstractValue {
} }
} }
export class SqModuleValue extends SqAbstractValue {
tag = Tag.Module as const;
get value() {
return new SqModule(this.valueMethod(RSValue.getModule), this.location);
}
}
export class SqNumberValue extends SqAbstractValue { export class SqNumberValue extends SqAbstractValue {
tag = Tag.Number as const; tag = Tag.Number as const;
@ -134,14 +107,6 @@ export class SqStringValue extends SqAbstractValue {
} }
} }
export class SqSymbolValue extends SqAbstractValue {
tag = Tag.Symbol as const;
get value(): string {
return this.valueMethod(RSValue.getSymbol);
}
}
export class SqTimeDurationValue extends SqAbstractValue { export class SqTimeDurationValue extends SqAbstractValue {
tag = Tag.TimeDuration as const; tag = Tag.TimeDuration as const;
@ -150,22 +115,6 @@ export class SqTimeDurationValue extends SqAbstractValue {
} }
} }
export class SqTypeValue extends SqAbstractValue {
tag = Tag.Type as const;
get value() {
return new SqType(this.valueMethod(RSValue.getType));
}
}
export class SqTypeIdentifierValue extends SqAbstractValue {
tag = Tag.TypeIdentifier as const;
get value() {
return this.valueMethod(RSValue.getTypeIdentifier);
}
}
export class SqVoidValue extends SqAbstractValue { export class SqVoidValue extends SqAbstractValue {
tag = Tag.Void as const; tag = Tag.Void as const;
@ -176,21 +125,15 @@ export class SqVoidValue extends SqAbstractValue {
const tagToClass = { const tagToClass = {
[Tag.Array]: SqArrayValue, [Tag.Array]: SqArrayValue,
[Tag.ArrayString]: SqArrayStringValue,
[Tag.Bool]: SqBoolValue, [Tag.Bool]: SqBoolValue,
[Tag.Call]: SqCallValue,
[Tag.Date]: SqDateValue, [Tag.Date]: SqDateValue,
[Tag.Declaration]: SqDeclarationValue, [Tag.Declaration]: SqDeclarationValue,
[Tag.Distribution]: SqDistributionValue, [Tag.Distribution]: SqDistributionValue,
[Tag.Lambda]: SqLambdaValue, [Tag.Lambda]: SqLambdaValue,
[Tag.Module]: SqModuleValue,
[Tag.Number]: SqNumberValue, [Tag.Number]: SqNumberValue,
[Tag.Record]: SqRecordValue, [Tag.Record]: SqRecordValue,
[Tag.String]: SqStringValue, [Tag.String]: SqStringValue,
[Tag.Symbol]: SqSymbolValue,
[Tag.TimeDuration]: SqTimeDurationValue, [Tag.TimeDuration]: SqTimeDurationValue,
[Tag.Type]: SqTypeValue,
[Tag.TypeIdentifier]: SqTypeIdentifierValue,
[Tag.Void]: SqVoidValue, [Tag.Void]: SqVoidValue,
} as const; } as const;
@ -198,19 +141,13 @@ const tagToClass = {
// type SqValue = typeof tagToClass[keyof typeof tagToClass]; // type SqValue = typeof tagToClass[keyof typeof tagToClass];
export type SqValue = export type SqValue =
| SqArrayValue | SqArrayValue
| SqArrayStringValue
| SqBoolValue | SqBoolValue
| SqCallValue
| SqDateValue | SqDateValue
| SqDeclarationValue | SqDeclarationValue
| SqDistributionValue | SqDistributionValue
| SqLambdaValue | SqLambdaValue
| SqModuleValue
| SqNumberValue | SqNumberValue
| SqRecordValue | SqRecordValue
| SqStringValue | SqStringValue
| SqSymbolValue
| SqTimeDurationValue | SqTimeDurationValue
| SqTypeValue
| SqTypeIdentifierValue
| SqVoidValue; | SqVoidValue;

View File

@ -1,4 +1,4 @@
import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen"; import { environment } from "../rescript/ForTS/ForTS__Types.gen";
import { SqProject } from "./SqProject"; import { SqProject } from "./SqProject";
import { SqValue, SqValueTag } from "./SqValue"; import { SqValue, SqValueTag } from "./SqValue";
export { SqValueLocation } from "./SqValueLocation"; export { SqValueLocation } from "./SqValueLocation";

View File

@ -86,6 +86,7 @@ let toFloatOperation = (
| (SampleSet(sampleSet), #Inv(r)) => SampleSetDist.percentile(sampleSet, r)->Some | (SampleSet(sampleSet), #Inv(r)) => SampleSetDist.percentile(sampleSet, r)->Some
| (SampleSet(sampleSet), #Min) => SampleSetDist.min(sampleSet)->Some | (SampleSet(sampleSet), #Min) => SampleSetDist.min(sampleSet)->Some
| (SampleSet(sampleSet), #Max) => SampleSetDist.max(sampleSet)->Some | (SampleSet(sampleSet), #Max) => SampleSetDist.max(sampleSet)->Some
| (SampleSet(sampleSet), #Cdf(r)) => SampleSetDist.cdf(sampleSet, r)->Some
| _ => None | _ => None
} }
@ -277,22 +278,14 @@ module AlgebraicCombination = {
Right now we don't yet have a way of getting probability mass, so I'll leave this for later. Right now we don't yet have a way of getting probability mass, so I'll leave this for later.
*/ */
let getLogarithmInputError = (t1: t, t2: t, ~toPointSetFn: toPointSetFn): option<error> => { let getLogarithmInputError = (t1: t, t2: t, ~toPointSetFn: toPointSetFn): option<error> => {
let firstOperandIsGreaterThanZero = let isDistGreaterThanZero = t =>
toFloatOperation( toFloatOperation(
t1, t,
~toPointSetFn, ~toPointSetFn,
~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten), ~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
) |> E.R.fmap(r => r > 0.) )->E.R2.fmap(r => r > 0.)
let secondOperandIsGreaterThanZero =
toFloatOperation( let items = E.A.R.firstErrorOrOpen([isDistGreaterThanZero(t1), isDistGreaterThanZero(t2)])
t2,
~toPointSetFn,
~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
) |> E.R.fmap(r => r > 0.)
let items = E.A.R.firstErrorOrOpen([
firstOperandIsGreaterThanZero,
secondOperandIsGreaterThanZero,
])
switch items { switch items {
| Error(r) => Some(r) | Error(r) => Some(r)
| Ok([true, _]) => | Ok([true, _]) =>

View File

@ -1,5 +1,6 @@
const pdfast = require("pdfast"); const pdfast = require("pdfast");
const _ = require("lodash"); const filter = require("lodash/filter");
const isFinite = require("lodash/isFinite");
const samplesToContinuousPdf = ( const samplesToContinuousPdf = (
samples, samples,
@ -8,12 +9,12 @@ const samplesToContinuousPdf = (
min = false, min = false,
max = false max = false
) => { ) => {
let _samples = _.filter(samples, _.isFinite); let _samples = filter(samples, isFinite);
if (_.isFinite(min)) { if (isFinite(min)) {
_samples = _.filter(_samples, (r) => r > min); _samples = filter(_samples, (r) => r > min);
} }
if (_.isFinite(max)) { if (isFinite(max)) {
_samples = _.filter(_samples, (r) => r < max); _samples = filter(_samples, (r) => r < max);
} }
// The pdf that's created from this function is not a pdf but a pmf. y values // The pdf that's created from this function is not a pdf but a pmf. y values

View File

@ -131,6 +131,10 @@ let max = t => T.get(t)->E.A.Floats.max
let stdev = t => T.get(t)->E.A.Floats.stdev let stdev = t => T.get(t)->E.A.Floats.stdev
let variance = t => T.get(t)->E.A.Floats.variance let variance = t => T.get(t)->E.A.Floats.variance
let percentile = (t, f) => T.get(t)->E.A.Floats.percentile(f) let percentile = (t, f) => T.get(t)->E.A.Floats.percentile(f)
let cdf = (t: t, f: float) => {
let countBelowF = t->E.A.reduce(0, (acc, x) => acc + (x <= f ? 1 : 0))
countBelowF->Js.Int.toFloat /. t->length->Js.Int.toFloat
}
let mixture = (values: array<(t, float)>, intendedLength: int) => { let mixture = (values: array<(t, float)>, intendedLength: int) => {
let totalWeight = values->E.A2.fmap(E.Tuple2.second)->E.A.Floats.sum let totalWeight = values->E.A2.fmap(E.Tuple2.second)->E.A.Floats.sum

View File

@ -33,19 +33,19 @@ module Internals = {
module KDE = { module KDE = {
let normalSampling = (samples, outputXYPoints, kernelWidth) => let normalSampling = (samples, outputXYPoints, kernelWidth) =>
samples |> JS.samplesToContinuousPdf(_, outputXYPoints, kernelWidth) |> JS.jsToDist samples->JS.samplesToContinuousPdf(outputXYPoints, kernelWidth)->JS.jsToDist
} }
module T = { module T = {
type t = array<float> type t = array<float>
let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => { let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => {
let xyPointRange = E.A.Sorted.range(samples) |> E.O.default(0.0) let xyPointRange = E.A.Sorted.range(samples)->E.O2.default(0.0)
let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints) let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints)
xWidth /. xyPointWidth xWidth /. xyPointWidth
} }
let formatUnitWidth = w => Jstat.max([w, 1.0]) |> int_of_float let formatUnitWidth = w => Jstat.max([w, 1.0])->int_of_float
let suggestedUnitWidth = (samples, outputXYPoints) => { let suggestedUnitWidth = (samples, outputXYPoints) => {
let suggestedXWidth = SampleSetDist_Bandwidth.nrd0(samples) let suggestedXWidth = SampleSetDist_Bandwidth.nrd0(samples)
@ -62,23 +62,24 @@ let toPointSetDist = (
~samplingInputs: SamplingInputs.samplingInputs, ~samplingInputs: SamplingInputs.samplingInputs,
(), (),
): Internals.Types.outputs => { ): Internals.Types.outputs => {
let samples = Js.Array2.copy(samples) let samples = samples->E.A.Floats.sort
Array.fast_sort(compare, samples)
let minDiscreteToKeep = MagicNumbers.ToPointSet.minDiscreteToKeep(samples) let minDiscreteToKeep = MagicNumbers.ToPointSet.minDiscreteToKeep(samples)
let (continuousPart, discretePart) = E.A.Floats.Sorted.splitContinuousAndDiscreteForMinWeight( let (continuousPart, discretePart) = E.A.Floats.Sorted.splitContinuousAndDiscreteForMinWeight(
samples, samples,
~minDiscreteWeight=minDiscreteToKeep, ~minDiscreteWeight=minDiscreteToKeep,
) )
let length = samples |> E.A.length |> float_of_int
let length = samples->E.A.length->float_of_int
let discrete: PointSetTypes.discreteShape = let discrete: PointSetTypes.discreteShape =
discretePart discretePart
|> E.FloatFloatMap.fmap(r => r /. length) ->E.FloatFloatMap.fmap(r => r /. length, _)
|> E.FloatFloatMap.toArray ->E.FloatFloatMap.toArray
|> XYShape.T.fromZippedArray ->XYShape.T.fromZippedArray
|> Discrete.make ->Discrete.make
let pdf = let pdf =
continuousPart |> E.A.length > 5 continuousPart->E.A.length > 5
? { ? {
let _suggestedXWidth = SampleSetDist_Bandwidth.nrd0(continuousPart) let _suggestedXWidth = SampleSetDist_Bandwidth.nrd0(continuousPart)
// todo: This does some recalculating from the last step. // todo: This does some recalculating from the last step.
@ -86,7 +87,7 @@ let toPointSetDist = (
continuousPart, continuousPart,
samplingInputs.outputXYPoints, samplingInputs.outputXYPoints,
) )
let usedWidth = samplingInputs.kernelWidth |> E.O.default(_suggestedXWidth) let usedWidth = samplingInputs.kernelWidth->E.O2.default(_suggestedXWidth)
let usedUnitWidth = Internals.T.xWidthToUnitWidth( let usedUnitWidth = Internals.T.xWidthToUnitWidth(
samples, samples,
samplingInputs.outputXYPoints, samplingInputs.outputXYPoints,
@ -101,18 +102,18 @@ let toPointSetDist = (
bandwidthUnitImplemented: usedUnitWidth, bandwidthUnitImplemented: usedUnitWidth,
} }
continuousPart continuousPart
|> Internals.T.kde( ->Internals.T.kde(
~samples=_, ~samples=_,
~outputXYPoints=samplingInputs.outputXYPoints, ~outputXYPoints=samplingInputs.outputXYPoints,
Internals.T.formatUnitWidth(usedUnitWidth), Internals.T.formatUnitWidth(usedUnitWidth),
) )
|> Continuous.make ->Continuous.make
|> (r => Some((r, samplingStats))) ->(r => Some((r, samplingStats)))
} }
: None : None
let pointSetDist = MixedShapeBuilder.buildSimple( let pointSetDist = MixedShapeBuilder.buildSimple(
~continuous=pdf |> E.O.fmap(fst), ~continuous=pdf->E.O2.fmap(fst),
~discrete=Some(discrete), ~discrete=Some(discrete),
) )
@ -125,7 +126,7 @@ let toPointSetDist = (
let normalizedPointSet = pointSetDist->E.O2.fmap(PointSetDist.T.normalize) let normalizedPointSet = pointSetDist->E.O2.fmap(PointSetDist.T.normalize)
let samplesParse: Internals.Types.outputs = { let samplesParse: Internals.Types.outputs = {
continuousParseParams: pdf |> E.O.fmap(snd), continuousParseParams: pdf->E.O2.fmap(snd),
pointSetDist: normalizedPointSet, pointSetDist: normalizedPointSet,
} }

View File

@ -0,0 +1,102 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let nameSpace = "" // no namespaced versions
type simpleDefinition = {
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
}
let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
Function.make(
~name,
~nameSpace,
~requiresNamespace=false,
~definitions=definitions->Js.Array2.map(({inputs, fn}) =>
FnDefinition.make(~name, ~inputs, ~run=(inputs, _, _) => fn(inputs), ())
),
(),
)
let makeFn = (
name: string,
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
) => makeFnMany(name, [{inputs: inputs, fn: fn}])
let library = [
Make.ff2f(~name="add", ~fn=(x, y) => x +. y, ()), // infix + (see Reducer/Reducer_Peggy/helpers.ts)
Make.ff2f(~name="subtract", ~fn=(x, y) => x -. y, ()), // infix -
Make.ff2f(~name="multiply", ~fn=(x, y) => x *. y, ()), // infix *
Make.ff2f(~name="divide", ~fn=(x, y) => x /. y, ()), // infix /
Make.ff2f(~name="pow", ~fn=(x, y) => Js.Math.pow_float(~base=x, ~exp=y), ()), // infix ^
Make.ff2b(~name="equal", ~fn=(x, y) => x == y, ()), // infix == on numbers
Make.bb2b(~name="equal", ~fn=(x, y) => x == y, ()), // infix == on booleans
Make.ff2b(~name="unequal", ~fn=(x, y) => x != y, ()), // infix != on numbers
Make.ff2b(~name="unequal", ~fn=(x, y) => x != y, ()), // infix != on booleans
Make.ff2b(~name="smaller", ~fn=(x, y) => x < y, ()), // infix <
Make.ff2b(~name="smallerEq", ~fn=(x, y) => x <= y, ()), // infix <=
Make.ff2b(~name="larger", ~fn=(x, y) => x > y, ()), // infix >
Make.ff2b(~name="largerEq", ~fn=(x, y) => x >= y, ()), // infix >=
Make.bb2b(~name="or", ~fn=(x, y) => x || y, ()), // infix ||
Make.bb2b(~name="and", ~fn=(x, y) => x && y, ()), // infix &&
Make.f2f(~name="unaryMinus", ~fn=x => -.x, ()), // unary prefix -
makeFn("not", [FRTypeNumber], inputs => {
// unary prefix !
switch inputs {
| [IEvNumber(x)] => IEvBool(x != 0.)->Ok
| _ => Error(impossibleError)
}
}),
makeFn("not", [FRTypeBool], inputs => {
// unary prefix !
switch inputs {
| [IEvBool(x)] => IEvBool(!x)->Ok
| _ => Error(impossibleError)
}
}),
makeFn("concat", [FRTypeString, FRTypeString], inputs => {
switch inputs {
| [IEvString(a), IEvString(b)] => {
let answer = Js.String2.concat(a, b)
answer->Reducer_T.IEvString->Ok
}
| _ => Error(impossibleError)
}
}),
makeFn("concat", [FRTypeArray(FRTypeAny), FRTypeArray(FRTypeAny)], inputs => {
switch inputs {
| [IEvArray(originalA), IEvArray(b)] => {
let a = originalA->Js.Array2.copy
let _ = Js.Array2.pushMany(a, b)
a->Reducer_T.IEvArray->Ok
}
| _ => Error(impossibleError)
}
}),
makeFn("inspect", [FRTypeAny], inputs => {
switch inputs {
| [value] => {
Js.log(value->Reducer_Value.toString)
value->Ok
}
| _ => Error(impossibleError)
}
}),
makeFn("inspect", [FRTypeAny, FRTypeString], inputs => {
switch inputs {
| [value, IEvString(label)] => {
Js.log(`${label}: ${value->Reducer_Value.toString}`)
value->Ok
}
| _ => Error(impossibleError)
}
}),
makeFn("javascriptraise", [FRTypeAny], inputs => {
switch inputs {
| [msg] => Js.Exn.raiseError(msg->Reducer_Value.toString)
| _ => Error(impossibleError)
}
}),
]

View File

@ -8,11 +8,11 @@ let requiresNamespace = true
module Combinatorics = { module Combinatorics = {
module Helpers = { module Helpers = {
let laplace = ((successes, trials)) => (successes +. 1.0) /. (trials +. 2.0) let laplace = (successes, trials) => (successes +. 1.0) /. (trials +. 2.0)
let factorial = Stdlib.Math.factorial let factorial = Stdlib.Math.factorial
let choose = ((n, k)) => factorial(n) /. (factorial(n -. k) *. factorial(k)) let choose = (n, k) => factorial(n) /. (factorial(n -. k) *. factorial(k))
let pow = (base, exp) => Js.Math.pow_float(~base, ~exp) let pow = (base, exp) => Js.Math.pow_float(~base, ~exp)
let binomial = ((n, k, p)) => choose((n, k)) *. pow(p, k) *. pow(1.0 -. p, n -. k) let binomial = (n, k, p) => choose(n, k) *. pow(p, k) *. pow(1.0 -. p, n -. k)
} }
module Lib = { module Lib = {
let laplace = Function.make( let laplace = Function.make(
@ -69,15 +69,15 @@ module Integration = {
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point) let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall( let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
aLambda, aLambda,
list{pointAsInternalExpression}, [pointAsInternalExpression],
environment, environment,
reducer, reducer,
) )
let result = switch resultAsInternalExpression { let result = switch resultAsInternalExpression {
| 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", "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,
) )
} }
result result
@ -132,7 +132,7 @@ module Integration = {
| (Ok(yMin), Ok(yMax)) => { | (Ok(yMin), Ok(yMax)) => {
let result = let result =
(yMin +. yMax) *. weightForAnOuterPoint +. innerPointsSum *. weightForAnInnerPoint (yMin +. yMax) *. weightForAnOuterPoint +. innerPointsSum *. weightForAnInnerPoint
let wrappedResult = result->ReducerInterface_InternalExpressionValue.IEvNumber->Ok let wrappedResult = result->Reducer_T.IEvNumber->Ok
wrappedResult wrappedResult
} }
| (Error(b), _) => Error(b) | (Error(b), _) => Error(b)
@ -141,11 +141,11 @@ module Integration = {
resultWithOuterPoints resultWithOuterPoints
} }
| Error(b) => | Error(b) =>
Error( ("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, b->Reducer_ErrorValue.errorToString)
) ->Reducer_ErrorValue.REOther
->Error
} }
result result
} }
@ -165,10 +165,12 @@ module Integration = {
FnDefinition.make( FnDefinition.make(
~name="integrateFunctionBetweenWithNumIntegrationPoints", ~name="integrateFunctionBetweenWithNumIntegrationPoints",
~inputs=[FRTypeLambda, FRTypeNumber, FRTypeNumber, FRTypeNumber], ~inputs=[FRTypeLambda, FRTypeNumber, FRTypeNumber, FRTypeNumber],
~run=(inputs, _, env, reducer) => { ~run=(inputs, env, reducer) => {
let result = switch inputs { let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] => | [_, _, _, IEvNumber(0.0)] =>
Error("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
->Error
| [ | [
IEvLambda(aLambda), IEvLambda(aLambda),
IEvNumber(min), IEvNumber(min),
@ -185,7 +187,9 @@ module Integration = {
) )
| _ => | _ =>
Error( Error(
Reducer_ErrorValue.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))",
),
) )
} }
result result
@ -205,10 +209,12 @@ module Integration = {
FnDefinition.make( FnDefinition.make(
~name="integrateFunctionBetweenWithEpsilon", ~name="integrateFunctionBetweenWithEpsilon",
~inputs=[FRTypeLambda, FRTypeNumber, FRTypeNumber, FRTypeNumber], ~inputs=[FRTypeLambda, FRTypeNumber, FRTypeNumber, FRTypeNumber],
~run=(inputs, _, env, reducer) => { ~run=(inputs, env, reducer) => {
let result = switch inputs { let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] => | [_, _, _, IEvNumber(0.0)] =>
Error("Integration error in Danger.integrate: Increment can't be 0.") "Integration error in Danger.integrate: Increment can't be 0."
->Reducer_ErrorValue.REOther
->Error
| [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] => | [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] =>
Helpers.integrateFunctionBetweenWithNumIntegrationPoints( Helpers.integrateFunctionBetweenWithNumIntegrationPoints(
aLambda, aLambda,
@ -218,12 +224,13 @@ module Integration = {
env, env,
reducer, reducer,
)->E.R2.errMap(b => )->E.R2.errMap(b =>
"Integration error 7 in Danger.integrate. Something went wrong along the way: " ++ b ("Integration error 7 in Danger.integrate. Something went wrong along the way: " ++
b->Reducer_ErrorValue.errorToString)->Reducer_ErrorValue.REOther
) )
| _ => | _ =>
Error( "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
) ->Error
} }
result result
}, },
@ -239,16 +246,16 @@ module DiminishingReturns = {
module Helpers = { module Helpers = {
type diminishingReturnsAccumulatorInner = { type diminishingReturnsAccumulatorInner = {
optimalAllocations: array<float>, optimalAllocations: array<float>,
currentMarginalReturns: result<array<float>, string>, currentMarginalReturns: result<array<float>, errorValue>,
} }
let findBiggestElementIndex = xs => let findBiggestElementIndex = (xs: array<float>) =>
E.A.reducei(xs, 0, (acc, newElement, index) => { E.A.reducei(xs, 0, (acc, newElement, index) => {
switch newElement > xs[acc] { switch newElement > xs[acc] {
| true => index | true => index
| false => acc | false => acc
} }
}) })
type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, string> type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, errorValue>
// 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
@ -283,19 +290,19 @@ module DiminishingReturns = {
) { ) {
| (false, _, _, _) => | (false, _, _, _) =>
Error( Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1.", "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->Reducer_ErrorValue.REOther,
) )
| (_, false, _, _) => | (_, false, _, _) =>
Error( Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0.", "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->Reducer_ErrorValue.REOther,
) )
| (_, _, false, _) => | (_, _, false, _) =>
Error( Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0.", "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->Reducer_ErrorValue.REOther,
) )
| (_, _, _, false) => | (_, _, _, false) =>
Error( Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount.", "Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->Reducer_ErrorValue.REOther,
) )
| (true, true, true, true) => { | (true, true, true, true) => {
let applyFunctionAtPoint = (lambda, point: float) => { let applyFunctionAtPoint = (lambda, point: float) => {
@ -303,15 +310,15 @@ module DiminishingReturns = {
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point) let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall( let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
lambda, lambda,
list{pointAsInternalExpression}, [pointAsInternalExpression],
environment, environment,
reducer, reducer,
) )
switch resultAsInternalExpression { switch resultAsInternalExpression {
| 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", "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,
) )
} }
} }
@ -396,16 +403,16 @@ module DiminishingReturns = {
FnDefinition.make( FnDefinition.make(
~name="optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions", ~name="optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions",
~inputs=[FRTypeArray(FRTypeLambda), FRTypeNumber, FRTypeNumber], ~inputs=[FRTypeArray(FRTypeLambda), FRTypeNumber, FRTypeNumber],
~run=(inputs, _, environment, reducer) => ~run=(inputs, environment, reducer) =>
switch inputs { switch inputs {
| [IEvArray(innerlambdas), IEvNumber(funds), IEvNumber(approximateIncrement)] => { | [IEvArray(innerlambdas), IEvNumber(funds), IEvNumber(approximateIncrement)] => {
let individuallyWrappedLambdas = E.A.fmap(innerLambda => { let individuallyWrappedLambdas = E.A.fmap(innerLambda => {
switch innerLambda { switch innerLambda {
| ReducerInterface_InternalExpressionValue.IEvLambda(lambda) => Ok(lambda) | Reducer_T.IEvLambda(lambda) => Ok(lambda)
| _ => | _ =>
Error( "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
) ->Error
} }
}, innerlambdas) }, innerlambdas)
let wrappedLambdas = E.A.R.firstErrorOrOpen(individuallyWrappedLambdas) let wrappedLambdas = E.A.R.firstErrorOrOpen(individuallyWrappedLambdas)
@ -424,7 +431,10 @@ module DiminishingReturns = {
} }
result result
} }
| _ => Error("Error in Danger.diminishingMarginalReturnsForTwoFunctions") | _ =>
"Error in Danger.diminishingMarginalReturnsForTwoFunctions"
->Reducer_ErrorValue.REOther
->Error
}, },
(), (),
), ),

View File

@ -0,0 +1,159 @@
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let makeFn = (
name: string,
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
) =>
Function.make(
~name,
~nameSpace="",
~requiresNamespace=false,
~definitions=[FnDefinition.make(~name, ~inputs, ~run=(inputs, _, _) => fn(inputs), ())],
(),
)
let makeNumberToDurationFn = (name: string, fn: float => DateTime.Duration.t) =>
Function.make(
~name,
~nameSpace="",
~requiresNamespace=false,
~definitions=[
FnDefinition.make(
~name,
~inputs=[FRTypeNumber],
~run=(inputs, _, _) =>
switch inputs {
| [IEvNumber(t)] => IEvTimeDuration(fn(t))->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
)
let makeDurationToNumberFn = (name: string, fn: DateTime.Duration.t => float) =>
Function.make(
~name,
~nameSpace="",
~requiresNamespace=false,
~definitions=[
FnDefinition.make(
~name,
~inputs=[FRTypeTimeDuration],
~run=(inputs, _, _) =>
switch inputs {
| [IEvTimeDuration(t)] => IEvNumber(fn(t))->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
)
let library = [
makeFn("toString", [FRTypeDate], inputs =>
switch inputs {
| [IEvDate(t)] => IEvString(DateTime.Date.toString(t))->Ok
| _ => Error(impossibleError)
}
),
makeFn("makeDateFromYear", [FRTypeNumber], inputs =>
switch inputs {
| [IEvNumber(year)] =>
switch DateTime.Date.makeFromYear(year) {
| Ok(t) => IEvDate(t)->Ok
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error
}
| _ => Error(impossibleError)
}
),
makeFn("dateFromNumber", [FRTypeNumber], inputs =>
switch inputs {
| [IEvNumber(f)] => IEvDate(DateTime.Date.fromFloat(f))->Ok
| _ => Error(impossibleError)
}
),
makeFn("toNumber", [FRTypeDate], inputs =>
switch inputs {
| [IEvDate(f)] => IEvNumber(DateTime.Date.toFloat(f))->Ok
| _ => Error(impossibleError)
}
),
makeFn("subtract", [FRTypeDate, FRTypeDate], inputs =>
switch inputs {
| [IEvDate(d1), IEvDate(d2)] =>
switch DateTime.Date.subtract(d1, d2) {
| Ok(d) => IEvTimeDuration(d)->Ok
| Error(e) => Error(RETodo(e))
}
| _ => Error(impossibleError)
}
),
makeFn("subtract", [FRTypeDate, FRTypeTimeDuration], inputs =>
switch inputs {
| [IEvDate(d1), IEvTimeDuration(d2)] => IEvDate(DateTime.Date.subtractDuration(d1, d2))->Ok
| _ => Error(impossibleError)
}
),
makeFn("add", [FRTypeDate, FRTypeTimeDuration], inputs =>
switch inputs {
| [IEvDate(d1), IEvTimeDuration(d2)] => IEvDate(DateTime.Date.addDuration(d1, d2))->Ok
| _ => Error(impossibleError)
}
),
makeFn("toString", [FRTypeTimeDuration], inputs =>
switch inputs {
| [IEvTimeDuration(t)] => IEvString(DateTime.Duration.toString(t))->Ok
| _ => Error(impossibleError)
}
),
makeNumberToDurationFn("minutes", DateTime.Duration.fromMinutes),
makeNumberToDurationFn("fromUnit_minutes", DateTime.Duration.fromMinutes),
makeNumberToDurationFn("hours", DateTime.Duration.fromHours),
makeNumberToDurationFn("fromUnit_hours", DateTime.Duration.fromHours),
makeNumberToDurationFn("days", DateTime.Duration.fromDays),
makeNumberToDurationFn("fromUnit_days", DateTime.Duration.fromDays),
makeNumberToDurationFn("years", DateTime.Duration.fromYears),
makeNumberToDurationFn("fromUnit_years", DateTime.Duration.fromYears),
makeDurationToNumberFn("toMinutes", DateTime.Duration.toMinutes),
makeDurationToNumberFn("toHours", DateTime.Duration.toHours),
makeDurationToNumberFn("toDays", DateTime.Duration.toDays),
makeDurationToNumberFn("toYears", DateTime.Duration.toYears),
makeFn("add", [FRTypeTimeDuration, FRTypeTimeDuration], inputs =>
switch inputs {
| [IEvTimeDuration(d1), IEvTimeDuration(d2)] =>
IEvTimeDuration(DateTime.Duration.add(d1, d2))->Ok
| _ => Error(impossibleError)
}
),
makeFn("subtract", [FRTypeTimeDuration, FRTypeTimeDuration], inputs =>
switch inputs {
| [IEvTimeDuration(d1), IEvTimeDuration(d2)] =>
IEvTimeDuration(DateTime.Duration.subtract(d1, d2))->Ok
| _ => Error(impossibleError)
}
),
makeFn("multiply", [FRTypeTimeDuration, FRTypeNumber], inputs =>
switch inputs {
| [IEvTimeDuration(d1), IEvNumber(d2)] =>
IEvTimeDuration(DateTime.Duration.multiply(d1, d2))->Ok
| _ => Error(impossibleError)
}
),
makeFn("divide", [FRTypeTimeDuration, FRTypeNumber], inputs =>
switch inputs {
| [IEvTimeDuration(d1), IEvNumber(d2)] => IEvTimeDuration(DateTime.Duration.divide(d1, d2))->Ok
| _ => Error(impossibleError)
}
),
makeFn("divide", [FRTypeTimeDuration, FRTypeTimeDuration], inputs =>
switch inputs {
| [IEvTimeDuration(d1), IEvTimeDuration(d2)] => IEvNumber(d1 /. d2)->Ok
| _ => Error(impossibleError)
}
),
]

View File

@ -4,23 +4,23 @@ open FunctionRegistry_Helpers
let nameSpace = "Dict" let nameSpace = "Dict"
module Internals = { module Internals = {
type t = ReducerInterface_InternalExpressionValue.map type t = Reducer_T.map
let keys = (a: t): internalExpressionValue => IEvArray( let keys = (a: t): Reducer_T.value => IEvArray(
Belt.Map.String.keysToArray(a)->E.A2.fmap(Wrappers.evString), Belt.Map.String.keysToArray(a)->E.A2.fmap(Wrappers.evString),
) )
let values = (a: t): internalExpressionValue => IEvArray(Belt.Map.String.valuesToArray(a)) let values = (a: t): Reducer_T.value => IEvArray(Belt.Map.String.valuesToArray(a))
let toList = (a: t): internalExpressionValue => let toList = (a: t): Reducer_T.value =>
Belt.Map.String.toArray(a) Belt.Map.String.toArray(a)
->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<internalExpressionValue>): result<internalExpressionValue, string> => let fromList = (items: array<Reducer_T.value>): result<Reducer_T.value, errorValue> =>
items items
->E.A2.fmap(item => { ->E.A2.fmap(item => {
switch (item: internalExpressionValue) { switch (item: Reducer_T.value) {
| IEvArray([IEvString(string), value]) => (string, value)->Ok | IEvArray([IEvString(string), value]) => (string, value)->Ok
| _ => Error(impossibleError) | _ => Error(impossibleError)
} }
@ -29,12 +29,8 @@ module Internals = {
->E.R2.fmap(Belt.Map.String.fromArray) ->E.R2.fmap(Belt.Map.String.fromArray)
->E.R2.fmap(Wrappers.evRecord) ->E.R2.fmap(Wrappers.evRecord)
let merge = (a: t, b: t): internalExpressionValue => IEvRecord(
Belt.Map.String.merge(a, b, (_, _, c) => c),
)
//Belt.Map.String has a function for mergeMany, but I couldn't understand how to use it yet. //Belt.Map.String has a function for mergeMany, but I couldn't understand how to use it yet.
let mergeMany = (a: array<t>): internalExpressionValue => { let mergeMany = (a: array<t>): Reducer_T.value => {
let mergedValues = let mergedValues =
a->E.A2.fmap(Belt.Map.String.toArray)->Belt.Array.concatMany->Belt.Map.String.fromArray a->E.A2.fmap(Belt.Map.String.toArray)->Belt.Array.concatMany->Belt.Map.String.fromArray
IEvRecord(mergedValues) IEvRecord(mergedValues)
@ -52,10 +48,10 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="merge", ~name="merge",
~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)], ~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)],
~run=(inputs, _, _, _) => { ~run=(inputs, _, _) => {
switch inputs { switch inputs {
| [IEvRecord(d1), IEvRecord(d2)] => Internals.merge(d1, d2)->Ok | [IEvRecord(d1), IEvRecord(d2)] => Internals.mergeMany([d1, d2])->Ok
| _ => Error(impossibleError) | _ => impossibleError->Error
} }
}, },
(), (),
@ -63,7 +59,6 @@ let library = [
], ],
(), (),
), ),
//TODO: Change to use new mergeMany() function.
Function.make( Function.make(
~name="mergeMany", ~name="mergeMany",
~nameSpace, ~nameSpace,
@ -74,13 +69,21 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="mergeMany", ~name="mergeMany",
~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))], ~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))],
~run=(_, inputs, _, _) => ~run=(inputs, _, _) => {
inputs switch inputs {
->Prepare.ToTypedArray.dicts | [IEvArray(dicts)] =>
->E.R2.fmap(E.Dict.concatMany) dicts
->E.R2.fmap(Js.Dict.map((. r) => FunctionRegistry_Core.FRType.matchReverse(r))) ->Belt.Array.map(dictValue =>
->E.R2.fmap(r => r->Js.Dict.entries->Belt.Map.String.fromArray) switch dictValue {
->E.R2.fmap(Wrappers.evRecord), | IEvRecord(dict) => dict
| _ => impossibleError->Reducer_ErrorValue.toException
}
)
->Internals.mergeMany
->Ok
| _ => impossibleError->Error
}
},
(), (),
), ),
], ],
@ -96,7 +99,7 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="keys", ~name="keys",
~inputs=[FRTypeDict(FRTypeAny)], ~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvRecord(d1)] => Internals.keys(d1)->Ok | [IEvRecord(d1)] => Internals.keys(d1)->Ok
| _ => Error(impossibleError) | _ => Error(impossibleError)
@ -116,7 +119,7 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="values", ~name="values",
~inputs=[FRTypeDict(FRTypeAny)], ~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvRecord(d1)] => Internals.values(d1)->Ok | [IEvRecord(d1)] => Internals.values(d1)->Ok
| _ => Error(impossibleError) | _ => Error(impossibleError)
@ -136,7 +139,7 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="toList", ~name="toList",
~inputs=[FRTypeDict(FRTypeAny)], ~inputs=[FRTypeDict(FRTypeAny)],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvRecord(dict)] => dict->Internals.toList->Ok | [IEvRecord(dict)] => dict->Internals.toList->Ok
| _ => Error(impossibleError) | _ => Error(impossibleError)
@ -156,7 +159,7 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="fromList", ~name="fromList",
~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))], ~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))],
~run=(inputs, _, _, _) => { ~run=(inputs, _, _) => {
switch inputs { switch inputs {
| [IEvArray(items)] => Internals.fromList(items) | [IEvArray(items)] => Internals.fromList(items)
| _ => Error(impossibleError) | _ => Error(impossibleError)

View File

@ -4,7 +4,7 @@ let twoArgs = E.Tuple2.toFnCall
module DistributionCreation = { module DistributionCreation = {
let nameSpace = "Dist" let nameSpace = "Dist"
let output = ReducerInterface_InternalExpressionValue.EvtDistribution let output = Reducer_Value.EvtDistribution
let requiresNamespace = false let requiresNamespace = false
let fnMake = (~name, ~examples, ~definitions) => { let fnMake = (~name, ~examples, ~definitions) => {
@ -16,13 +16,13 @@ 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))
let make = (name, fn) => { let make = (name, fn) => {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
~run=(_, inputs, accessors, _) => ~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env=accessors.environment),
(), (),
) )
} }
@ -31,10 +31,8 @@ module DistributionCreation = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])], ~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
~run=(_, inputs, accessors, _) => ~run=(inputs, env, _) =>
inputs inputs->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))->process(~fn, ~env),
->Prepare.ToValueTuple.Record.twoDistOrNumber
->process(~fn, ~env=accessors.environment),
(), (),
) )
} }
@ -43,10 +41,10 @@ module DistributionCreation = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])], ~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
~run=(_, inputs, accessors, _) => ~run=(inputs, env, _) =>
inputs inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber ->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev"))
->process(~fn, ~env=accessors.environment), ->process(~fn, ~env),
(), (),
) )
} }
@ -57,13 +55,13 @@ 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))
let make = (name, fn) => let make = (name, fn) =>
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeDistOrNumber], ~inputs=[FRTypeDistOrNumber],
~run=(_, inputs, accessors, _) => ~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env=accessors.environment),
(), (),
) )
} }

View File

@ -7,23 +7,21 @@ module Declaration = {
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))), ("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
]) ])
let fromExpressionValue = (e: frValue): result<internalExpressionValue, string> => { let fromExpressionValue = (e: Reducer_T.value): result<Reducer_T.value, string> => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs([e]) { switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs([e], ("fn", "inputs")) {
| Ok([FRValueLambda(lambda), FRValueArray(inputs)]) => { | Ok([IEvLambda(lambda), IEvArray(inputs)]) => {
open FunctionRegistry_Helpers.Prepare open FunctionRegistry_Helpers.Prepare
let getMinMax = arg => let getMinMax = arg =>
ToValueArray.Record.toArgs([arg]) ToValueArray.Record.twoArgs([arg], ("min", "max"))
->E.R.bind(ToValueTuple.twoNumbers) ->E.R.bind(ToValueTuple.twoNumbers)
->E.R2.fmap(((min, max)) => Declaration.ContinuousFloatArg.make(min, max)) ->E.R2.fmap(((min, max)) => Declaration.ContinuousFloatArg.make(min, max))
inputs inputs
->E.A2.fmap(getMinMax) ->E.A2.fmap(getMinMax)
->E.A.R.firstErrorOrOpen ->E.A.R.firstErrorOrOpen
->E.R2.fmap(args => ReducerInterface_InternalExpressionValue.IEvDeclaration( ->E.R2.fmap(args => Reducer_T.IEvDeclaration(Declaration.make(lambda, args)))
Declaration.make(lambda, args),
))
} }
| Error(r) => Error(r) | Error(r) => Error(r)
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError) | Ok(_) => Error(impossibleErrorString)
} }
} }
} }
@ -51,8 +49,8 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="declare", ~name="declare",
~inputs=[Declaration.frType], ~inputs=[Declaration.frType],
~run=(_, inputs, _, _) => { ~run=(inputs, _, _) => {
inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue) inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue)->E.R2.errMap(wrapError)
}, },
(), (),
), ),

View File

@ -0,0 +1,410 @@
open FunctionRegistry_Core
module Old = {
module Helpers = {
let arithmeticMap = r =>
switch r {
| "add" => #Add
| "dotAdd" => #Add
| "subtract" => #Subtract
| "dotSubtract" => #Subtract
| "divide" => #Divide
| "log" => #Logarithm
| "dotDivide" => #Divide
| "pow" => #Power
| "dotPow" => #Power
| "multiply" => #Multiply
| "dotMultiply" => #Multiply
| _ => #Multiply
}
let catchAndConvertTwoArgsToDists = (args: array<Reducer_T.value>): option<(
DistributionTypes.genericDist,
DistributionTypes.genericDist,
)> =>
switch args {
| [IEvDistribution(a), IEvDistribution(b)] => Some((a, b))
| [IEvNumber(a), IEvDistribution(b)] => Some((GenericDist.fromFloat(a), b))
| [IEvDistribution(a), IEvNumber(b)] => Some((a, GenericDist.fromFloat(b)))
| _ => None
}
let toFloatFn = (
fnCall: DistributionTypes.DistributionOperation.toFloat,
dist: DistributionTypes.genericDist,
~env: GenericDist.env,
) => {
FromDist(#ToFloat(fnCall), dist)->DistributionOperation.run(~env)->Some
}
let toStringFn = (
fnCall: DistributionTypes.DistributionOperation.toString,
dist: DistributionTypes.genericDist,
~env: GenericDist.env,
) => {
FromDist(#ToString(fnCall), dist)->DistributionOperation.run(~env)->Some
}
let toBoolFn = (
fnCall: DistributionTypes.DistributionOperation.toBool,
dist: DistributionTypes.genericDist,
~env: GenericDist.env,
) => {
FromDist(#ToBool(fnCall), dist)->DistributionOperation.run(~env)->Some
}
let toDistFn = (
fnCall: DistributionTypes.DistributionOperation.toDist,
dist,
~env: GenericDist.env,
) => {
FromDist(#ToDist(fnCall), dist)->DistributionOperation.run(~env)->Some
}
let twoDiststoDistFn = (direction, arithmetic, dist1, dist2, ~env: GenericDist.env) => {
FromDist(
#ToDistCombination(direction, arithmeticMap(arithmetic), #Dist(dist2)),
dist1,
)->DistributionOperation.run(~env)
}
let parseNumber = (args: Reducer_T.value): Belt.Result.t<float, string> =>
switch args {
| IEvNumber(x) => Ok(x)
| _ => Error("Not a number")
}
let parseNumberArray = (ags: array<Reducer_T.value>): Belt.Result.t<array<float>, string> =>
E.A.fmap(parseNumber, ags) |> E.A.R.firstErrorOrOpen
let parseDist = (args: Reducer_T.value): Belt.Result.t<DistributionTypes.genericDist, string> =>
switch args {
| IEvDistribution(x) => Ok(x)
| IEvNumber(x) => Ok(GenericDist.fromFloat(x))
| _ => Error("Not a distribution")
}
let parseDistributionArray = (ags: array<Reducer_T.value>): Belt.Result.t<
array<DistributionTypes.genericDist>,
string,
> => E.A.fmap(parseDist, ags) |> E.A.R.firstErrorOrOpen
let mixtureWithGivenWeights = (
distributions: array<DistributionTypes.genericDist>,
weights: array<float>,
~env: GenericDist.env,
): DistributionOperation.outputType =>
E.A.length(distributions) == E.A.length(weights)
? Mixture(Belt.Array.zip(distributions, weights))->DistributionOperation.run(~env)
: GenDistError(
ArgumentError("Error, mixture call has different number of distributions and weights"),
)
let mixtureWithDefaultWeights = (
distributions: array<DistributionTypes.genericDist>,
~env: GenericDist.env,
): DistributionOperation.outputType => {
let length = E.A.length(distributions)
let weights = Belt.Array.make(length, 1.0 /. Belt.Int.toFloat(length))
mixtureWithGivenWeights(distributions, weights, ~env)
}
let mixture = (
args: array<Reducer_T.value>,
~env: GenericDist.env,
): DistributionOperation.outputType => {
let error = (err: string): DistributionOperation.outputType =>
err->DistributionTypes.ArgumentError->GenDistError
switch args {
| [IEvArray(distributions)] =>
switch parseDistributionArray(distributions) {
| Ok(distrs) => mixtureWithDefaultWeights(distrs, ~env)
| Error(err) => error(err)
}
| [IEvArray(distributions), IEvArray(weights)] =>
switch (parseDistributionArray(distributions), parseNumberArray(weights)) {
| (Ok(distrs), Ok(wghts)) => mixtureWithGivenWeights(distrs, wghts, ~env)
| (Error(err), Ok(_)) => error(err)
| (Ok(_), Error(err)) => error(err)
| (Error(err1), Error(err2)) => error(`${err1}|${err2}`)
}
| _ =>
switch E.A.last(args) {
| Some(IEvArray(b)) => {
let weights = parseNumberArray(b)
let distributions = parseDistributionArray(
Belt.Array.slice(args, ~offset=0, ~len=E.A.length(args) - 1),
)
switch E.R.merge(distributions, weights) {
| Ok(d, w) => mixtureWithGivenWeights(d, w, ~env)
| Error(err) => error(err)
}
}
| Some(IEvNumber(_))
| Some(IEvDistribution(_)) =>
switch parseDistributionArray(args) {
| Ok(distributions) => mixtureWithDefaultWeights(distributions, ~env)
| Error(err) => error(err)
}
| _ => error("Last argument of mx must be array or distribution")
}
}
}
}
module SymbolicConstructors = {
let threeFloat = name =>
switch name {
| "triangular" => Ok(SymbolicDist.Triangular.make)
| _ => Error("Unreachable state")
}
let symbolicResultToOutput = (
symbolicResult: result<SymbolicDistTypes.symbolicDist, string>,
): option<DistributionOperation.outputType> =>
switch symbolicResult {
| Ok(r) => Some(Dist(Symbolic(r)))
| Error(r) => Some(GenDistError(OtherError(r)))
}
}
let dispatchToGenericOutput = (call: Reducer_Value.functionCall, env: GenericDist.env): option<
DistributionOperation.outputType,
> => {
let (fnName, args) = call
switch (fnName, args) {
| ("triangular" as fnName, [IEvNumber(f1), IEvNumber(f2), IEvNumber(f3)]) =>
SymbolicConstructors.threeFloat(fnName)
->E.R.bind(r => r(f1, f2, f3))
->SymbolicConstructors.symbolicResultToOutput
| ("sample", [IEvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist, ~env)
| ("sampleN", [IEvDistribution(dist), IEvNumber(n)]) =>
Some(FloatArray(GenericDist.sampleN(dist, Belt.Int.fromFloat(n))))
| (("mean" | "stdev" | "variance" | "min" | "max" | "mode") as op, [IEvDistribution(dist)]) => {
let fn = switch op {
| "mean" => #Mean
| "stdev" => #Stdev
| "variance" => #Variance
| "min" => #Min
| "max" => #Max
| "mode" => #Mode
| _ => #Mean
}
Helpers.toFloatFn(fn, dist, ~env)
}
| ("integralSum", [IEvDistribution(dist)]) => Helpers.toFloatFn(#IntegralSum, dist, ~env)
| ("toString", [IEvDistribution(dist)]) => Helpers.toStringFn(ToString, dist, ~env)
| ("sparkline", [IEvDistribution(dist)]) =>
Helpers.toStringFn(ToSparkline(MagicNumbers.Environment.sparklineLength), dist, ~env)
| ("sparkline", [IEvDistribution(dist), IEvNumber(n)]) =>
Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist, ~env)
| ("exp", [IEvDistribution(a)]) =>
// https://mathjs.org/docs/reference/functions/exp.html
Helpers.twoDiststoDistFn(
Algebraic(AsDefault),
"pow",
GenericDist.fromFloat(MagicNumbers.Math.e),
a,
~env,
)->Some
| ("normalize", [IEvDistribution(dist)]) => Helpers.toDistFn(Normalize, dist, ~env)
| ("isNormalized", [IEvDistribution(dist)]) => Helpers.toBoolFn(IsNormalized, dist, ~env)
| ("toPointSet", [IEvDistribution(dist)]) => Helpers.toDistFn(ToPointSet, dist, ~env)
| ("scaleLog", [IEvDistribution(dist)]) =>
Helpers.toDistFn(Scale(#Logarithm, MagicNumbers.Math.e), dist, ~env)
| ("scaleLog10", [IEvDistribution(dist)]) =>
Helpers.toDistFn(Scale(#Logarithm, 10.0), dist, ~env)
| ("scaleLog", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toDistFn(Scale(#Logarithm, float), dist, ~env)
| ("scaleLogWithThreshold", [IEvDistribution(dist), IEvNumber(base), IEvNumber(eps)]) =>
Helpers.toDistFn(Scale(#LogarithmWithThreshold(eps), base), dist, ~env)
| ("scaleMultiply", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toDistFn(Scale(#Multiply, float), dist, ~env)
| ("scalePow", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toDistFn(Scale(#Power, float), dist, ~env)
| ("scaleExp", [IEvDistribution(dist)]) =>
Helpers.toDistFn(Scale(#Power, MagicNumbers.Math.e), dist, ~env)
| ("cdf", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toFloatFn(#Cdf(float), dist, ~env)
| ("pdf", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toFloatFn(#Pdf(float), dist, ~env)
| ("inv", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toFloatFn(#Inv(float), dist, ~env)
| ("quantile", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toFloatFn(#Inv(float), dist, ~env)
| ("inspect", [IEvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist, ~env)
| ("truncateLeft", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toDistFn(Truncate(Some(float), None), dist, ~env)
| ("truncateRight", [IEvDistribution(dist), IEvNumber(float)]) =>
Helpers.toDistFn(Truncate(None, Some(float)), dist, ~env)
| ("truncate", [IEvDistribution(dist), IEvNumber(float1), IEvNumber(float2)]) =>
Helpers.toDistFn(Truncate(Some(float1), Some(float2)), dist, ~env)
| ("mx" | "mixture", args) => Helpers.mixture(args, ~env)->Some
| ("log", [IEvDistribution(a)]) =>
Helpers.twoDiststoDistFn(
Algebraic(AsDefault),
"log",
a,
GenericDist.fromFloat(MagicNumbers.Math.e),
~env,
)->Some
| ("log10", [IEvDistribution(a)]) =>
Helpers.twoDiststoDistFn(
Algebraic(AsDefault),
"log",
a,
GenericDist.fromFloat(10.0),
~env,
)->Some
| ("unaryMinus", [IEvDistribution(a)]) =>
Helpers.twoDiststoDistFn(
Algebraic(AsDefault),
"multiply",
a,
GenericDist.fromFloat(-1.0),
~env,
)->Some
| (
("add" | "multiply" | "subtract" | "divide" | "pow" | "log") as arithmetic,
[_, _] as args,
) =>
Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) =>
Helpers.twoDiststoDistFn(Algebraic(AsDefault), arithmetic, fst, snd, ~env)
)
| (
("dotAdd"
| "dotMultiply"
| "dotSubtract"
| "dotDivide"
| "dotPow") as arithmetic,
[_, _] as args,
) =>
Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) =>
Helpers.twoDiststoDistFn(Pointwise, arithmetic, fst, snd, ~env)
)
| ("dotExp", [IEvDistribution(a)]) =>
Helpers.twoDiststoDistFn(
Pointwise,
"dotPow",
GenericDist.fromFloat(MagicNumbers.Math.e),
a,
~env,
)->Some
| _ => None
}
}
let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
Reducer_T.value,
Reducer_ErrorValue.errorValue,
> =>
switch o {
| Dist(d) => Ok(Reducer_T.IEvDistribution(d))
| Float(d) => Ok(IEvNumber(d))
| String(d) => Ok(IEvString(d))
| Bool(d) => Ok(IEvBool(d))
| FloatArray(d) => Ok(IEvArray(d |> E.A.fmap(r => Reducer_T.IEvNumber(r))))
| GenDistError(err) => Error(REDistributionError(err))
}
let dispatch = (call: Reducer_Value.functionCall, environment) =>
switch dispatchToGenericOutput(call, environment) {
| Some(o) => genericOutputToReducerValue(o)
| None =>
Reducer_ErrorValue.REOther("Internal error in FR_GenericDist implementation")
->Reducer_ErrorValue.ErrorException
->raise
}
}
let makeProxyFn = (name: string, inputs: array<frType>) => {
Function.make(
~name,
~nameSpace="",
~requiresNamespace=false,
~definitions=[
FnDefinition.make(
~name,
~inputs,
~run=(inputs, env, _) => Old.dispatch((name, inputs), env),
(),
),
],
(),
)
}
let makeOperationFns = (): array<function> => {
let ops = [
"add",
"multiply",
"subtract",
"divide",
"pow",
"log",
"dotAdd",
"dotMultiply",
"dotSubtract",
"dotDivide",
"dotPow",
]
let twoArgTypes = [
// can't use numeric+numeric, since number+number should be delegated to builtin arithmetics
[FRTypeDist, FRTypeNumber],
[FRTypeNumber, FRTypeDist],
[FRTypeDist, FRTypeDist],
]
ops->E.A2.fmap(op => twoArgTypes->E.A2.fmap(types => makeProxyFn(op, types)))->E.A.concatMany
}
// TODO - duplicates the switch above, should rewrite with standard FR APIs
let library = E.A.concatMany([
[
makeProxyFn("triangular", [FRTypeNumber, FRTypeNumber, FRTypeNumber]),
makeProxyFn("sample", [FRTypeDist]),
makeProxyFn("sampleN", [FRTypeDist, FRTypeNumber]),
makeProxyFn("mean", [FRTypeDist]),
makeProxyFn("stdev", [FRTypeDist]),
makeProxyFn("variance", [FRTypeDist]),
makeProxyFn("min", [FRTypeDist]),
makeProxyFn("max", [FRTypeDist]),
makeProxyFn("mode", [FRTypeDist]),
makeProxyFn("integralSum", [FRTypeDist]),
makeProxyFn("toString", [FRTypeDist]),
makeProxyFn("sparkline", [FRTypeDist]),
makeProxyFn("sparkline", [FRTypeDist, FRTypeNumber]),
makeProxyFn("exp", [FRTypeDist]),
makeProxyFn("normalize", [FRTypeDist]),
makeProxyFn("isNormalized", [FRTypeDist]),
makeProxyFn("toPointSet", [FRTypeDist]),
makeProxyFn("scaleLog", [FRTypeDist]),
makeProxyFn("scaleLog10", [FRTypeDist]),
makeProxyFn("scaleLog", [FRTypeDist, FRTypeNumber]),
makeProxyFn("scaleLogWithThreshold", [FRTypeDist, FRTypeNumber, FRTypeNumber]),
makeProxyFn("scaleMultiply", [FRTypeDist, FRTypeNumber]),
makeProxyFn("scalePow", [FRTypeDist, FRTypeNumber]),
makeProxyFn("scaleExp", [FRTypeDist]),
makeProxyFn("cdf", [FRTypeDist, FRTypeNumber]),
makeProxyFn("pdf", [FRTypeDist, FRTypeNumber]),
makeProxyFn("inv", [FRTypeDist, FRTypeNumber]),
makeProxyFn("quantile", [FRTypeDist, FRTypeNumber]),
makeProxyFn("inspect", [FRTypeDist]),
makeProxyFn("truncateLeft", [FRTypeDist, FRTypeNumber]),
makeProxyFn("truncateRight", [FRTypeDist, FRTypeNumber]),
makeProxyFn("truncate", [FRTypeDist, FRTypeNumber, FRTypeNumber]),
makeProxyFn("log", [FRTypeDist]),
makeProxyFn("log10", [FRTypeDist]),
makeProxyFn("unaryMinus", [FRTypeDist]),
makeProxyFn("dotExp", [FRTypeDist]),
],
makeOperationFns(),
])
// FIXME - impossible to implement with FR due to arbitrary parameters length;
let mxLambda = Reducer_Expression_Lambda.makeFFILambda((inputs, env, _) => {
switch Old.dispatch(("mx", inputs), env) {
| Ok(value) => value
| Error(e) => e->Reducer_ErrorValue.ErrorException->raise
}
})

View File

@ -1,6 +1,3 @@
// module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
// module ProjectReducerFnT = ReducerProject_ReducerFn_T
open FunctionRegistry_Core open FunctionRegistry_Core
open FunctionRegistry_Helpers open FunctionRegistry_Helpers
@ -8,38 +5,32 @@ let nameSpace = "List"
let requiresNamespace = true let requiresNamespace = true
module Internals = { module Internals = {
let makeFromNumber = ( let makeFromNumber = (n: float, value: Reducer_T.value): Reducer_T.value => IEvArray(
n: float, Belt.Array.make(E.Float.toInt(n), value),
value: internalExpressionValue, )
): internalExpressionValue => IEvArray(Belt.Array.make(E.Float.toInt(n), value))
let upTo = (low: float, high: float): internalExpressionValue => IEvArray( let upTo = (low: float, high: float): Reducer_T.value => IEvArray(
E.A.Floats.range(low, high, (high -. low +. 1.0)->E.Float.toInt)->E.A2.fmap(Wrappers.evNumber), E.A.Floats.range(low, high, (high -. low +. 1.0)->E.Float.toInt)->E.A2.fmap(Wrappers.evNumber),
) )
let first = (v: array<internalExpressionValue>): result<internalExpressionValue, string> => let first = (v: array<Reducer_T.value>): result<Reducer_T.value, string> =>
v->E.A.first |> E.O.toResult("No first element") v->E.A.first |> E.O.toResult("No first element")
let last = (v: array<internalExpressionValue>): result<internalExpressionValue, string> => let last = (v: array<Reducer_T.value>): result<Reducer_T.value, string> =>
v->E.A.last |> E.O.toResult("No last element") v->E.A.last |> E.O.toResult("No last element")
let reverse = (array: array<internalExpressionValue>): internalExpressionValue => IEvArray( let reverse = (array: array<Reducer_T.value>): Reducer_T.value => IEvArray(
Belt.Array.reverse(array), Belt.Array.reverse(array),
) )
let map = ( let map = (
array: array<internalExpressionValue>, array: array<Reducer_T.value>,
accessors: ProjectAccessorsT.t,
eLambdaValue, eLambdaValue,
reducer: ProjectReducerFnT.t, env: Reducer_T.environment,
): ReducerInterface_InternalExpressionValue.t => { reducer: Reducer_T.reducerFn,
): Reducer_T.value => {
Belt.Array.map(array, elem => Belt.Array.map(array, elem =>
Reducer_Expression_Lambda.doLambdaCall( Reducer_Expression_Lambda.doLambdaCall(eLambdaValue, [elem], env, reducer)
eLambdaValue,
list{elem},
(accessors: ProjectAccessorsT.t),
(reducer: ProjectReducerFnT.t),
)
)->Wrappers.evArray )->Wrappers.evArray
} }
@ -47,11 +38,11 @@ module Internals = {
aValueArray, aValueArray,
initialValue, initialValue,
aLambdaValue, aLambdaValue,
accessors: ProjectAccessorsT.t, env: Reducer_T.environment,
reducer: ProjectReducerFnT.t, reducer: Reducer_T.reducerFn,
) => { ) => {
aValueArray->E.A.reduce(initialValue, (acc, elem) => aValueArray->E.A.reduce(initialValue, (acc, elem) =>
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, accessors, reducer) Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer)
) )
} }
@ -59,27 +50,22 @@ module Internals = {
aValueArray, aValueArray,
initialValue, initialValue,
aLambdaValue, aLambdaValue,
accessors: ProjectAccessorsT.t, env: Reducer_T.environment,
reducer: ProjectReducerFnT.t, reducer: Reducer_T.reducerFn,
) => { ) => {
aValueArray->Belt.Array.reduceReverse(initialValue, (acc, elem) => aValueArray->Belt.Array.reduceReverse(initialValue, (acc, elem) =>
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, accessors, reducer) Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer)
) )
} }
let filter = ( let filter = (
aValueArray, aValueArray,
aLambdaValue, aLambdaValue,
accessors: ProjectAccessorsT.t, env: Reducer_T.environment,
reducer: ProjectReducerFnT.t, reducer: Reducer_T.reducerFn,
) => { ) => {
Js.Array2.filter(aValueArray, elem => { Js.Array2.filter(aValueArray, elem => {
let result = Reducer_Expression_Lambda.doLambdaCall( let result = Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [elem], env, reducer)
aLambdaValue,
list{elem},
accessors,
reducer,
)
switch result { switch result {
| IEvBool(true) => true | IEvBool(true) => true
| _ => false | _ => false
@ -100,7 +86,7 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="make", ~name="make",
~inputs=[FRTypeNumber, FRTypeAny], ~inputs=[FRTypeNumber, FRTypeAny],
~run=(inputs, _, _, _) => { ~run=(inputs, _, _) => {
switch inputs { switch inputs {
| [IEvNumber(number), value] => Internals.makeFromNumber(number, value)->Ok | [IEvNumber(number), value] => Internals.makeFromNumber(number, value)->Ok
| _ => Error(impossibleError) | _ => Error(impossibleError)
@ -121,10 +107,11 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="upTo", ~name="upTo",
~inputs=[FRTypeNumber, FRTypeNumber], ~inputs=[FRTypeNumber, FRTypeNumber],
~run=(_, inputs, _, _) => ~run=(inputs, _, _) =>
inputs switch inputs {
->Prepare.ToValueTuple.twoNumbers | [IEvNumber(low), IEvNumber(high)] => Internals.upTo(low, high)->Ok
->E.R2.fmap(((low, high)) => Internals.upTo(low, high)), | _ => impossibleError->Error
},
(), (),
), ),
], ],
@ -139,9 +126,9 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="first", ~name="first",
~inputs=[FRTypeArray(FRTypeAny)], ~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvArray(array)] => Internals.first(array) | [IEvArray(array)] => Internals.first(array)->E.R2.errMap(wrapError)
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -158,9 +145,9 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="last", ~name="last",
~inputs=[FRTypeArray(FRTypeAny)], ~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvArray(array)] => Internals.last(array) | [IEvArray(array)] => Internals.last(array)->E.R2.errMap(wrapError)
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -178,7 +165,7 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="reverse", ~name="reverse",
~inputs=[FRTypeArray(FRTypeAny)], ~inputs=[FRTypeArray(FRTypeAny)],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvArray(array)] => Internals.reverse(array)->Ok | [IEvArray(array)] => Internals.reverse(array)->Ok
| _ => Error(impossibleError) | _ => Error(impossibleError)
@ -198,10 +185,9 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="map", ~name="map",
~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda], ~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer) => ~run=(inputs, env, reducer) =>
switch inputs { switch inputs {
| [IEvArray(array), IEvLambda(lambda)] => | [IEvArray(array), IEvLambda(lambda)] => Ok(Internals.map(array, lambda, env, reducer))
Ok(Internals.map(array, accessors, lambda, reducer))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -218,10 +204,10 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="reduce", ~name="reduce",
~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda], ~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer) => ~run=(inputs, env, reducer) =>
switch inputs { switch inputs {
| [IEvArray(array), initialValue, IEvLambda(lambda)] => | [IEvArray(array), initialValue, IEvLambda(lambda)] =>
Ok(Internals.reduce(array, initialValue, lambda, accessors, reducer)) Ok(Internals.reduce(array, initialValue, lambda, env, reducer))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -238,10 +224,10 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="reduceReverse", ~name="reduceReverse",
~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda], ~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer: ProjectReducerFnT.t) => ~run=(inputs, env, reducer) =>
switch inputs { switch inputs {
| [IEvArray(array), initialValue, IEvLambda(lambda)] => | [IEvArray(array), initialValue, IEvLambda(lambda)] =>
Ok(Internals.reduceReverse(array, initialValue, lambda, accessors, reducer)) Ok(Internals.reduceReverse(array, initialValue, lambda, env, reducer))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -258,10 +244,10 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="filter", ~name="filter",
~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda], ~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer: ProjectReducerFnT.t) => ~run=(inputs, env, reducer) =>
switch inputs { switch inputs {
| [IEvArray(array), IEvLambda(lambda)] => | [IEvArray(array), IEvLambda(lambda)] =>
Ok(Internals.filter(array, lambda, accessors, reducer)) Ok(Internals.filter(array, lambda, env, reducer))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),

View File

@ -0,0 +1,21 @@
open FunctionRegistry_Helpers
let library = [
// ported MathJS functions
// https://docs.google.com/spreadsheets/d/1bUK2RaBFg8aJHuzZcw9yXp8StCBH5If5sU2iRw1T_HY/edit
// TODO - implement the rest of useful stuff
Make.f2f(
~name="sqrt",
~nameSpace="Math",
~requiresNamespace=true,
~fn=x => Js.Math.pow_float(~base=x, ~exp=0.5),
(),
),
Make.f2f(~name="sin", ~nameSpace="Math", ~requiresNamespace=true, ~fn=Js.Math.sin, ()),
Make.f2f(~name="cos", ~nameSpace="Math", ~requiresNamespace=true, ~fn=Js.Math.cos, ()),
Make.f2f(~name="tan", ~nameSpace="Math", ~requiresNamespace=true, ~fn=Js.Math.tan, ()),
Make.f2f(~name="asin", ~nameSpace="Math", ~requiresNamespace=true, ~fn=Js.Math.asin, ()),
Make.f2f(~name="acos", ~nameSpace="Math", ~requiresNamespace=true, ~fn=Js.Math.acos, ()),
Make.f2f(~name="atan", ~nameSpace="Math", ~requiresNamespace=true, ~fn=Js.Math.atan, ()),
]

View File

@ -9,34 +9,24 @@ module ArrayNumberDist = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeArray(FRTypeNumber)], ~inputs=[FRTypeArray(FRTypeNumber)],
~run=(_, inputs, _, _) => ~run=(inputs, _, _) =>
Prepare.ToTypedArray.numbers(inputs) inputs
->Prepare.ToTypedArray.numbers
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r)) ->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
->E.R.bind(fn), ->E.R.bind(fn)
(), ->E.R2.errMap(wrapError),
)
}
let make2 = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeArray(FRTypeAny)],
~run=(_, inputs, _, _) =>
Prepare.ToTypedArray.numbers(inputs)
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
->E.R.bind(fn),
(), (),
) )
} }
} }
let library = [ let library = [
Function.make( Make.f2f(
~name="floor", ~name="floor",
~nameSpace, ~nameSpace,
~requiresNamespace, ~requiresNamespace,
~output=EvtNumber,
~examples=[`floor(3.5)`], ~examples=[`floor(3.5)`],
~definitions=[DefineFn.Numbers.oneToOne("floor", Js.Math.floor_float)], ~fn=Js.Math.floor_float,
(), (),
), ),
Function.make( Function.make(

View File

@ -4,45 +4,48 @@ open FunctionRegistry_Helpers
let nameSpace = "PointSet" let nameSpace = "PointSet"
let requiresNamespace = true let requiresNamespace = true
let inputsTodist = (inputs: array<FunctionRegistry_Core.frValue>, makeDist) => { let inputsToDist = (inputs: array<Reducer_T.value>, xyShapeToPointSetDist) => {
let array = inputs->getOrError(0)->E.R.bind(Prepare.ToValueArray.Array.openA) // TODO - rewrite in more functional/functor-based style
let xyCoords = switch inputs {
array->E.R.bind(xyCoords => | [IEvArray(items)] =>
xyCoords items
->E.A2.fmap(xyCoord => ->Belt.Array.map(item =>
[xyCoord]->Prepare.ToValueArray.Record.twoArgs->E.R.bind(Prepare.ToValueTuple.twoNumbers) switch item {
| IEvRecord(map) => {
let xValue = map->Belt.Map.String.get("x")
let yValue = map->Belt.Map.String.get("y")
switch (xValue, yValue) {
| (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y)
| _ => impossibleError->Reducer_ErrorValue.toException
}
}
| _ => impossibleError->Reducer_ErrorValue.toException
}
) )
->E.A.R.firstErrorOrOpen ->Ok
)
let expressionValue =
xyCoords
->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 => ReducerInterface_InternalExpressionValue.IEvDistribution( ->E.R2.fmap(r => Reducer_T.IEvDistribution(PointSet(r->xyShapeToPointSetDist)))
PointSet(makeDist(r)), | _ => impossibleError->Reducer_ErrorValue.toException
)) }
expressionValue
} }
module Internal = { module Internal = {
type t = PointSetDist.t type t = PointSetDist.t
let toType = (r): result< let toType = (r): result<Reducer_T.value, Reducer_ErrorValue.errorValue> =>
ReducerInterface_InternalExpressionValue.t,
Reducer_ErrorValue.errorValue,
> =>
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, environment, reducer) => let doLambdaCall = (aLambdaValue, list, env, reducer) =>
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, environment, reducer) { switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
| IEvNumber(f) => Ok(f) | Reducer_T.IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction) | _ => Error(Operation.SampleMapNeedsNtoNFunction)
} }
let mapY = (pointSetDist: t, aLambdaValue, env, reducer) => { let mapY = (pointSetDist: t, aLambdaValue, env, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, env, reducer) let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], env, reducer)
PointSetDist.T.mapYResult(~fn, pointSetDist)->toType PointSetDist.T.mapYResult(~fn, pointSetDist)->toType
} }
} }
@ -53,23 +56,23 @@ let library = [
~nameSpace, ~nameSpace,
~requiresNamespace=true, ~requiresNamespace=true,
~examples=[`PointSet.fromDist(normal(5,2))`], ~examples=[`PointSet.fromDist(normal(5,2))`],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="fromDist", ~name="fromDist",
~inputs=[FRTypeDist], ~inputs=[FRTypeDist],
~run=(_, inputs, accessors, _) => ~run=(inputs, env, _) =>
switch inputs { switch inputs {
| [FRValueDist(dist)] => | [IEvDistribution(dist)] =>
GenericDist.toPointSet( GenericDist.toPointSet(
dist, dist,
~xyPointLength=accessors.environment.xyPointLength, ~xyPointLength=env.xyPointLength,
~sampleCount=accessors.environment.sampleCount, ~sampleCount=env.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.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -82,15 +85,15 @@ let library = [
~nameSpace, ~nameSpace,
~requiresNamespace=true, ~requiresNamespace=true,
~examples=[`PointSet.mapY(mx(normal(5,2)), {|x| x + 1})`], ~examples=[`PointSet.mapY(mx(normal(5,2)), {|x| x + 1})`],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="mapY", ~name="mapY",
~inputs=[FRTypeDist, FRTypeLambda], ~inputs=[FRTypeDist, FRTypeLambda],
~run=(inputs, _, env, reducer) => ~run=(inputs, env, reducer) =>
switch inputs { switch inputs {
| [IEvDistribution(PointSet(dist)), IEvLambda(lambda)] => | [IEvDistribution(PointSet(dist)), IEvLambda(lambda)] =>
Internal.mapY(dist, lambda, env, reducer)->E.R2.errMap(Reducer_ErrorValue.errorToString) Internal.mapY(dist, lambda, env, reducer)
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -110,12 +113,13 @@ let library = [
{x: 3, y: 0.2} {x: 3, y: 0.2}
])`, ])`,
], ],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="makeContinuous", ~name="makeContinuous",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))], ~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(_, inputs, _, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))), ~run=(inputs, _, _) =>
inputsToDist(inputs, r => Continuous(Continuous.make(r)))->E.R2.errMap(wrapError),
(), (),
), ),
], ],
@ -133,12 +137,13 @@ let library = [
{x: 3, y: 0.2} {x: 3, y: 0.2}
])`, ])`,
], ],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="makeDiscrete", ~name="makeDiscrete",
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))], ~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
~run=(_, inputs, _, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))), ~run=(inputs, _, _) =>
inputsToDist(inputs, r => Discrete(Discrete.make(r)))->E.R2.errMap(wrapError),
(), (),
), ),
], ],

View File

@ -1,5 +1,3 @@
// module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
// module ProjectReducerFnT = ReducerProject_ReducerFn_T
open FunctionRegistry_Core open FunctionRegistry_Core
open FunctionRegistry_Helpers open FunctionRegistry_Helpers
@ -12,51 +10,46 @@ module Internal = {
let doLambdaCall = ( let doLambdaCall = (
aLambdaValue, aLambdaValue,
list, list,
accessors: ProjectAccessorsT.t, env: Reducer_T.environment,
reducer: ProjectReducerFnT.t, reducer: Reducer_T.reducerFn,
) => ) =>
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, accessors, reducer) { switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
| IEvNumber(f) => Ok(f) | IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction) | _ => Error(Operation.SampleMapNeedsNtoNFunction)
} }
let toType = (r): result< let toType = (r): result<Reducer_T.value, Reducer_ErrorValue.errorValue> =>
ReducerInterface_InternalExpressionValue.t,
Reducer_ErrorValue.errorValue,
> =>
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, accessors: ProjectAccessorsT.t, reducer: ProjectReducerFnT.t) => { let fromFn = (aLambdaValue, environment: Reducer_T.environment, reducer: Reducer_T.reducerFn) => {
let sampleCount = accessors.environment.sampleCount let sampleCount = environment.sampleCount
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, accessors, reducer) let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, 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, accessors: ProjectAccessorsT.t, reducer) => { let map1 = (sampleSetDist: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, accessors, reducer) let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer)
SampleSetDist.samplesMap(~fn, sampleSetDist)->toType SampleSetDist.samplesMap(~fn, sampleSetDist)->toType
} }
let map2 = (t1: t, t2: t, aLambdaValue, accessors: ProjectAccessorsT.t, reducer) => { let map2 = (t1: t, t2: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = (a, b) => let fn = (a, b) =>
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b)}, accessors, 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, accessors: ProjectAccessorsT.t, reducer) => { let map3 = (t1: t, t2: t, t3: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = (a, b, c) => let fn = (a, b, c) =>
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)}, accessors, reducer) doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], environment, reducer)
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
} }
let parseSampleSetArray = (arr: array<internalExpressionValue>): option< let parseSampleSetArray = (arr: array<Reducer_T.value>): option<array<SampleSetDist.t>> => {
array<SampleSetDist.t>, let parseSampleSet = (value: Reducer_T.value): option<SampleSetDist.t> =>
> => {
let parseSampleSet = (value: internalExpressionValue): option<SampleSetDist.t> =>
switch value { switch value {
| IEvDistribution(SampleSet(dist)) => Some(dist) | IEvDistribution(SampleSet(dist)) => Some(dist)
| _ => None | _ => None
@ -65,9 +58,9 @@ module Internal = {
} }
let mapN = ( let mapN = (
aValueArray: array<internalExpressionValue>, aValueArray: array<Reducer_T.value>,
aLambdaValue, aLambdaValue,
accessors: ProjectAccessorsT.t, environment: Reducer_T.environment,
reducer, reducer,
) => { ) => {
switch parseSampleSetArray(aValueArray) { switch parseSampleSetArray(aValueArray) {
@ -75,8 +68,8 @@ module Internal = {
let fn = a => let fn = a =>
doLambdaCall( doLambdaCall(
aLambdaValue, aLambdaValue,
list{IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))}, [IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))],
accessors, environment,
reducer, reducer,
) )
SampleSetDist.mapN(~fn, ~t1)->toType SampleSetDist.mapN(~fn, ~t1)->toType
@ -91,18 +84,18 @@ let libaryBase = [
~nameSpace, ~nameSpace,
~requiresNamespace=true, ~requiresNamespace=true,
~examples=[`SampleSet.fromDist(normal(5,2))`], ~examples=[`SampleSet.fromDist(normal(5,2))`],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="fromDist", ~name="fromDist",
~inputs=[FRTypeDist], ~inputs=[FRTypeDist],
~run=(_, inputs, accessors: ProjectAccessorsT.t, _) => ~run=(inputs, environment, _) =>
switch inputs { switch inputs {
| [FRValueDist(dist)] => | [IEvDistribution(dist)] =>
GenericDist.toSampleSetDist(dist, accessors.environment.sampleCount) GenericDist.toSampleSetDist(dist, 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(DistributionTypes.Error.toString) ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e))
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -115,17 +108,19 @@ let libaryBase = [
~nameSpace, ~nameSpace,
~requiresNamespace=true, ~requiresNamespace=true,
~examples=[`SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])`], ~examples=[`SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])`],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="fromList", ~name="fromList",
~inputs=[FRTypeArray(FRTypeNumber)], ~inputs=[FRTypeArray(FRTypeNumber)],
~run=(_, inputs, _, _) => { ~run=(inputs, _, _) => {
let sampleSet = let sampleSet =
Prepare.ToTypedArray.numbers(inputs) |> E.R2.bind(r => inputs->Prepare.ToTypedArray.numbers
SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??") |> E.R2.bind(r => SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??"))
) sampleSet
sampleSet->E.R2.fmap(Wrappers.sampleSet)->E.R2.fmap(Wrappers.evDistribution) ->E.R2.fmap(Wrappers.sampleSet)
->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(wrapError)
}, },
(), (),
), ),
@ -137,12 +132,12 @@ let libaryBase = [
~nameSpace, ~nameSpace,
~requiresNamespace=true, ~requiresNamespace=true,
~examples=[`SampleSet.toList(SampleSet.fromDist(normal(5,2)))`], ~examples=[`SampleSet.toList(SampleSet.fromDist(normal(5,2)))`],
~output=ReducerInterface_InternalExpressionValue.EvtArray, ~output=Reducer_Value.EvtArray,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="toList", ~name="toList",
~inputs=[FRTypeDist], ~inputs=[FRTypeDist],
~run=(inputs, _, _, _) => ~run=(inputs, _, _) =>
switch inputs { switch inputs {
| [IEvDistribution(SampleSet(dist))] => | [IEvDistribution(SampleSet(dist))] =>
dist->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok dist->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
@ -158,17 +153,17 @@ let libaryBase = [
~nameSpace, ~nameSpace,
~requiresNamespace=true, ~requiresNamespace=true,
~examples=[`SampleSet.fromFn({|| sample(normal(5,2))})`], ~examples=[`SampleSet.fromFn({|| sample(normal(5,2))})`],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="fromFn", ~name="fromFn",
~inputs=[FRTypeLambda], ~inputs=[FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer: ProjectReducerFnT.t) => ~run=(inputs, environment, reducer) =>
switch inputs { switch inputs {
| [IEvLambda(lambda)] => | [IEvLambda(lambda)] =>
switch Internal.fromFn(lambda, accessors, 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) => Error(Operation.Error.toString(e)) | Error(e) => e->Reducer_ErrorValue.REOperationError->Error
} }
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
@ -182,15 +177,15 @@ let libaryBase = [
~nameSpace, ~nameSpace,
~requiresNamespace, ~requiresNamespace,
~examples=[`SampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1})`], ~examples=[`SampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1})`],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="map", ~name="map",
~inputs=[FRTypeDist, FRTypeLambda], ~inputs=[FRTypeDist, FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer) => ~run=(inputs, environment, reducer) =>
switch inputs { switch inputs {
| [IEvDistribution(SampleSet(dist)), IEvLambda(lambda)] => | [IEvDistribution(SampleSet(dist)), IEvLambda(lambda)] =>
Internal.map1(dist, lambda, accessors, reducer)->E.R2.errMap(_ => "") Internal.map1(dist, lambda, environment, reducer)
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -205,19 +200,19 @@ let libaryBase = [
~examples=[ ~examples=[
`SampleSet.map2(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y| x + y})`, `SampleSet.map2(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y| x + y})`,
], ],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="map2", ~name="map2",
~inputs=[FRTypeDist, FRTypeDist, FRTypeLambda], ~inputs=[FRTypeDist, FRTypeDist, FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer) => { ~run=(inputs, environment, reducer) => {
switch inputs { switch inputs {
| [ | [
IEvDistribution(SampleSet(dist1)), IEvDistribution(SampleSet(dist1)),
IEvDistribution(SampleSet(dist2)), IEvDistribution(SampleSet(dist2)),
IEvLambda(lambda), IEvLambda(lambda),
] => ] =>
Internal.map2(dist1, dist2, lambda, accessors, reducer)->E.R2.errMap(_ => "") Internal.map2(dist1, dist2, lambda, environment, reducer)
| _ => Error(impossibleError) | _ => Error(impossibleError)
} }
}, },
@ -233,12 +228,12 @@ let libaryBase = [
~examples=[ ~examples=[
`SampleSet.map3(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y, z| max([x,y,z])})`, `SampleSet.map3(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y, z| max([x,y,z])})`,
], ],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="map3", ~name="map3",
~inputs=[FRTypeDist, FRTypeDist, FRTypeDist, FRTypeLambda], ~inputs=[FRTypeDist, FRTypeDist, FRTypeDist, FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer) => ~run=(inputs, environment, reducer) =>
switch inputs { switch inputs {
| [ | [
IEvDistribution(SampleSet(dist1)), IEvDistribution(SampleSet(dist1)),
@ -246,7 +241,7 @@ let libaryBase = [
IEvDistribution(SampleSet(dist3)), IEvDistribution(SampleSet(dist3)),
IEvLambda(lambda), IEvLambda(lambda),
] => ] =>
Internal.map3(dist1, dist2, dist3, lambda, accessors, reducer)->E.R2.errMap(_ => "") Internal.map3(dist1, dist2, dist3, lambda, environment, reducer)
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -261,17 +256,15 @@ let libaryBase = [
~examples=[ ~examples=[
`SampleSet.mapN([SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2))], {|x| max(x)})`, `SampleSet.mapN([SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2))], {|x| max(x)})`,
], ],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="mapN", ~name="mapN",
~inputs=[FRTypeArray(FRTypeDist), FRTypeLambda], ~inputs=[FRTypeArray(FRTypeDist), FRTypeLambda],
~run=(inputs, _, accessors: ProjectAccessorsT.t, reducer) => ~run=(inputs, environment, reducer) =>
switch inputs { switch inputs {
| [IEvArray(dists), IEvLambda(lambda)] => | [IEvArray(dists), IEvLambda(lambda)] =>
Internal.mapN(dists, lambda, accessors, reducer)->E.R2.errMap(_e => { Internal.mapN(dists, lambda, environment, reducer)
"AHHH doesn't work"
})
| _ => Error(impossibleError) | _ => Error(impossibleError)
}, },
(), (),
@ -286,7 +279,7 @@ module Comparison = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs, ~inputs,
~run=(inputs, _, _, _) => { ~run=(inputs, _, _) => {
run(inputs) run(inputs)
}, },
(), (),
@ -296,7 +289,9 @@ module Comparison = {
let wrapper = r => let wrapper = r =>
r r
->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution) ->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution)
->E.R2.errMap(SampleSetDist.Error.toString) ->E.R2.errMap(e =>
e->DistributionTypes.Error.sampleErrorToDistErr->Reducer_ErrorValue.REDistributionError
)
let mkBig = (name, withDist, withFloat) => let mkBig = (name, withDist, withFloat) =>
Function.make( Function.make(
@ -308,7 +303,7 @@ module Comparison = {
`SampleSet.${name}(SampleSet.fromDist(normal(5,2)), 3.0)`, `SampleSet.${name}(SampleSet.fromDist(normal(5,2)), 3.0)`,
`SampleSet.${name}(4.0, SampleSet.fromDist(normal(6,2)))`, `SampleSet.${name}(4.0, SampleSet.fromDist(normal(6,2)))`,
], ],
~output=ReducerInterface_InternalExpressionValue.EvtDistribution, ~output=Reducer_Value.EvtDistribution,
~definitions=[ ~definitions=[
template(name, [FRTypeDist, FRTypeDist], inputs => { template(name, [FRTypeDist, FRTypeDist], inputs => {
switch inputs { switch inputs {

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(DistributionTypes.Error.toString) ->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e))
} }
let library = [ let library = [
@ -30,17 +30,16 @@ let library = [
("prior", FRTypeDist), ("prior", FRTypeDist),
]), ]),
], ],
~run=(_, inputs, accessors, _) => { ~run=(inputs, environment, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs) { switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d)), FRValueDist(prior)]) => inputs,
runScoring(estimate, Score_Dist(d), Some(prior), accessors.environment) ("estimate", "answer", "prior"),
| Ok([ ) {
FRValueDist(estimate), | Ok([IEvDistribution(estimate), IEvDistribution(d), IEvDistribution(prior)]) =>
FRValueDistOrNumber(FRValueNumber(d)), runScoring(estimate, Score_Dist(d), Some(prior), environment)
FRValueDist(prior), | Ok([IEvDistribution(estimate), IEvNumber(d), IEvDistribution(prior)]) =>
]) => runScoring(estimate, Score_Scalar(d), Some(prior), environment)
runScoring(estimate, Score_Scalar(d), Some(prior), accessors.environment) | Error(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| Error(e) => Error(e)
| _ => Error(FunctionRegistry_Helpers.impossibleError) | _ => Error(FunctionRegistry_Helpers.impossibleError)
} }
}, },
@ -49,13 +48,16 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="logScore", ~name="logScore",
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])], ~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
~run=(_, inputs, accessors, _) => { ~run=(inputs, environment, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs) { switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d))]) => inputs,
runScoring(estimate, Score_Dist(d), None, accessors.environment) ("estimate", "answer"),
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueNumber(d))]) => ) {
runScoring(estimate, Score_Scalar(d), None, accessors.environment) | Ok([IEvDistribution(estimate), IEvDistribution(d)]) =>
| Error(e) => Error(e) runScoring(estimate, Score_Dist(d), None, environment)
| Ok([IEvDistribution(estimate), IEvNumber(d)]) =>
runScoring(estimate, Score_Scalar(d), None, environment)
| Error(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError) | _ => Error(FunctionRegistry_Helpers.impossibleError)
} }
}, },
@ -74,10 +76,10 @@ let library = [
FnDefinition.make( FnDefinition.make(
~name="klDivergence", ~name="klDivergence",
~inputs=[FRTypeDist, FRTypeDist], ~inputs=[FRTypeDist, FRTypeDist],
~run=(_, inputs, accessors, _) => { ~run=(inputs, environment, _) => {
switch inputs { switch inputs {
| [FRValueDist(estimate), FRValueDist(d)] => | [IEvDistribution(estimate), IEvDistribution(d)] =>
runScoring(estimate, Score_Dist(d), None, accessors.environment) runScoring(estimate, Score_Dist(d), None, environment)
| _ => Error(FunctionRegistry_Helpers.impossibleError) | _ => Error(FunctionRegistry_Helpers.impossibleError)
} }
}, },

View File

@ -0,0 +1,34 @@
open FunctionRegistry_Core
let makeUnitFn = (name: string, multiplier: float) => {
Function.make(
~name="fromUnit_" ++ name,
~nameSpace="",
~requiresNamespace=false,
~definitions=[
FnDefinition.make(
~name="fromUnit_" ++ name,
~inputs=[FRTypeNumber],
~run=(inputs, _, _) => {
switch inputs {
| [IEvNumber(f)] => IEvNumber(f *. multiplier)->Ok
| _ => FunctionRegistry_Helpers.impossibleError->Error
}
},
(),
),
],
(),
)
}
let library = [
makeUnitFn("n", 1E-9),
makeUnitFn("m", 1E-3),
makeUnitFn("k", 1E3),
makeUnitFn("M", 1E6),
makeUnitFn("B", 1E9),
makeUnitFn("G", 1E9),
makeUnitFn("T", 1E12),
makeUnitFn("P", 1E15),
]

View File

@ -8,7 +8,7 @@
type environment = ForTS_Distribution_Environment.environment //use type environment = ForTS_Distribution_Environment.environment //use
@genType @genType
let defaultEnvironment: environment = DistributionOperation.defaultEnv let defaultEnvironment: environment = Reducer_Context.defaultEnvironment
@module("./ForTS_Distribution_tag") @scope("distributionTag") @module("./ForTS_Distribution_tag") @scope("distributionTag")
external dtPointSet_: string = "PointSet" external dtPointSet_: string = "PointSet"

View File

@ -1,14 +1,14 @@
@genType type reducerProject = ReducerProject_T.t //re-export @genType type reducerProject = ReducerProject_T.project //re-export
type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use
type squiggleValue = ForTS_SquiggleValue.squiggleValue //use type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
type squiggleValue_Module = ForTS_SquiggleValue_Module.squiggleValue_Module //use type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use
type environment = ForTS_Distribution_Environment.environment //use type environment = ForTS_Distribution_Environment.environment //use
module T = ReducerProject_T module T = ReducerProject_T
module Private = ReducerProject.Private module Private = ReducerProject
/* /*
PUBLIC FUNCTIONS PUBLIC FUNCTIONS
@ -35,35 +35,34 @@ A project has a public field tag with a constant value "reducerProject"
project = {tag: "reducerProject"} project = {tag: "reducerProject"}
*/ */
@genType @genType
let createProject = (): reducerProject => Private.createProject()->T.Private.castFromInternalProject let createProject = (): reducerProject => Private.createProject()
/* /*
Answer all the source ids of all the sources in the project. Answer all the source ids of all the sources in the project.
*/ */
@genType @genType
let getSourceIds = (project: reducerProject): array<string> => let getSourceIds = (project: reducerProject): array<string> => project->Private.getSourceIds
project->T.Private.castToInternalProject->Private.getSourceIds
/* /*
Sets the source for a given source Id. Sets the source for a given source Id.
*/ */
@genType @genType
let setSource = (project: reducerProject, sourceId: string, value: string): unit => let setSource = (project: reducerProject, sourceId: string, value: string): unit =>
project->T.Private.castToInternalProject->Private.setSource(sourceId, value) project->Private.setSource(sourceId, value)
/* /*
Gets the source for a given source id. Gets the source for a given source id.
*/ */
@genType @genType
let getSource = (project: reducerProject, sourceId: string): option<string> => let getSource = (project: reducerProject, sourceId: string): option<string> =>
project->T.Private.castToInternalProject->Private.getSource(sourceId) project->Private.getSource(sourceId)
/* /*
Touches the source for a given source id. This and dependent, sources are set to be re-evaluated. Touches the source for a given source id. This and dependent, sources are set to be re-evaluated.
*/ */
@genType @genType
let touchSource = (project: reducerProject, sourceId: string): unit => let touchSource = (project: reducerProject, sourceId: string): unit =>
project->T.Private.castToInternalProject->Private.touchSource(sourceId) project->Private.touchSource(sourceId)
/* /*
Cleans the compilation artifacts for a given source ID. The results stay untouched, so compilation won't be run again. Cleans the compilation artifacts for a given source ID. The results stay untouched, so compilation won't be run again.
@ -71,15 +70,13 @@ Cleans the compilation artifacts for a given source ID. The results stay untouch
Normally, you would never need the compilation artifacts again as the results with the same sources would never change. However, they are needed in case of any debugging reruns Normally, you would never need the compilation artifacts again as the results with the same sources would never change. However, they are needed in case of any debugging reruns
*/ */
@genType @genType
let clean = (project: reducerProject, sourceId: string): unit => let clean = (project: reducerProject, sourceId: string): unit => project->Private.clean(sourceId)
project->T.Private.castToInternalProject->Private.clean(sourceId)
/* /*
Cleans all the compilation artifacts in all of the project Cleans all the compilation artifacts in all of the project
*/ */
@genType @genType
let cleanAll = (project: reducerProject): unit => let cleanAll = (project: reducerProject): unit => project->Private.cleanAll
project->T.Private.castToInternalProject->Private.cleanAll
/* /*
Cleans results. Compilation stays untouched to be able to re-run the source. Cleans results. Compilation stays untouched to be able to re-run the source.
@ -87,14 +84,13 @@ You would not do this if you were not trying to debug the source code.
*/ */
@genType @genType
let cleanResults = (project: reducerProject, sourceId: string): unit => let cleanResults = (project: reducerProject, sourceId: string): unit =>
project->T.Private.castToInternalProject->Private.cleanResults(sourceId) project->Private.cleanResults(sourceId)
/* /*
Cleans all results. Compilations remains untouched to rerun the source. Cleans all results. Compilations remains untouched to rerun the source.
*/ */
@genType @genType
let cleanAllResults = (project: reducerProject): unit => let cleanAllResults = (project: reducerProject): unit => project->Private.cleanAllResults
project->T.Private.castToInternalProject->Private.cleanAllResults
/* /*
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.
@ -103,19 +99,19 @@ To set the includes one first has to call "parseIncludes". The parsed includes o
let getIncludes = (project: reducerProject, sourceId: string): result< let getIncludes = (project: reducerProject, sourceId: string): result<
array<string>, array<string>,
reducerErrorValue, reducerErrorValue,
> => project->T.Private.castToInternalProject->Private.getIncludes(sourceId) > => 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
let getPastChain = (project: reducerProject, sourceId: string): array<string> => let getPastChain = (project: reducerProject, sourceId: string): array<string> =>
project->T.Private.castToInternalProject->Private.getPastChain(sourceId) project->Private.getPastChain(sourceId)
/* /*
Answers the source codes after which this source code is continuing Answers the source codes after which this source code is continuing
*/ */
@genType @genType
let getContinues = (project: reducerProject, sourceId: string): array<string> => let getContinues = (project: reducerProject, sourceId: string): array<string> =>
project->T.Private.castToInternalProject->Private.getContinues(sourceId) project->Private.getContinues(sourceId)
/* /*
"continues" acts like hidden includes in the source. "continues" acts like hidden includes in the source.
@ -124,35 +120,34 @@ let getContinues = (project: reducerProject, sourceId: string): array<string> =>
*/ */
@genType @genType
let setContinues = (project: reducerProject, sourceId: string, continues: array<string>): unit => let setContinues = (project: reducerProject, sourceId: string, continues: array<string>): unit =>
project->T.Private.castToInternalProject->Private.setContinues(sourceId, continues) project->Private.setContinues(sourceId, continues)
/* /*
This source depends on the array of sources returned. This source depends on the array of sources returned.
*/ */
@genType @genType
let getDependencies = (project: reducerProject, sourceId: string): array<string> => let getDependencies = (project: reducerProject, sourceId: string): array<string> =>
project->T.Private.castToInternalProject->Private.getDependencies(sourceId) project->Private.getDependencies(sourceId)
/* /*
The sources returned are dependent on this The sources returned are dependent on this
*/ */
@genType @genType
let getDependents = (project: reducerProject, sourceId: string): array<string> => let getDependents = (project: reducerProject, sourceId: string): array<string> =>
project->T.Private.castToInternalProject->Private.getDependents(sourceId) project->Private.getDependents(sourceId)
/* /*
Get the run order for the sources in the project. Get the run order for the sources in the project.
*/ */
@genType @genType
let getRunOrder = (project: reducerProject): array<string> => let getRunOrder = (project: reducerProject): array<string> => project->Private.getRunOrder
project->T.Private.castToInternalProject->Private.getRunOrder
/* /*
Get the run order to get the results of this specific source Get the run order to get the results of this specific source
*/ */
@genType @genType
let getRunOrderFor = (project: reducerProject, sourceId: string) => let getRunOrderFor = (project: reducerProject, sourceId: string) =>
project->T.Private.castToInternalProject->Private.getRunOrderFor(sourceId) project->Private.getRunOrderFor(sourceId)
/* /*
Parse includes so that you can load them before running. Parse includes so that you can load them before running.
@ -162,7 +157,7 @@ It is your responsibility to load the includes before running.
@genType @genType
let parseIncludes = (project: reducerProject, sourceId: string): unit => let parseIncludes = (project: reducerProject, sourceId: string): unit =>
project->T.Private.castToInternalProject->Private.parseIncludes(sourceId) project->Private.parseIncludes(sourceId)
/* /*
Parse the source code if it is not done already. Parse the source code if it is not done already.
@ -171,28 +166,26 @@ You would need this function if you want to see the parse tree without running t
*/ */
@genType @genType
let rawParse = (project: reducerProject, sourceId: string): unit => let rawParse = (project: reducerProject, sourceId: string): unit =>
project->T.Private.castToInternalProject->Private.rawParse(sourceId) project->Private.rawParse(sourceId)
/* /*
Runs a specific source code if it is not done already. The code is parsed if it is not already done. It runs the dependencies if it is not already done. Runs a specific source code if it is not done already. The code is parsed if it is not already done. It runs the dependencies if it is not already done.
*/ */
@genType @genType
let run = (project: reducerProject, sourceId: string): unit => let run = (project: reducerProject, sourceId: string): unit => project->Private.run(sourceId)
project->T.Private.castToInternalProject->Private.run(sourceId)
/* /*
Runs all of the sources in a project. Their results and bindings will be available Runs all of the sources in a project. Their results and bindings will be available
*/ */
@genType @genType
let runAll = (project: reducerProject): unit => let runAll = (project: reducerProject): unit => project->Private.runAll
project->T.Private.castToInternalProject->Private.runAll
/* /*
Get the bindings after running this source fil. The bindings are local to the source Get the bindings after running this source fil. The bindings are local to the source
*/ */
@genType @genType
let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Module => let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Record =>
project->T.Private.castToInternalProject->Private.getBindings(sourceId) project->Private.getBindings(sourceId)->Reducer_Namespace.toMap
/* /*
Get the result after running this source file or the project Get the result after running this source file or the project
@ -201,7 +194,7 @@ Get the result after running this source file or the project
let getResult = (project: reducerProject, sourceId: string): result< let getResult = (project: reducerProject, sourceId: string): result<
squiggleValue, squiggleValue,
reducerErrorValue, reducerErrorValue,
> => project->T.Private.castToInternalProject->Private.getResult(sourceId) > => 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.
@ -211,12 +204,15 @@ The source has to be include free
@genType @genType
let evaluate = (sourceCode: string): ( let evaluate = (sourceCode: string): (
result<squiggleValue, reducerErrorValue>, result<squiggleValue, reducerErrorValue>,
squiggleValue_Module, squiggleValue_Record,
) => Private.evaluate(sourceCode) ) => Private.evaluate(sourceCode)
@genType @genType
let setEnvironment = (project: reducerProject, environment: environment): unit => let setEnvironment = (project: reducerProject, environment: environment): unit =>
project->T.Private.castToInternalProject->Private.setEnvironment(environment) project->Private.setEnvironment(environment)
@genType
let getEnvironment = (project: reducerProject): environment => project->Private.getEnvironment
/* /*
Foreign function interface is intentionally demolished. Foreign function interface is intentionally demolished.

View File

@ -1,10 +1,8 @@
@genType type squiggleValue = ReducerInterface_InternalExpressionValue.t //re-export @genType type squiggleValue = Reducer_T.value //re-export
type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use
@genType type squiggleValue_Array = ReducerInterface_InternalExpressionValue.squiggleArray //re-export recursive type @genType type squiggleValue_Array = Reducer_T.arrayValue //re-export recursive type
@genType type squiggleValue_Module = ReducerInterface_InternalExpressionValue.nameSpace //re-export recursive type @genType type squiggleValue_Record = Reducer_T.map //re-export recursive type
@genType type squiggleValue_Record = ReducerInterface_InternalExpressionValue.map //re-export recursive type
@genType type squiggleValue_Type = ReducerInterface_InternalExpressionValue.map //re-export recursive type
type squiggleValue_Declaration = ForTS_SquiggleValue_Declaration.squiggleValue_Declaration //use type squiggleValue_Declaration = ForTS_SquiggleValue_Declaration.squiggleValue_Declaration //use
type squiggleValue_Distribution = ForTS_SquiggleValue_Distribution.squiggleValue_Distribution //use type squiggleValue_Distribution = ForTS_SquiggleValue_Distribution.squiggleValue_Distribution //use
type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //use type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //use
@ -14,15 +12,9 @@ type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //us
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtArray_: string = "Array" external svtArray_: string = "Array"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtArrayString_: string = "ArrayString"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtBool_: string = "Bool" external svtBool_: string = "Bool"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtCall_: string = "Call"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtDate_: string = "Date" external svtDate_: string = "Date"
@ -35,9 +27,6 @@ external svtDistribution_: string = "Distribution"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtLambda_: string = "Lambda" external svtLambda_: string = "Lambda"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtModule_: string = "Module"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtNumber_: string = "Number" external svtNumber_: string = "Number"
@ -47,18 +36,9 @@ external svtRecord_: string = "Record"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtString_: string = "String" external svtString_: string = "String"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtSymbol_: string = "Symbol"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtTimeDuration_: string = "TimeDuration" external svtTimeDuration_: string = "TimeDuration"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtType_: string = "Type"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtTypeIdentifier_: string = "TypeIdentifier"
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
external svtVoid_: string = "Void" external svtVoid_: string = "Void"
@ -71,33 +51,26 @@ external castEnum: string => squiggleValueTag = "%identity"
let getTag = (variant: squiggleValue): squiggleValueTag => let getTag = (variant: squiggleValue): squiggleValueTag =>
switch variant { switch variant {
| IEvArray(_) => svtArray_->castEnum | IEvArray(_) => svtArray_->castEnum
| IEvArrayString(_) => svtArrayString_->castEnum
| IEvBool(_) => svtBool_->castEnum | IEvBool(_) => svtBool_->castEnum
| IEvCall(_) => svtCall_->castEnum //Impossible
| IEvDate(_) => svtDate_->castEnum | IEvDate(_) => svtDate_->castEnum
| IEvDeclaration(_) => svtDeclaration_->castEnum | IEvDeclaration(_) => svtDeclaration_->castEnum
| IEvDistribution(_) => svtDistribution_->castEnum | IEvDistribution(_) => svtDistribution_->castEnum
| IEvLambda(_) => svtLambda_->castEnum | IEvLambda(_) => svtLambda_->castEnum
| IEvBindings(_) => svtModule_->castEnum //Impossible
| IEvNumber(_) => svtNumber_->castEnum | IEvNumber(_) => svtNumber_->castEnum
| IEvRecord(_) => svtRecord_->castEnum | IEvRecord(_) => svtRecord_->castEnum
| IEvString(_) => svtString_->castEnum | IEvString(_) => svtString_->castEnum
| IEvSymbol(_) => svtSymbol_->castEnum
| IEvTimeDuration(_) => svtTimeDuration_->castEnum | IEvTimeDuration(_) => svtTimeDuration_->castEnum
| IEvType(_) => svtType_->castEnum
| IEvTypeIdentifier(_) => svtTypeIdentifier_->castEnum
| IEvVoid => svtVoid_->castEnum | IEvVoid => svtVoid_->castEnum
} }
@genType @genType
let toString = (variant: squiggleValue) => let toString = (variant: squiggleValue) => Reducer_Value.toString(variant)
ReducerInterface_InternalExpressionValue.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, reducerErrorValue>) =>
ReducerInterface_InternalExpressionValue.toStringResult(variantResult) Reducer_Value.toStringResult(variantResult)
@genType @genType
let getArray = (variant: squiggleValue): option<squiggleValue_Array> => let getArray = (variant: squiggleValue): option<squiggleValue_Array> =>
@ -107,13 +80,6 @@ let getArray = (variant: squiggleValue): option<squiggleValue_Array> =>
| _ => None | _ => None
} }
@genType
let getArrayString = (variant: squiggleValue): option<array<string>> =>
switch variant {
| IEvArrayString(value) => value->Some
| _ => None
}
@genType @genType
let getBool = (variant: squiggleValue): option<bool> => let getBool = (variant: squiggleValue): option<bool> =>
switch variant { switch variant {
@ -121,13 +87,6 @@ let getBool = (variant: squiggleValue): option<bool> =>
| _ => None | _ => None
} }
@genType
let getCall = (variant: squiggleValue): option<string> =>
switch variant {
| IEvCall(value) => value->Some
| _ => None
}
@genType @genType
let getDate = (variant: squiggleValue): option<Js.Date.t> => let getDate = (variant: squiggleValue): option<Js.Date.t> =>
switch variant { switch variant {
@ -156,13 +115,6 @@ let getLambda = (variant: squiggleValue): option<squiggleValue_Lambda> =>
| _ => None | _ => None
} }
@genType
let getModule = (variant: squiggleValue): option<squiggleValue_Module> =>
switch variant {
| IEvBindings(value) => value->Some
| _ => None
}
@genType @genType
let getNumber = (variant: squiggleValue): option<float> => let getNumber = (variant: squiggleValue): option<float> =>
switch variant { switch variant {
@ -184,30 +136,9 @@ let getString = (variant: squiggleValue): option<string> =>
| _ => None | _ => None
} }
@genType
let getSymbol = (variant: squiggleValue): option<string> =>
switch variant {
| IEvSymbol(value) => value->Some
| _ => None
}
@genType @genType
let getTimeDuration = (variant: squiggleValue): option<float> => let getTimeDuration = (variant: squiggleValue): option<float> =>
switch variant { switch variant {
| IEvTimeDuration(value) => value->Some | IEvTimeDuration(value) => value->Some
| _ => None | _ => None
} }
@genType
let getType = (variant: squiggleValue): option<squiggleValue_Type> =>
switch variant {
| IEvType(value) => value->Some
| _ => None
}
@genType
let getTypeIdentifier = (variant: squiggleValue): option<string> =>
switch variant {
| IEvTypeIdentifier(value) => value->Some
| _ => None
}

View File

@ -2,9 +2,7 @@ type squiggleValue = ForTS_SquiggleValue.squiggleValue
@genType type squiggleValue_Array = ForTS_SquiggleValue.squiggleValue_Array //re-export recursive type @genType type squiggleValue_Array = ForTS_SquiggleValue.squiggleValue_Array //re-export recursive type
@genType @genType
let getValues = (v: squiggleValue_Array): array<squiggleValue> => let getValues = (v: squiggleValue_Array): array<squiggleValue> => Reducer_Value.arrayToValueArray(v)
ReducerInterface_InternalExpressionValue.arrayToValueArray(v)
@genType @genType
let toString = (v: squiggleValue_Array): string => let toString = (v: squiggleValue_Array): string => Reducer_Value.toStringArray(v)
ReducerInterface_InternalExpressionValue.toStringArray(v)

View File

@ -1,5 +1,4 @@
@genType type squiggleValue_Declaration = ReducerInterface_InternalExpressionValue.lambdaDeclaration //re-export @genType type squiggleValue_Declaration = Reducer_T.lambdaDeclaration //re-export
@genType @genType
let toString = (v: squiggleValue_Declaration): string => let toString = (v: squiggleValue_Declaration): string => Reducer_Value.toStringDeclaration(v)
ReducerInterface_InternalExpressionValue.toStringDeclaration(v)

View File

@ -1,5 +1,4 @@
@genType type squiggleValue_Distribution = ForTS_Distribution.distribution @genType type squiggleValue_Distribution = ForTS_Distribution.distribution
@genType @genType
let toString = (v: squiggleValue_Distribution): string => let toString = (v: squiggleValue_Distribution): string => Reducer_Value.toStringDistribution(v)
ReducerInterface_InternalExpressionValue.toStringDistribution(v)

View File

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

View File

@ -1,16 +0,0 @@
type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
@genType type squiggleValue_Module = ForTS_SquiggleValue.squiggleValue_Module //re-export recursive type
@genType
let getKeyValuePairs = (v: squiggleValue_Module): array<(string, squiggleValue)> =>
ReducerInterface_InternalExpressionValue.nameSpaceToKeyValuePairs(v)
@genType
let toString = (v: squiggleValue_Module): string =>
ReducerInterface_InternalExpressionValue.toStringNameSpace(v)
@genType
let toSquiggleValue = (v: squiggleValue_Module): squiggleValue => IEvBindings(v)
@genType
let get = ReducerInterface_InternalExpressionValue.nameSpaceGet

View File

@ -3,7 +3,10 @@ type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
@genType @genType
let getKeyValuePairs = (value: squiggleValue_Record): array<(string, squiggleValue)> => let getKeyValuePairs = (value: squiggleValue_Record): array<(string, squiggleValue)> =>
ReducerInterface_InternalExpressionValue.recordToKeyValuePairs(value) Reducer_Value.recordToKeyValuePairs(value)
@genType @genType
let toString = (v: squiggleValue_Record) => ReducerInterface_InternalExpressionValue.toStringMap(v) let toString = (v: squiggleValue_Record) => Reducer_Value.toStringMap(v)
@genType
let toSquiggleValue = (v: squiggleValue_Record): squiggleValue => IEvRecord(v)

View File

@ -1,10 +0,0 @@
type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
@genType type squiggleValue_Type = ForTS_SquiggleValue.squiggleValue_Type //re-export recursive type
@genType
let getKeyValuePairs = (value: squiggleValue_Type): array<(string, squiggleValue)> =>
ReducerInterface_InternalExpressionValue.recordToKeyValuePairs(value)
@genType
let toString = (value: squiggleValue_Type): string =>
ReducerInterface_InternalExpressionValue.toStringType(value)

View File

@ -1,19 +1,13 @@
export enum squiggleValueTag { export enum squiggleValueTag {
Array = "Array", Array = "Array",
ArrayString = "ArrayString",
Bool = "Bool", Bool = "Bool",
Call = "Call",
Date = "Date", Date = "Date",
Declaration = "Declaration", Declaration = "Declaration",
Distribution = "Distribution", Distribution = "Distribution",
Lambda = "Lambda", Lambda = "Lambda",
Module = "Module",
Number = "Number", Number = "Number",
Record = "Record", Record = "Record",
String = "String", String = "String",
Symbol = "Symbol",
TimeDuration = "TimeDuration", TimeDuration = "TimeDuration",
Type = "Type",
TypeIdentifier = "TypeIdentifier",
Void = "Void", Void = "Void",
} }

View File

@ -6,9 +6,7 @@
@genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export @genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export
@genType type squiggleValue_Declaration = ForTS_SquiggleValue_Declaration.squiggleValue_Declaration //re-export @genType type squiggleValue_Declaration = ForTS_SquiggleValue_Declaration.squiggleValue_Declaration //re-export
@genType type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //re-export @genType type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //re-export
@genType type squiggleValue_Module = ForTS_SquiggleValue_Module.squiggleValue_Module //re-export
@genType type squiggleValue_Record = ForTS_SquiggleValue_Record.squiggleValue_Record //re-export @genType type squiggleValue_Record = ForTS_SquiggleValue_Record.squiggleValue_Record //re-export
@genType type squiggleValue_Type = ForTS_SquiggleValue_Type.squiggleValue_Type //re-export
/* Distribution related */ /* Distribution related */
@genType type squiggleValue_Distribution = ForTS_Distribution.distribution //re-export @genType type squiggleValue_Distribution = ForTS_Distribution.distribution //re-export

View File

@ -1,7 +1,5 @@
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T type internalExpressionValueType = Reducer_Value.internalExpressionValueType
module ProjectReducerFnT = ReducerProject_ReducerFn_T type errorValue = Reducer_ErrorValue.errorValue
type internalExpressionValue = ReducerInterface_InternalExpressionValue.t
type internalExpressionValueType = ReducerInterface_InternalExpressionValue.internalExpressionValueType
/* /*
Function Registry "Type". A type, without any other information. Function Registry "Type". A type, without any other information.
@ -9,7 +7,10 @@ type internalExpressionValueType = ReducerInterface_InternalExpressionValue.inte
*/ */
type rec frType = type rec frType =
| FRTypeNumber | FRTypeNumber
| FRTypeBool
| FRTypeNumeric | FRTypeNumeric
| FRTypeDate
| FRTypeTimeDuration
| FRTypeDistOrNumber | FRTypeDistOrNumber
| FRTypeDist | FRTypeDist
| FRTypeLambda | FRTypeLambda
@ -22,35 +23,16 @@ type rec frType =
and frTypeRecord = array<frTypeRecordParam> and frTypeRecord = array<frTypeRecordParam>
and frTypeRecordParam = (string, frType) and frTypeRecordParam = (string, frType)
/* type frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
Function Registry "Value". A type, with the information of that type.
Like, #Float(40.0)
*/
type rec frValue =
| FRValueNumber(float)
| FRValueDist(DistributionTypes.genericDist)
| FRValueArray(array<frValue>)
| FRValueDistOrNumber(frValueDistOrNumber)
| FRValueRecord(frValueRecord)
| FRValueLambda(ReducerInterface_InternalExpressionValue.lambdaValue)
| FRValueString(string)
| FRValueVariant(string)
| FRValueAny(frValue)
| FRValueDict(Js.Dict.t<frValue>)
and frValueRecord = array<frValueRecordParam>
and frValueRecordParam = (string, frValue)
and frValueDictParam = (string, frValue)
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
type fnDefinition = { type fnDefinition = {
name: string, name: string,
inputs: array<frType>, inputs: array<frType>,
run: ( run: (
array<internalExpressionValue>, array<Reducer_T.value>,
array<frValue>, Reducer_T.environment,
ProjectAccessorsT.t, Reducer_T.reducerFn,
ProjectReducerFnT.t, ) => result<Reducer_T.value, errorValue>,
) => result<internalExpressionValue, string>,
} }
type function = { type function = {
@ -64,14 +46,14 @@ type function = {
isExperimental: bool, isExperimental: bool,
} }
type fnNameDict = Js.Dict.t<array<function>>
type registry = {functions: array<function>, fnNameDict: fnNameDict}
module FRType = { module FRType = {
type t = frType type t = frType
let rec toString = (t: t) => let rec toString = (t: t) =>
switch t { switch t {
| FRTypeNumber => "number" | FRTypeNumber => "number"
| FRTypeBool => "bool"
| FRTypeDate => "date"
| FRTypeTimeDuration => "duration"
| FRTypeNumeric => "numeric" | FRTypeNumeric => "numeric"
| FRTypeDist => "distribution" | FRTypeDist => "distribution"
| FRTypeDistOrNumber => "distribution|number" | FRTypeDistOrNumber => "distribution|number"
@ -87,284 +69,44 @@ module FRType = {
| FRTypeAny => `any` | FRTypeAny => `any`
} }
let rec toFrValue = (r: internalExpressionValue): option<frValue> => let rec matchWithValue = (t: t, r: Reducer_T.value): bool =>
switch r {
| IEvNumber(f) => Some(FRValueNumber(f))
| IEvString(f) => Some(FRValueString(f))
| IEvDistribution(f) => Some(FRValueDistOrNumber(FRValueDist(f)))
| IEvLambda(f) => Some(FRValueLambda(f))
| IEvArray(elements) =>
elements->E.A2.fmap(toFrValue)->E.A.O.openIfAllSome->E.O2.fmap(r => FRValueArray(r))
| IEvRecord(map) =>
Belt.Map.String.toArray(map)
->E.A2.fmap(((key, item)) => item->toFrValue->E.O2.fmap(o => (key, o)))
->E.A.O.openIfAllSome
->E.O2.fmap(r => FRValueRecord(r))
| _ => None
}
let rec matchWithExpressionValue = (t: t, r: internalExpressionValue): option<frValue> =>
switch (t, r) { switch (t, r) {
| (FRTypeAny, f) => toFrValue(f) | (FRTypeAny, _) => true
| (FRTypeString, IEvString(f)) => Some(FRValueString(f)) | (FRTypeString, IEvString(_)) => true
| (FRTypeNumber, IEvNumber(f)) => Some(FRValueNumber(f)) | (FRTypeNumber, IEvNumber(_)) => true
| (FRTypeDistOrNumber, IEvNumber(f)) => Some(FRValueDistOrNumber(FRValueNumber(f))) | (FRTypeBool, IEvBool(_)) => true
| (FRTypeDistOrNumber, IEvDistribution(Symbolic(#Float(f)))) => | (FRTypeDate, IEvDate(_)) => true
Some(FRValueDistOrNumber(FRValueNumber(f))) | (FRTypeTimeDuration, IEvTimeDuration(_)) => true
| (FRTypeDistOrNumber, IEvDistribution(f)) => Some(FRValueDistOrNumber(FRValueDist(f))) | (FRTypeDistOrNumber, IEvNumber(_)) => true
| (FRTypeDist, IEvDistribution(f)) => Some(FRValueDist(f)) | (FRTypeDistOrNumber, IEvDistribution(_)) => true
| (FRTypeNumeric, IEvNumber(f)) => Some(FRValueNumber(f)) | (FRTypeDist, IEvDistribution(_)) => true
| (FRTypeNumeric, IEvDistribution(Symbolic(#Float(f)))) => Some(FRValueNumber(f)) | (FRTypeNumeric, IEvNumber(_)) => true
| (FRTypeLambda, IEvLambda(f)) => Some(FRValueLambda(f)) | (FRTypeNumeric, IEvDistribution(Symbolic(#Float(_)))) => true
| (FRTypeArray(intendedType), IEvArray(elements)) => { | (FRTypeLambda, IEvLambda(_)) => true
let el = elements->E.A2.fmap(matchWithExpressionValue(intendedType)) | (FRTypeArray(intendedType), IEvArray(elements)) =>
E.A.O.openIfAllSome(el)->E.O2.fmap(r => FRValueArray(r)) elements->Belt.Array.every(v => matchWithValue(intendedType, v))
}
| (FRTypeDict(r), IEvRecord(map)) => | (FRTypeDict(r), IEvRecord(map)) =>
map map->Belt.Map.String.valuesToArray->Belt.Array.every(v => matchWithValue(r, v))
->Belt.Map.String.toArray | (FRTypeRecord(recordParams), IEvRecord(map)) =>
->E.A2.fmap(((key, item)) => matchWithExpressionValue(r, item)->E.O2.fmap(o => (key, o))) recordParams->Belt.Array.every(((name, input)) => {
->E.A.O.openIfAllSome switch map->Belt.Map.String.get(name) {
->E.O2.fmap(r => FRValueDict(Js.Dict.fromArray(r))) | Some(v) => matchWithValue(input, v)
| (FRTypeRecord(recordParams), IEvRecord(map)) => { | None => false
let getAndMatch = (name, input) =>
Belt.Map.String.get(map, name)->E.O.bind(matchWithExpressionValue(input))
//All names in the type must be present. If any are missing, the corresponding
//value will be None, and this function would return None.
let namesAndValues: array<option<(Js.Dict.key, frValue)>> =
recordParams->E.A2.fmap(((name, input)) =>
getAndMatch(name, input)->E.O2.fmap(match => (name, match))
)
namesAndValues->E.A.O.openIfAllSome->E.O2.fmap(r => FRValueRecord(r))
} }
| _ => None })
| _ => false
} }
let rec matchReverse = (e: frValue): internalExpressionValue => let matchWithValueArray = (inputs: array<t>, args: array<Reducer_T.value>): bool => {
switch e {
| FRValueNumber(f) => IEvNumber(f)
| FRValueDistOrNumber(FRValueNumber(n)) => IEvNumber(n)
| FRValueDistOrNumber(FRValueDist(n)) => IEvDistribution(n)
| FRValueDist(dist) => IEvDistribution(dist)
| FRValueArray(elements) => IEvArray(elements->E.A2.fmap(matchReverse))
| FRValueRecord(frValueRecord) => {
let map =
frValueRecord
->E.A2.fmap(((name, value)) => (name, matchReverse(value)))
->Belt.Map.String.fromArray
IEvRecord(map)
}
| FRValueDict(frValueRecord) => {
let map =
frValueRecord
->Js.Dict.entries
->E.A2.fmap(((name, value)) => (name, matchReverse(value)))
->Belt.Map.String.fromArray
IEvRecord(map)
}
| FRValueLambda(l) => IEvLambda(l)
| FRValueString(string) => IEvString(string)
| FRValueVariant(string) => IEvString(string)
| FRValueAny(f) => matchReverse(f)
}
let matchWithExpressionValueArray = (
inputs: array<t>,
args: array<internalExpressionValue>,
): option<array<frValue>> => {
let isSameLength = E.A.length(inputs) == E.A.length(args) let isSameLength = E.A.length(inputs) == E.A.length(args)
if !isSameLength { if !isSameLength {
None false
} else { } else {
E.A.zip(inputs, args) E.A.zip(inputs, args)->Belt.Array.every(((input, arg)) => matchWithValue(input, arg))
->E.A2.fmap(((input, arg)) => matchWithExpressionValue(input, arg))
->E.A.O.openIfAllSome
} }
} }
} }
/*
This module, Matcher, is fairly lengthy. However, only two functions from it
are meant to be used outside of it. These are findMatches and matchToDef in Matches.Registry.
The rest of it is just called from those two functions.
Update: This really should be completely re-done sometime, and tested. It works, but it's pretty messy. I'm sure
there are internal bugs, but the end functionality works, so I'm not too worried.
*/
module Matcher = {
module MatchSimple = {
type t = DifferentName | SameNameDifferentArguments | FullMatch
let isFullMatch = (match: t) =>
switch match {
| FullMatch => true
| _ => false
}
let isNameMatchOnly = (match: t) =>
switch match {
| SameNameDifferentArguments => true
| _ => false
}
}
module Match = {
type t<'a, 'b> = DifferentName | SameNameDifferentArguments('a) | FullMatch('b)
let isFullMatch = (match: t<'a, 'b>): bool =>
switch match {
| FullMatch(_) => true
| _ => false
}
let isNameMatchOnly = (match: t<'a, 'b>) =>
switch match {
| SameNameDifferentArguments(_) => true
| _ => false
}
}
module FnDefinition = {
let matchAssumingSameName = (f: fnDefinition, args: array<internalExpressionValue>) => {
switch FRType.matchWithExpressionValueArray(f.inputs, args) {
| Some(_) => MatchSimple.FullMatch
| None => MatchSimple.SameNameDifferentArguments
}
}
let match = (f: fnDefinition, fnName: string, args: array<internalExpressionValue>) => {
if f.name !== fnName {
MatchSimple.DifferentName
} else {
matchAssumingSameName(f, args)
}
}
}
module Function = {
type definitionId = int
type match = Match.t<array<definitionId>, definitionId>
let match = (
f: function,
nameSpace: option<string>,
fnName: string,
args: array<internalExpressionValue>,
): match => {
switch nameSpace {
| Some(ns) if ns !== f.nameSpace => Match.DifferentName
| _ => {
let matchedDefinition = () =>
E.A.getIndexBy(f.definitions, r =>
MatchSimple.isFullMatch(FnDefinition.match(r, fnName, args))
) |> E.O.fmap(r => Match.FullMatch(r))
let getMatchedNameOnlyDefinition = () => {
let nameMatchIndexes =
f.definitions
->E.A2.fmapi((index, r) =>
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args))
? Some(index)
: None
)
->E.A.O.concatSomes
switch nameMatchIndexes {
| [] => None
| elements => Some(Match.SameNameDifferentArguments(elements))
}
}
E.A.O.firstSomeFnWithDefault(
[matchedDefinition, getMatchedNameOnlyDefinition],
Match.DifferentName,
)
}
}
}
}
module RegistryMatch = {
type match = {
nameSpace: string,
fnName: string,
inputIndex: int,
}
let makeMatch = (nameSpace: string, fnName: string, inputIndex: int) => {
nameSpace: nameSpace,
fnName: fnName,
inputIndex: inputIndex,
}
}
module Registry = {
let _findExactMatches = (
r: registry,
nameSpace: option<string>,
fnName: string,
args: array<internalExpressionValue>,
) => {
let functionMatchPairs =
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match))
fullMatch->E.O.bind(((fn, match)) =>
switch match {
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
| _ => None
}
)
}
let _findNameMatches = (
r: registry,
nameSpace: option<string>,
fnName: string,
args: array<internalExpressionValue>,
) => {
let functionMatchPairs =
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
let getNameMatches =
functionMatchPairs
->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None)
->E.A.O.concatSomes
let matches =
getNameMatches
->E.A2.fmap(((fn, match)) =>
switch match {
| SameNameDifferentArguments(indexes) =>
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
| _ => []
}
)
->Belt.Array.concatMany
E.A.toNoneIfEmpty(matches)
}
let findMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
let fnNameInParts = Js.String.split(".", fnName)
let fnToSearch = E.A.get(fnNameInParts, 1) |> E.O.default(fnNameInParts[0])
let nameSpace = E.A.length(fnNameInParts) > 1 ? Some(fnNameInParts[0]) : None
switch _findExactMatches(r, nameSpace, fnToSearch, args) {
| Some(r) => Match.FullMatch(r)
| None =>
switch _findNameMatches(r, nameSpace, fnToSearch, args) {
| Some(r) => Match.SameNameDifferentArguments(r)
| None => Match.DifferentName
}
}
}
let matchToDef = (
registry: registry,
{nameSpace, fnName, inputIndex}: RegistryMatch.match,
): option<fnDefinition> =>
registry.functions
->E.A.getBy(fn => {
nameSpace === fn.nameSpace && fnName === fn.name
})
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
}
}
module FnDefinition = { module FnDefinition = {
type t = fnDefinition type t = fnDefinition
@ -373,24 +115,19 @@ module FnDefinition = {
t.name ++ `(${inputs})` t.name ++ `(${inputs})`
} }
let isMatch = (t: t, args: array<internalExpressionValue>) => { let isMatch = (t: t, args: array<Reducer_T.value>) => {
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args) FRType.matchWithValueArray(t.inputs, args)
switch argValues {
| Some(_) => true
| None => false
}
} }
let run = ( let run = (
t: t, t: t,
args: array<internalExpressionValue>, args: array<Reducer_T.value>,
accessors: ProjectAccessorsT.t, env: Reducer_T.environment,
reducer: ProjectReducerFnT.t, reducer: Reducer_T.reducerFn,
) => { ) => {
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args) switch t->isMatch(args) {
switch argValues { | true => t.run(args, env, reducer)
| Some(values) => t.run(args, values, accessors, reducer) | false => REOther("Incorrect Types")->Error
| None => Error("Incorrect Types")
} }
} }
@ -442,43 +179,41 @@ module Function = {
} }
} }
module NameSpace = {
type t = {name: string, functions: array<function>}
let definitions = (t: t) => t.functions->E.A2.fmap(f => f.definitions)->E.A.concatMany
let uniqueFnNames = (t: t) => definitions(t)->E.A2.fmap(r => r.name)->E.A.uniq
let nameToDefinitions = (t: t, name: string) => definitions(t)->E.A2.filter(r => r.name == name)
}
module Registry = { module Registry = {
type fnNameDict = Belt.Map.String.t<array<fnDefinition>>
type registry = {functions: array<function>, fnNameDict: fnNameDict}
let toJson = (r: registry) => r.functions->E.A2.fmap(Function.toJson) let toJson = (r: registry) => r.functions->E.A2.fmap(Function.toJson)
let allExamples = (r: registry) => r.functions->E.A2.fmap(r => r.examples)->E.A.concatMany let allExamples = (r: registry) => r.functions->E.A2.fmap(r => r.examples)->E.A.concatMany
let allExamplesWithFns = (r: registry) => let allExamplesWithFns = (r: registry) =>
r.functions->E.A2.fmap(fn => fn.examples->E.A2.fmap(example => (fn, example)))->E.A.concatMany r.functions->E.A2.fmap(fn => fn.examples->E.A2.fmap(example => (fn, example)))->E.A.concatMany
let allNames = (r: registry) => r.fnNameDict->Belt.Map.String.keysToArray
let _buildFnNameDict = (r: array<function>): fnNameDict => { let _buildFnNameDict = (r: array<function>): fnNameDict => {
let allDefinitionsWithFns = // Three layers of reduce:
r // 1. functions
->E.A2.fmap(fn => fn.definitions->E.A2.fmap(definitions => (fn, definitions))) // 2. definitions of each function
->E.A.concatMany // 3. name variations of each definition
let functionsWithFnNames = r->Belt.Array.reduce(Belt.Map.String.empty, (acc, fn) =>
allDefinitionsWithFns fn.definitions->Belt.Array.reduce(acc, (acc, def) => {
->E.A2.fmap(((fn, def)) => { let names =
let nameWithNamespace = `${fn.nameSpace}.${def.name}` [
let nameWithoutNamespace = def.name fn.nameSpace == "" ? [] : [`${fn.nameSpace}.${def.name}`],
fn.requiresNamespace fn.requiresNamespace ? [] : [def.name],
? [(nameWithNamespace, fn)] ]->E.A.concatMany
: [(nameWithNamespace, fn), (nameWithoutNamespace, fn)]
names->Belt.Array.reduce(acc, (acc, name) => {
switch acc->Belt.Map.String.get(name) {
| Some(fns) => {
let _ = fns->Js.Array2.push(def) // mutates the array, no need to update acc
acc
}
| None => acc->Belt.Map.String.set(name, [def])
}
})
}) })
->E.A.concatMany
let uniqueNames = functionsWithFnNames->E.A2.fmap(((name, _)) => name)->E.A.uniq
let cacheAsArray: array<(string, array<function>)> = uniqueNames->E.A2.fmap(uniqueName => {
let relevantItems =
E.A2.filter(functionsWithFnNames, ((defName, _)) => defName == uniqueName)->E.A2.fmap(
E.Tuple2.second,
) )
(uniqueName, relevantItems)
})
cacheAsArray->Js.Dict.fromArray
} }
let make = (fns: array<function>): registry => { let make = (fns: array<function>): registry => {
@ -486,48 +221,31 @@ module Registry = {
{functions: fns, fnNameDict: dict} {functions: fns, fnNameDict: dict}
} }
/* let call = (
There's a (potential+minor) bug here: If a function definition is called outside of the calls registry,
to the registry, then it's possible that there could be a match after the registry is fnName: string,
called. However, for now, we could just call the registry last. args: array<Reducer_T.value>,
*/ env: Reducer_T.environment,
let _matchAndRun = ( reducer: Reducer_T.reducerFn,
~registry: registry, ): result<Reducer_T.value, errorValue> => {
~fnName: string, switch Belt.Map.String.get(registry.fnNameDict, fnName) {
~args: array<internalExpressionValue>, | Some(definitions) => {
~accessors: ProjectAccessorsT.t, let showNameMatchDefinitions = () => {
~reducer: ProjectReducerFnT.t, let defsString =
) => { definitions
let relevantFunctions = Js.Dict.get(registry.fnNameDict, fnName) |> E.O.default([])
let modified = {functions: relevantFunctions, fnNameDict: registry.fnNameDict}
let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
let showNameMatchDefinitions = matches => {
let defs =
matches
->E.A2.fmap(matchToDef)
->E.A.O.concatSomes
->E.A2.fmap(FnDefinition.toString) ->E.A2.fmap(FnDefinition.toString)
->E.A2.fmap(r => `[${r}]`) ->E.A2.fmap(r => `[${r}]`)
->E.A2.joinWith("; ") ->E.A2.joinWith("; ")
`There are function matches for ${fnName}(), but with different arguments: ${defs}` `There are function matches for ${fnName}(), but with different arguments: ${defsString}`
} }
switch Matcher.Registry.findMatches(modified, fnName, args) { let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args))
| Matcher.Match.FullMatch(match) => switch match {
match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, accessors, reducer)) | Some(def) => def->FnDefinition.run(args, env, reducer)
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m))) | None => REOther(showNameMatchDefinitions())->Error
| _ => None
} }
} }
| None => RESymbolNotFound(fnName)->Error
let dispatch = ( }
registry,
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall,
accessors: ProjectAccessorsT.t,
reducer: ProjectReducerFnT.t,
) => {
_matchAndRun(~registry, ~fnName, ~args, ~accessors, ~reducer)->E.O2.fmap(
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
)
} }
} }

View File

@ -1,59 +1,65 @@
open FunctionRegistry_Core open FunctionRegistry_Core
open Reducer_T
let impossibleError = "Wrong inputs / Logically impossible" let impossibleErrorString = "Wrong inputs / Logically impossible"
let impossibleError: errorValue = impossibleErrorString->Reducer_ErrorValue.REOther
let wrapError = e => Reducer_ErrorValue.REOther(e)
module Wrappers = { module Wrappers = {
let symbolic = r => DistributionTypes.Symbolic(r) let symbolic = r => DistributionTypes.Symbolic(r)
let pointSet = r => DistributionTypes.PointSet(r) let pointSet = r => DistributionTypes.PointSet(r)
let sampleSet = r => DistributionTypes.SampleSet(r) let sampleSet = r => DistributionTypes.SampleSet(r)
let evDistribution = r => ReducerInterface_InternalExpressionValue.IEvDistribution(r) let evDistribution = r => Reducer_T.IEvDistribution(r)
let evNumber = r => ReducerInterface_InternalExpressionValue.IEvNumber(r) let evNumber = r => Reducer_T.IEvNumber(r)
let evArray = r => ReducerInterface_InternalExpressionValue.IEvArray(r) let evArray = r => Reducer_T.IEvArray(r)
let evRecord = r => ReducerInterface_InternalExpressionValue.IEvRecord(r) let evRecord = r => Reducer_T.IEvRecord(r)
let evString = r => ReducerInterface_InternalExpressionValue.IEvString(r) let evString = r => Reducer_T.IEvString(r)
let symbolicEvDistribution = r => r->DistributionTypes.Symbolic->evDistribution let symbolicEvDistribution = r => r->DistributionTypes.Symbolic->evDistribution
let evArrayOfEvNumber = xs => xs->Belt.Array.map(evNumber)->evArray let evArrayOfEvNumber = xs => xs->Belt.Array.map(evNumber)->evArray
} }
let getOrError = (a, g) => E.A.get(a, g) |> E.O.toResult(impossibleError) let getOrError = (a, g) => E.A.get(a, g) |> E.O.toResult(impossibleErrorString)
module Prepare = { module Prepare = {
type t = frValue type t = value
type ts = array<frValue> type ts = array<value>
type err = string type err = string
module ToValueArray = { module ToValueArray = {
module Record = { module Record = {
let twoArgs = (inputs: ts): result<ts, err> => let twoArgs = (inputs: ts, (arg1: string, arg2: string)): result<ts, err> =>
switch inputs { switch inputs {
| [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2]) | [IEvRecord(map)] => {
| _ => Error(impossibleError) let n1 = map->Belt.Map.String.getExn(arg1)
let n2 = map->Belt.Map.String.getExn(arg2)
Ok([n1, n2])
}
| _ => Error(impossibleErrorString)
} }
let threeArgs = (inputs: ts): result<ts, err> => let threeArgs = (inputs: ts, (arg1: string, arg2: string, arg3: string)): result<ts, err> =>
switch inputs { switch inputs {
| [FRValueRecord([(_, n1), (_, n2), (_, n3)])] => Ok([n1, n2, n3]) | [IEvRecord(map)] => {
| _ => Error(impossibleError) let n1 = map->Belt.Map.String.getExn(arg1)
let n2 = map->Belt.Map.String.getExn(arg2)
let n3 = map->Belt.Map.String.getExn(arg3)
Ok([n1, n2, n3])
} }
| _ => Error(impossibleErrorString)
let toArgs = (inputs: ts): result<ts, err> =>
switch inputs {
| [FRValueRecord(args)] => args->E.A2.fmap(((_, b)) => b)->Ok
| _ => Error(impossibleError)
} }
} }
module Array = { module Array = {
let openA = (inputs: t): result<ts, err> => let openA = (inputs: t): result<ts, err> =>
switch inputs { switch inputs {
| FRValueArray(n) => Ok(n) | IEvArray(n) => Ok(n)
| _ => Error(impossibleError) | _ => Error(impossibleErrorString)
} }
let arrayOfArrays = (inputs: t): result<array<ts>, err> => let arrayOfArrays = (inputs: t): result<array<ts>, err> =>
switch inputs { switch inputs {
| FRValueArray(n) => n->E.A2.fmap(openA)->E.A.R.firstErrorOrOpen | IEvArray(n) => n->E.A2.fmap(openA)->E.A.R.firstErrorOrOpen
| _ => Error(impossibleError) | _ => Error(impossibleErrorString)
} }
} }
} }
@ -61,8 +67,11 @@ module Prepare = {
module ToValueTuple = { module ToValueTuple = {
let twoDistOrNumber = (values: ts): result<(frValueDistOrNumber, frValueDistOrNumber), err> => { let twoDistOrNumber = (values: ts): result<(frValueDistOrNumber, frValueDistOrNumber), err> => {
switch values { switch values {
| [FRValueDistOrNumber(a1), FRValueDistOrNumber(a2)] => Ok(a1, a2) | [IEvDistribution(a1), IEvDistribution(a2)] => Ok(FRValueDist(a1), FRValueDist(a2))
| _ => Error(impossibleError) | [IEvDistribution(a1), IEvNumber(a2)] => Ok(FRValueDist(a1), FRValueNumber(a2))
| [IEvNumber(a1), IEvDistribution(a2)] => Ok(FRValueNumber(a1), FRValueDist(a2))
| [IEvNumber(a1), IEvNumber(a2)] => Ok(FRValueNumber(a1), FRValueNumber(a2))
| _ => Error(impossibleErrorString)
} }
} }
@ -71,67 +80,57 @@ module Prepare = {
err, err,
> => { > => {
switch values { switch values {
| [FRValueDist(a1), FRValueDist(a2)] => Ok(a1, a2) | [IEvDistribution(a1), IEvDistribution(a2)] => Ok(a1, a2)
| _ => Error(impossibleError) | _ => Error(impossibleErrorString)
} }
} }
let twoNumbers = (values: ts): result<(float, float), err> => { let twoNumbers = (values: ts): result<(float, float), err> => {
switch values { switch values {
| [FRValueNumber(a1), FRValueNumber(a2)] => Ok(a1, a2) | [IEvNumber(a1), IEvNumber(a2)] => Ok(a1, a2)
| _ => Error(impossibleError) | _ => Error(impossibleErrorString)
} }
} }
let threeNumbers = (values: ts): result<(float, float, float), err> => { let threeNumbers = (values: ts): result<(float, float, float), err> => {
switch values { switch values {
| [FRValueNumber(a1), FRValueNumber(a2), FRValueNumber(a3)] => Ok(a1, a2, a3) | [IEvNumber(a1), IEvNumber(a2), IEvNumber(a3)] => Ok(a1, a2, a3)
| _ => Error(impossibleError) | _ => Error(impossibleErrorString)
} }
} }
let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => { let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => {
switch values { switch values {
| [FRValueDistOrNumber(a1)] => Ok(a1) | [IEvNumber(a1)] => FRValueNumber(a1)->Ok
| _ => Error(impossibleError) | [IEvDistribution(a2)] => FRValueDist(a2)->Ok
| _ => Error(impossibleErrorString)
} }
} }
module Record = { module Record = {
let twoDistOrNumber = (values: ts): result<(frValueDistOrNumber, frValueDistOrNumber), err> => let twoDistOrNumber = (values: ts, labels: (string, string)): result<
values->ToValueArray.Record.twoArgs->E.R.bind(twoDistOrNumber) (frValueDistOrNumber, frValueDistOrNumber),
err,
> => values->ToValueArray.Record.twoArgs(labels)->E.R.bind(twoDistOrNumber)
let twoDist = (values: ts): result< let twoDist = (values: ts, labels: (string, string)): result<
(DistributionTypes.genericDist, DistributionTypes.genericDist), (DistributionTypes.genericDist, DistributionTypes.genericDist),
err, err,
> => values->ToValueArray.Record.twoArgs->E.R.bind(twoDist) > => values->ToValueArray.Record.twoArgs(labels)->E.R.bind(twoDist)
} }
} }
module ToArrayRecordPairs = { let oneNumber = (value: t): result<float, err> => {
let twoArgs = (input: t): result<array<ts>, err> => { switch value {
let array = input->ToValueArray.Array.openA | IEvNumber(a1) => Ok(a1)
let pairs = | _ => Error(impossibleErrorString)
array->E.R.bind(pairs =>
pairs
->E.A2.fmap(xyCoord => [xyCoord]->ToValueArray.Record.twoArgs)
->E.A.R.firstErrorOrOpen
)
pairs
} }
} }
let oneNumber = (values: t): result<float, err> => { let oneDict = (value: t): result<Reducer_T.map, err> => {
switch values { switch value {
| FRValueNumber(a1) => Ok(a1) | IEvRecord(a1) => Ok(a1)
| _ => Error(impossibleError) | _ => Error(impossibleErrorString)
}
}
let oneDict = (values: t): result<Js.Dict.t<frValue>, err> => {
switch values {
| FRValueDict(a1) => Ok(a1)
| _ => Error(impossibleError)
} }
} }
@ -142,7 +141,7 @@ module Prepare = {
inputs->getOrError(0)->E.R.bind(ToValueArray.Array.openA)->E.R.bind(openNumbers) inputs->getOrError(0)->E.R.bind(ToValueArray.Array.openA)->E.R.bind(openNumbers)
} }
let dicts = (inputs: ts): Belt.Result.t<array<Js.Dict.t<frValue>>, err> => { let dicts = (inputs: ts): Belt.Result.t<array<Reducer_T.map>, err> => {
let openDicts = (elements: array<t>) => elements->E.A2.fmap(oneDict)->E.A.R.firstErrorOrOpen let openDicts = (elements: array<t>) => elements->E.A2.fmap(oneDict)->E.A.R.firstErrorOrOpen
inputs->getOrError(0)->E.R.bind(ToValueArray.Array.openA)->E.R.bind(openDicts) inputs->getOrError(0)->E.R.bind(ToValueArray.Array.openA)->E.R.bind(openDicts)
} }
@ -223,12 +222,11 @@ module DefineFn = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeNumber], ~inputs=[FRTypeNumber],
~run=(_, inputs, _, _) => { ~run=(inputs, _, _) => {
inputs switch inputs {
->getOrError(0) | [IEvNumber(x)] => fn(x)->IEvNumber->Ok
->E.R.bind(Prepare.oneNumber) | _ => Error(impossibleError)
->E.R2.fmap(fn) }
->E.R2.fmap(Wrappers.evNumber)
}, },
(), (),
) )
@ -236,8 +234,11 @@ module DefineFn = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeNumber, FRTypeNumber], ~inputs=[FRTypeNumber, FRTypeNumber],
~run=(_, inputs, _, _) => { ~run=(inputs, _, _) => {
inputs->Prepare.ToValueTuple.twoNumbers->E.R2.fmap(fn)->E.R2.fmap(Wrappers.evNumber) switch inputs {
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvNumber->Ok
| _ => Error(impossibleError)
}
}, },
(), (),
) )
@ -245,10 +246,153 @@ module DefineFn = {
FnDefinition.make( FnDefinition.make(
~name, ~name,
~inputs=[FRTypeNumber, FRTypeNumber, FRTypeNumber], ~inputs=[FRTypeNumber, FRTypeNumber, FRTypeNumber],
~run=(_, inputs, _, _) => { ~run=(inputs, _, _) => {
inputs->Prepare.ToValueTuple.threeNumbers->E.R2.fmap(fn)->E.R2.fmap(Wrappers.evNumber) switch inputs {
| [IEvNumber(x), IEvNumber(y), IEvNumber(z)] => fn(x, y, z)->IEvNumber->Ok
| _ => Error(impossibleError)
}
}, },
(), (),
) )
} }
} }
module Make = {
/*
Opinionated explanations for API choices here:
Q: Why such short names?
A: Because we have to type them a lot in definitions.
Q: Why not DefineFn.Numbers.oneToOne / DefineFn.Numbers.twoToOne / ...?
A: Because return values matter too, and we have many possible combinations: numbers to numbers, pairs of numbers to numbers, pair of numbers to bools.
Q: Does this approach scale?
A: It's good enough for most cases, and we can fall back on raw `Function.make` if necessary. We should figure out the better API powered by parameterized types, but it's hard (and might require PPX).
Q: What about `frValue` types?
A: I hope we'll get rid of them soon.
Q: What about polymorphic functions with multiple definitions? Why ~fn is not an array?
A: We often define the same function in multiple `FR_*` files, so that doesn't work well anyway. In 90%+ cases there's a single definition. And having to write `name` twice is annoying.
*/
let f2f = (
~name: string,
~fn: float => float,
~nameSpace="",
~requiresNamespace=false,
~examples=?,
(),
) => {
Function.make(
~name,
~nameSpace,
~requiresNamespace,
~examples=examples->E.O.default([], _),
~output=EvtNumber,
~definitions=[
FnDefinition.make(
~name,
~inputs=[FRTypeNumber],
~run=(inputs, _, _) =>
switch inputs {
| [IEvNumber(x)] => fn(x)->IEvNumber->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
)
}
let ff2f = (
~name: string,
~fn: (float, float) => float,
~nameSpace="",
~requiresNamespace=false,
~examples=?,
(),
) => {
Function.make(
~name,
~nameSpace,
~requiresNamespace,
~examples=examples->E.O.default([], _),
~output=EvtNumber,
~definitions=[
FnDefinition.make(
~name,
~inputs=[FRTypeNumber, FRTypeNumber],
~run=(inputs, _, _) =>
switch inputs {
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvNumber->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
)
}
let ff2b = (
~name: string,
~fn: (float, float) => bool,
~nameSpace="",
~requiresNamespace=false,
~examples=?,
(),
) => {
Function.make(
~name,
~nameSpace,
~requiresNamespace,
~examples=examples->E.O.default([], _),
~output=EvtBool,
~definitions=[
FnDefinition.make(
~name,
~inputs=[FRTypeNumber, FRTypeNumber],
~run=(inputs, _, _) =>
switch inputs {
| [IEvNumber(x), IEvNumber(y)] => fn(x, y)->IEvBool->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
)
}
let bb2b = (
~name: string,
~fn: (bool, bool) => bool,
~nameSpace="",
~requiresNamespace=false,
~examples=?,
(),
) => {
Function.make(
~name,
~nameSpace,
~requiresNamespace,
~examples=examples->E.O.default([], _),
~output=EvtBool,
~definitions=[
FnDefinition.make(
~name,
~inputs=[FRTypeBool, FRTypeBool],
~run=(inputs, _, _) =>
switch inputs {
| [IEvBool(x), IEvBool(y)] => fn(x, y)->IEvBool->Ok
| _ => Error(impossibleError)
},
(),
),
],
(),
)
}
}

View File

@ -1,4 +1,5 @@
let fnList = Belt.Array.concatMany([ let fnList = Belt.Array.concatMany([
FR_Builtin.library,
FR_Dict.library, FR_Dict.library,
FR_Dist.library, FR_Dist.library,
FR_Danger.library, FR_Danger.library,
@ -8,7 +9,16 @@ let fnList = Belt.Array.concatMany([
FR_Number.library, FR_Number.library,
FR_Pointset.library, FR_Pointset.library,
FR_Scoring.library, FR_Scoring.library,
FR_GenericDist.library,
FR_Units.library,
FR_Date.library,
FR_Math.library,
]) ])
let registry = FunctionRegistry_Core.Registry.make(fnList) let registry = FunctionRegistry_Core.Registry.make(fnList)
let dispatch = FunctionRegistry_Core.Registry.dispatch(registry) let call = FunctionRegistry_Core.Registry.call(registry)
let nonRegistryLambdas: array<(string, Reducer_T.lambdaValue)> = [
("mx", FR_GenericDist.mxLambda),
("mixture", FR_GenericDist.mxLambda),
]

View File

@ -9,9 +9,9 @@ The main interface is fairly constrained. Basically, write functions like the fo
~name="Normal", ~name="Normal",
~definitions=[ ~definitions=[
FnDefinition.make( FnDefinition.make(
~name="Normal", ~name="normal",
~definitions=[ ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
FnDefinition.make(~name="normal", ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=( ~run=(
inputs, inputs,
env, env,
) => ) =>
@ -25,22 +25,22 @@ The main interface is fairly constrained. Basically, write functions like the fo
), ),
) )
->E.R2.fmap(Wrappers.evDistribution) ->E.R2.fmap(Wrappers.evDistribution)
),
],
) )
], ],
) )
``` ```
The Function name is just there for future documentation. The function defintions The Function name is just there for future documentation.
## Key Files ## Key Files
**FunctionRegistry_Core** **FunctionRegistry_Core**
Key types, internal functionality, and a `Registry` module with a `matchAndRun` function to call function definitions. Key types, internal functionality, and a `Registry` module with a `call` function to call function definitions.
**FunctionRegistry_Library** **FunctionRegistry_Library**
A list of all the Functions defined in the Function Registry. A list of all the Functions defined in the Function Registry.
The definition arrays are stored in `FR_*` modules, by convention.
**FunctionRegistry_Helpers** **FunctionRegistry_Helpers**
A list of helper functions for the FunctionRegistry_Library. A list of helper functions for the `FunctionRegistry_Library`.

View File

@ -0,0 +1,47 @@
/*
Bindings describe the entire set of bound variables accessible to the squiggle code.
Bindings objects are stored as linked lists of scopes:
{ localX: ..., localY: ... } <- { globalZ: ..., ... } <- { importedT: ..., ... } <- { stdlibFunction: ..., ... }
*/
type t = Reducer_T.bindings
let rec get = ({namespace, parent}: t, id: string) => {
switch namespace->Reducer_Namespace.get(id) {
| Some(v) => Some(v)
| None =>
switch parent {
| Some(p) => p->get(id)
| None => None
}
}
}
let set = ({namespace} as bindings: t, id: string, value): t => {
{
...bindings,
namespace: namespace->Reducer_Namespace.set(id, value),
}
}
let rec toString = ({namespace, parent}: t) => {
let pairs = namespace->Reducer_Namespace.toString
switch parent {
| Some(p) => `{${pairs}} / ${toString(p)}`
| None => `{${pairs}}`
}
}
let extend = (bindings: t): t => {namespace: Reducer_Namespace.make(), parent: bindings->Some}
let make = (): t => {namespace: Reducer_Namespace.make(), parent: None}
let removeResult = ({namespace} as bindings: t): t => {
...bindings,
namespace: namespace->Belt.Map.String.remove("__result__"),
}
let locals = ({namespace}: t): Reducer_T.namespace => namespace
let fromNamespace = (namespace: Reducer_Namespace.t): t => {namespace: namespace, parent: None}

View File

@ -1,189 +0,0 @@
// Only Bindings as the global module is supported
// Other module operations such as import export will be preprocessed jobs
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Reducer_ErrorValue
open ReducerInterface_InternalExpressionValue
let expressionValueToString = toString
type t = ReducerInterface_InternalExpressionValue.nameSpace
let typeAliasesKey = "_typeAliases_"
let typeReferencesKey = "_typeReferences_"
let getType = (NameSpace(container): t, id: string) => {
Belt.Map.String.get(container, typeAliasesKey)->Belt.Option.flatMap(aliases =>
switch aliases {
| IEvRecord(r) => Belt.Map.String.get(r, id)
| _ => None
}
)
}
let getTypeOf = (NameSpace(container): t, id: string) => {
Belt.Map.String.get(container, typeReferencesKey)->Belt.Option.flatMap(defs =>
switch defs {
| IEvRecord(r) => Belt.Map.String.get(r, id)
| _ => None
}
)
}
let getWithDefault = (NameSpace(container): t, id: string, default) =>
switch Belt.Map.String.get(container, id) {
| Some(v) => v
| None => default
}
let get = (NameSpace(container): t, id: string) => Belt.Map.String.get(container, id)
let emptyMap: map = Belt.Map.String.empty
let setTypeAlias = (NameSpace(container): t, id: string, value): t => {
let rValue = Belt.Map.String.getWithDefault(container, typeAliasesKey, IEvRecord(emptyMap))
let r = switch rValue {
| IEvRecord(r) => r
| _ => emptyMap
}
let r2 = Belt.Map.String.set(r, id, value)->IEvRecord
NameSpace(Belt.Map.String.set(container, typeAliasesKey, r2))
}
let setTypeOf = (NameSpace(container): t, id: string, value): t => {
let rValue = Belt.Map.String.getWithDefault(container, typeReferencesKey, IEvRecord(emptyMap))
let r = switch rValue {
| IEvRecord(r) => r
| _ => emptyMap
}
let r2 = Belt.Map.String.set(r, id, value)->IEvRecord
NameSpace(Belt.Map.String.set(container, typeReferencesKey, r2))
}
let set = (NameSpace(container): t, id: string, value): t => NameSpace(
Belt.Map.String.set(container, id, value),
)
let emptyModule: t = NameSpace(emptyMap)
let emptyBindings = emptyModule
let emptyNameSpace = emptyModule
// let fromTypeScriptBindings = ReducerInterface_InternalExpressionValue.nameSpaceFromTypeScriptBindings
// let toTypeScriptBindings = ReducerInterface_InternalExpressionValue.nameSpaceToTypeScriptBindings
let toExpressionValue = (nameSpace: t): internalExpressionValue => IEvBindings(nameSpace)
let fromExpressionValue = (aValue: internalExpressionValue): t =>
switch aValue {
| IEvBindings(nameSpace) => nameSpace
| _ => emptyModule
}
let fromArray = a => NameSpace(Belt.Map.String.fromArray(a))
let mergeFrom = (NameSpace(container): t, NameSpace(newContainer): t): t => {
NameSpace(
newContainer->Belt.Map.String.reduce(container, (container, key, value) =>
Belt.Map.String.set(container, key, value)
),
)
}
let removeOther = (NameSpace(container): t, NameSpace(otherContainer): t): t => {
let keys = Belt.Map.String.keysToArray(otherContainer)
NameSpace(
Belt.Map.String.keep(container, (key, _value) => {
let removeThis = Js.Array2.includes(keys, key)
!removeThis
}),
)
}
external castExpressionToInternalCode: ExpressionT.expressionOrFFI => internalCode = "%identity"
let eLambdaFFIValue = (ffiFn: ExpressionT.ffiFn) => {
IEvLambda({
parameters: [],
context: emptyModule,
body: FFI(ffiFn)->castExpressionToInternalCode,
})
}
let functionNotFoundError = (call: functionCall) =>
REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)->Error
let functionNotFoundErrorFFIFn = (functionName: string): ExpressionT.ffiFn => {
(args: array<internalExpressionValue>, _environment: environment): result<
internalExpressionValue,
errorValue,
> => {
let call = (functionName, args)
functionNotFoundError(call)
}
}
let convertOptionToFfiFnReturningResult = (
myFunctionName: string,
myFunction: ExpressionT.optionFfiFnReturningResult,
): ExpressionT.ffiFn => {
(args: array<InternalExpressionValue.t>, environment) => {
myFunction(args, environment)->Belt.Option.getWithDefault(
functionNotFoundErrorFFIFn(myFunctionName)(args, environment),
)
}
}
let convertOptionToFfiFn = (
myFunctionName: string,
myFunction: ExpressionT.optionFfiFn,
): ExpressionT.ffiFn => {
(args: array<InternalExpressionValue.t>, environment) => {
myFunction(args, environment)
->Belt.Option.map(v => v->Ok)
->Belt.Option.getWithDefault(functionNotFoundErrorFFIFn(myFunctionName)(args, environment))
}
}
// -- Module definition
let define = (NameSpace(container): t, identifier: string, ev: internalExpressionValue): t => {
NameSpace(Belt.Map.String.set(container, identifier, ev))
}
let defineNumber = (nameSpace: t, identifier: string, value: float): t =>
nameSpace->define(identifier, IEvNumber(value))
let defineString = (nameSpace: t, identifier: string, value: string): t =>
nameSpace->define(identifier, IEvString(value))
let defineBool = (nameSpace: t, identifier: string, value: bool): t =>
nameSpace->define(identifier, IEvBool(value))
let defineModule = (nameSpace: t, identifier: string, value: t): t =>
nameSpace->define(identifier, toExpressionValue(value))
let defineFunction = (nameSpace: t, identifier: string, value: ExpressionT.optionFfiFn): t => {
nameSpace->define(identifier, convertOptionToFfiFn(identifier, value)->eLambdaFFIValue)
}
let defineFunctionReturningResult = (
nameSpace: t,
identifier: string,
value: ExpressionT.optionFfiFnReturningResult,
): t => {
nameSpace->define(
identifier,
convertOptionToFfiFnReturningResult(identifier, value)->eLambdaFFIValue,
)
}
let emptyStdLib: t = emptyModule->defineBool("_standardLibrary", true)
let chainTo = (nameSpace: t, previousNameSpaces: array<t>) => {
previousNameSpaces->Belt.Array.reduce(nameSpace, (topNameSpace, prevNameSpace) =>
mergeFrom(prevNameSpace, topNameSpace)
)
}
let removeResult = (NameSpace(container): t): t => {
container->Belt.Map.String.remove("__result__")->NameSpace
}

View File

@ -0,0 +1,12 @@
type t = Reducer_T.context
let defaultEnvironment: Reducer_T.environment = DistributionOperation.defaultEnv
let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environment): t => {
{
bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend,
environment: environment,
}
}
let createDefaultContext = (): t => createContext(SquiggleLibrary_StdLib.stdLib, defaultEnvironment)

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