Merge pull request #1009 from quantified-uncertainty/epic-reducer-project
0.4.0 - Epic reducer project
This commit is contained in:
		
						commit
						84be552782
					
				|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@quri/squiggle-components", | ||||
|   "version": "0.3.2", | ||||
|   "version": "0.4.0-alpha.1", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@floating-ui/react-dom": "^1.0.0", | ||||
|  | @ -8,7 +8,7 @@ | |||
|     "@headlessui/react": "^1.6.6", | ||||
|     "@heroicons/react": "^1.0.6", | ||||
|     "@hookform/resolvers": "^2.9.7", | ||||
|     "@quri/squiggle-lang": "^0.3.0", | ||||
|     "@quri/squiggle-lang": "^0.4.0-alpha.0", | ||||
|     "@react-hook/size": "^2.1.2", | ||||
|     "clsx": "^1.2.1", | ||||
|     "framer-motion": "^7.2.1", | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| import * as React from "react"; | ||||
| import { | ||||
|   Distribution, | ||||
|   SqDistribution, | ||||
|   result, | ||||
|   distributionError, | ||||
|   distributionErrorToString, | ||||
|   squiggleExpression, | ||||
|   SqDistributionError, | ||||
|   resultMap, | ||||
|   SqRecord, | ||||
|   environment, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { Vega } from "react-vega"; | ||||
| import { ErrorAlert } from "./Alert"; | ||||
|  | @ -28,17 +28,16 @@ export type DistributionPlottingSettings = { | |||
| 
 | ||||
| export type DistributionChartProps = { | ||||
|   plot: Plot; | ||||
|   environment: environment; | ||||
|   width?: number; | ||||
|   height: number; | ||||
| } & DistributionPlottingSettings; | ||||
| 
 | ||||
| export function defaultPlot(distribution: Distribution): Plot { | ||||
| export function defaultPlot(distribution: SqDistribution): Plot { | ||||
|   return { distributions: [{ name: "default", distribution }] }; | ||||
| } | ||||
| 
 | ||||
| export function makePlot(record: { | ||||
|   [key: string]: squiggleExpression; | ||||
| }): Plot | void { | ||||
| export function makePlot(record: SqRecord): Plot | void { | ||||
|   const plotResult = parsePlot(record); | ||||
|   if (plotResult.tag === "Ok") { | ||||
|     return plotResult.value; | ||||
|  | @ -46,22 +45,29 @@ export function makePlot(record: { | |||
| } | ||||
| 
 | ||||
| export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | ||||
|   const { plot, height, showSummary, width, logX, actions = false } = props; | ||||
|   const { | ||||
|     plot, | ||||
|     environment, | ||||
|     height, | ||||
|     showSummary, | ||||
|     width, | ||||
|     logX, | ||||
|     actions = false, | ||||
|   } = props; | ||||
|   const [sized] = useSize((size) => { | ||||
|     let shapes = flattenResult( | ||||
|       plot.distributions.map((x) => | ||||
|         resultMap(x.distribution.pointSet(), (shape) => ({ | ||||
|     const shapes = flattenResult( | ||||
|       plot.distributions.map((x) => { | ||||
|         return resultMap(x.distribution.pointSet(environment), (pointSet) => ({ | ||||
|           ...pointSet.asShape(), | ||||
|           name: x.name, | ||||
|           // color: x.color, // not supported yet
 | ||||
|           continuous: shape.continuous, | ||||
|           discrete: shape.discrete, | ||||
|         })) | ||||
|       ) | ||||
|         })); | ||||
|       }) | ||||
|     ); | ||||
|     if (shapes.tag === "Error") { | ||||
|       return ( | ||||
|         <ErrorAlert heading="Distribution Error"> | ||||
|           {distributionErrorToString(shapes.value)} | ||||
|           {shapes.value.toString()} | ||||
|         </ErrorAlert> | ||||
|       ); | ||||
|     } | ||||
|  | @ -96,7 +102,10 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | |||
|         )} | ||||
|         <div className="flex justify-center"> | ||||
|           {showSummary && plot.distributions.length === 1 && ( | ||||
|             <SummaryTable distribution={plot.distributions[0].distribution} /> | ||||
|             <SummaryTable | ||||
|               distribution={plot.distributions[0].distribution} | ||||
|               environment={environment} | ||||
|             /> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|  | @ -120,32 +129,36 @@ const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => ( | |||
| ); | ||||
| 
 | ||||
| type SummaryTableProps = { | ||||
|   distribution: Distribution; | ||||
|   distribution: SqDistribution; | ||||
|   environment: environment; | ||||
| }; | ||||
| 
 | ||||
| const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => { | ||||
|   const mean = distribution.mean(); | ||||
|   const stdev = distribution.stdev(); | ||||
|   const p5 = distribution.inv(0.05); | ||||
|   const p10 = distribution.inv(0.1); | ||||
|   const p25 = distribution.inv(0.25); | ||||
|   const p50 = distribution.inv(0.5); | ||||
|   const p75 = distribution.inv(0.75); | ||||
|   const p90 = distribution.inv(0.9); | ||||
|   const p95 = distribution.inv(0.95); | ||||
| const SummaryTable: React.FC<SummaryTableProps> = ({ | ||||
|   distribution, | ||||
|   environment, | ||||
| }) => { | ||||
|   const mean = distribution.mean(environment); | ||||
|   const stdev = distribution.stdev(environment); | ||||
|   const p5 = distribution.inv(environment, 0.05); | ||||
|   const p10 = distribution.inv(environment, 0.1); | ||||
|   const p25 = distribution.inv(environment, 0.25); | ||||
|   const p50 = distribution.inv(environment, 0.5); | ||||
|   const p75 = distribution.inv(environment, 0.75); | ||||
|   const p90 = distribution.inv(environment, 0.9); | ||||
|   const p95 = distribution.inv(environment, 0.95); | ||||
| 
 | ||||
|   const hasResult = (x: result<number, distributionError>): boolean => | ||||
|   const hasResult = (x: result<number, SqDistributionError>): boolean => | ||||
|     x.tag === "Ok"; | ||||
| 
 | ||||
|   const unwrapResult = ( | ||||
|     x: result<number, distributionError> | ||||
|     x: result<number, SqDistributionError> | ||||
|   ): React.ReactNode => { | ||||
|     if (x.tag === "Ok") { | ||||
|       return <NumberShower number={x.value} />; | ||||
|     } else { | ||||
|       return ( | ||||
|         <ErrorAlert heading="Distribution Error"> | ||||
|           {distributionErrorToString(x.value)} | ||||
|           {x.value.toString()} | ||||
|         </ErrorAlert> | ||||
|       ); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,10 +1,5 @@ | |||
| import * as React from "react"; | ||||
| import { | ||||
|   lambdaValue, | ||||
|   environment, | ||||
|   runForeign, | ||||
|   errorValueToString, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; | ||||
| import { FunctionChart1Dist } from "./FunctionChart1Dist"; | ||||
| import { FunctionChart1Number } from "./FunctionChart1Number"; | ||||
| import { DistributionPlottingSettings } from "./DistributionChart"; | ||||
|  | @ -17,7 +12,7 @@ export type FunctionChartSettings = { | |||
| }; | ||||
| 
 | ||||
| interface FunctionChartProps { | ||||
|   fn: lambdaValue; | ||||
|   fn: SqLambda; | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   distributionPlotSettings: DistributionPlottingSettings; | ||||
|   environment: environment; | ||||
|  | @ -38,8 +33,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | |||
|       </MessageAlert> | ||||
|     ); | ||||
|   } | ||||
|   const result1 = runForeign(fn, [chartSettings.start], environment); | ||||
|   const result2 = runForeign(fn, [chartSettings.stop], environment); | ||||
|   const result1 = fn.call([chartSettings.start]); | ||||
|   const result2 = fn.call([chartSettings.stop]); | ||||
|   const getValidResult = () => { | ||||
|     if (result1.tag === "Ok") { | ||||
|       return result1; | ||||
|  | @ -53,14 +48,12 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | |||
| 
 | ||||
|   if (validResult.tag === "Error") { | ||||
|     return ( | ||||
|       <ErrorAlert heading="Error"> | ||||
|         {errorValueToString(validResult.value)} | ||||
|       </ErrorAlert> | ||||
|       <ErrorAlert heading="Error">{validResult.value.toString()}</ErrorAlert> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   switch (validResult.value.tag) { | ||||
|     case "distribution": | ||||
|     case SqValueTag.Distribution: | ||||
|       return ( | ||||
|         <FunctionChart1Dist | ||||
|           fn={fn} | ||||
|  | @ -70,7 +63,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | |||
|           distributionPlotSettings={distributionPlotSettings} | ||||
|         /> | ||||
|       ); | ||||
|     case "number": | ||||
|     case SqValueTag.Number: | ||||
|       return ( | ||||
|         <FunctionChart1Number | ||||
|           fn={fn} | ||||
|  |  | |||
|  | @ -2,14 +2,13 @@ import * as React from "react"; | |||
| import _ from "lodash"; | ||||
| import type { Spec } from "vega"; | ||||
| import { | ||||
|   Distribution, | ||||
|   SqDistribution, | ||||
|   result, | ||||
|   lambdaValue, | ||||
|   SqLambda, | ||||
|   environment, | ||||
|   runForeign, | ||||
|   squiggleExpression, | ||||
|   errorValue, | ||||
|   errorValueToString, | ||||
|   SqError, | ||||
|   SqValue, | ||||
|   SqValueTag, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { createClassFromSpec } from "react-vega"; | ||||
| import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; | ||||
|  | @ -46,7 +45,7 @@ export type FunctionChartSettings = { | |||
| }; | ||||
| 
 | ||||
| interface FunctionChart1DistProps { | ||||
|   fn: lambdaValue; | ||||
|   fn: SqLambda; | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   distributionPlotSettings: DistributionPlottingSettings; | ||||
|   environment: environment; | ||||
|  | @ -77,9 +76,17 @@ type errors = _.Dictionary< | |||
|   }[] | ||||
| >; | ||||
| 
 | ||||
| type point = { x: number; value: result<Distribution, string> }; | ||||
| type point = { x: number; value: result<SqDistribution, string> }; | ||||
| 
 | ||||
| let getPercentiles = ({ chartSettings, fn, environment }) => { | ||||
| let getPercentiles = ({ | ||||
|   chartSettings, | ||||
|   fn, | ||||
|   environment, | ||||
| }: { | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   fn: SqLambda; | ||||
|   environment: environment; | ||||
| }) => { | ||||
|   let chartPointsToRender = _rangeByCount( | ||||
|     chartSettings.start, | ||||
|     chartSettings.stop, | ||||
|  | @ -87,9 +94,9 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { | |||
|   ); | ||||
| 
 | ||||
|   let chartPointsData: point[] = chartPointsToRender.map((x) => { | ||||
|     let result = runForeign(fn, [x], environment); | ||||
|     let result = fn.call([x]); | ||||
|     if (result.tag === "Ok") { | ||||
|       if (result.value.tag === "distribution") { | ||||
|       if (result.value.tag === SqValueTag.Distribution) { | ||||
|         return { x, value: { tag: "Ok", value: result.value.value } }; | ||||
|       } else { | ||||
|         return { | ||||
|  | @ -104,13 +111,13 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { | |||
|     } else { | ||||
|       return { | ||||
|         x, | ||||
|         value: { tag: "Error", value: errorValueToString(result.value) }, | ||||
|         value: { tag: "Error", value: result.value.toString() }, | ||||
|       }; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   let initialPartition: [ | ||||
|     { x: number; value: Distribution }[], | ||||
|     { x: number; value: SqDistribution }[], | ||||
|     { x: number; value: string }[] | ||||
|   ] = [[], []]; | ||||
| 
 | ||||
|  | @ -126,26 +133,23 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { | |||
|   let groupedErrors: errors = _.groupBy(errors, (x) => x.value); | ||||
| 
 | ||||
|   let percentiles: percentiles = functionImage.map(({ x, value }) => { | ||||
|     // We convert it to to a pointSet distribution first, so that in case its a sample set
 | ||||
|     // distribution, it doesn't internally convert it to a pointSet distribution for every
 | ||||
|     // single inv() call.
 | ||||
|     let toPointSet: Distribution = unwrap(value.toPointSet()); | ||||
|     return { | ||||
|     const res = { | ||||
|       x: x, | ||||
|       p1: unwrap(toPointSet.inv(0.01)), | ||||
|       p5: unwrap(toPointSet.inv(0.05)), | ||||
|       p10: unwrap(toPointSet.inv(0.1)), | ||||
|       p20: unwrap(toPointSet.inv(0.2)), | ||||
|       p30: unwrap(toPointSet.inv(0.3)), | ||||
|       p40: unwrap(toPointSet.inv(0.4)), | ||||
|       p50: unwrap(toPointSet.inv(0.5)), | ||||
|       p60: unwrap(toPointSet.inv(0.6)), | ||||
|       p70: unwrap(toPointSet.inv(0.7)), | ||||
|       p80: unwrap(toPointSet.inv(0.8)), | ||||
|       p90: unwrap(toPointSet.inv(0.9)), | ||||
|       p95: unwrap(toPointSet.inv(0.95)), | ||||
|       p99: unwrap(toPointSet.inv(0.99)), | ||||
|       p1: unwrap(value.inv(environment, 0.01)), | ||||
|       p5: unwrap(value.inv(environment, 0.05)), | ||||
|       p10: unwrap(value.inv(environment, 0.1)), | ||||
|       p20: unwrap(value.inv(environment, 0.2)), | ||||
|       p30: unwrap(value.inv(environment, 0.3)), | ||||
|       p40: unwrap(value.inv(environment, 0.4)), | ||||
|       p50: unwrap(value.inv(environment, 0.5)), | ||||
|       p60: unwrap(value.inv(environment, 0.6)), | ||||
|       p70: unwrap(value.inv(environment, 0.7)), | ||||
|       p80: unwrap(value.inv(environment, 0.8)), | ||||
|       p90: unwrap(value.inv(environment, 0.9)), | ||||
|       p95: unwrap(value.inv(environment, 0.95)), | ||||
|       p99: unwrap(value.inv(environment, 0.99)), | ||||
|     }; | ||||
|     return res; | ||||
|   }); | ||||
| 
 | ||||
|   return { percentiles, errors: groupedErrors }; | ||||
|  | @ -168,19 +172,20 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({ | |||
|   const signalListeners = { mousemove: handleHover, mouseout: handleOut }; | ||||
| 
 | ||||
|   //TODO: This custom error handling is a bit hacky and should be improved.
 | ||||
|   let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay | ||||
|     ? runForeign(fn, [mouseOverlay], environment) | ||||
|   let mouseItem: result<SqValue, SqError> = !!mouseOverlay | ||||
|     ? fn.call([mouseOverlay]) | ||||
|     : { | ||||
|         tag: "Error", | ||||
|         value: { | ||||
|           tag: "RETodo", | ||||
|           value: "Hover x-coordinate returned NaN. Expected a number.", | ||||
|         }, | ||||
|         value: SqError.createOtherError( | ||||
|           "Hover x-coordinate returned NaN. Expected a number." | ||||
|         ), | ||||
|       }; | ||||
|   let showChart = | ||||
|     mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? ( | ||||
|     mouseItem.tag === "Ok" && | ||||
|     mouseItem.value.tag === SqValueTag.Distribution ? ( | ||||
|       <DistributionChart | ||||
|         plot={defaultPlot(mouseItem.value.value)} | ||||
|         environment={environment} | ||||
|         width={400} | ||||
|         height={50} | ||||
|         {...distributionPlotSettings} | ||||
|  |  | |||
|  | @ -1,16 +1,11 @@ | |||
| import * as React from "react"; | ||||
| import _ from "lodash"; | ||||
| import type { Spec } from "vega"; | ||||
| import { | ||||
|   result, | ||||
|   lambdaValue, | ||||
|   environment, | ||||
|   runForeign, | ||||
|   errorValueToString, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; | ||||
| import { createClassFromSpec } from "react-vega"; | ||||
| import * as lineChartSpec from "../vega-specs/spec-line-chart.json"; | ||||
| import { ErrorAlert } from "./Alert"; | ||||
| import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag"; | ||||
| 
 | ||||
| let SquiggleLineChart = createClassFromSpec({ | ||||
|   spec: lineChartSpec as Spec, | ||||
|  | @ -30,7 +25,7 @@ export type FunctionChartSettings = { | |||
| }; | ||||
| 
 | ||||
| interface FunctionChart1NumberProps { | ||||
|   fn: lambdaValue; | ||||
|   fn: SqLambda; | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   environment: environment; | ||||
|   height: number; | ||||
|  | @ -38,7 +33,15 @@ interface FunctionChart1NumberProps { | |||
| 
 | ||||
| type point = { x: number; value: result<number, string> }; | ||||
| 
 | ||||
| let getFunctionImage = ({ chartSettings, fn, environment }) => { | ||||
| let getFunctionImage = ({ | ||||
|   chartSettings, | ||||
|   fn, | ||||
|   environment, | ||||
| }: { | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   fn: SqLambda; | ||||
|   environment: environment; | ||||
| }) => { | ||||
|   let chartPointsToRender = _rangeByCount( | ||||
|     chartSettings.start, | ||||
|     chartSettings.stop, | ||||
|  | @ -46,9 +49,9 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => { | |||
|   ); | ||||
| 
 | ||||
|   let chartPointsData: point[] = chartPointsToRender.map((x) => { | ||||
|     let result = runForeign(fn, [x], environment); | ||||
|     let result = fn.call([x]); | ||||
|     if (result.tag === "Ok") { | ||||
|       if (result.value.tag == "number") { | ||||
|       if (result.value.tag === SqValueTag.Number) { | ||||
|         return { x, value: { tag: "Ok", value: result.value.value } }; | ||||
|       } else { | ||||
|         return { | ||||
|  | @ -62,7 +65,7 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => { | |||
|     } else { | ||||
|       return { | ||||
|         x, | ||||
|         value: { tag: "Error", value: errorValueToString(result.value) }, | ||||
|         value: { tag: "Error", value: result.value.toString() }, | ||||
|       }; | ||||
|     } | ||||
|   }); | ||||
|  |  | |||
|  | @ -1,15 +1,14 @@ | |||
| import * as React from "react"; | ||||
| import { | ||||
|   squiggleExpression, | ||||
|   bindings, | ||||
|   SqValue, | ||||
|   environment, | ||||
|   jsImports, | ||||
|   defaultImports, | ||||
|   defaultBindings, | ||||
|   defaultEnvironment, | ||||
|   resultMap, | ||||
|   SqValueTag, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { useSquiggle } from "../lib/hooks"; | ||||
| import { SquiggleViewer } from "./SquiggleViewer"; | ||||
| import { JsImports } from "../lib/jsImports"; | ||||
| 
 | ||||
| export interface SquiggleChartProps { | ||||
|   /** The input string for squiggle */ | ||||
|  | @ -27,14 +26,12 @@ export interface SquiggleChartProps { | |||
|   /** If the result is a function, the amount of stops sampled */ | ||||
|   diagramCount?: number; | ||||
|   /** When the squiggle code gets reevaluated */ | ||||
|   onChange?(expr: squiggleExpression | undefined): void; | ||||
|   onChange?(expr: SqValue | undefined): void; | ||||
|   /** CSS width of the element */ | ||||
|   width?: number; | ||||
|   height?: number; | ||||
|   /** Bindings of previous variables declared */ | ||||
|   bindings?: bindings; | ||||
|   /** JS imported parameters */ | ||||
|   jsImports?: jsImports; | ||||
|   jsImports?: JsImports; | ||||
|   /** Whether to show a summary of the distribution */ | ||||
|   showSummary?: boolean; | ||||
|   /** Set the x scale to be logarithmic by deault */ | ||||
|  | @ -57,6 +54,7 @@ export interface SquiggleChartProps { | |||
| } | ||||
| 
 | ||||
| const defaultOnChange = () => {}; | ||||
| const defaultImports: JsImports = {}; | ||||
| 
 | ||||
| export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||
|   ({ | ||||
|  | @ -65,7 +63,6 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | |||
|     environment, | ||||
|     onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 | ||||
|     height = 200, | ||||
|     bindings = defaultBindings, | ||||
|     jsImports = defaultImports, | ||||
|     showSummary = false, | ||||
|     width, | ||||
|  | @ -82,9 +79,8 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | |||
|     distributionChartActions, | ||||
|     enableLocalSettings = false, | ||||
|   }) => { | ||||
|     const result = useSquiggle({ | ||||
|     const { result, bindings } = useSquiggle({ | ||||
|       code, | ||||
|       bindings, | ||||
|       environment, | ||||
|       jsImports, | ||||
|       onChange, | ||||
|  | @ -109,9 +105,13 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | |||
|       count: diagramCount, | ||||
|     }; | ||||
| 
 | ||||
|     const resultToRender = resultMap(result, (value) => | ||||
|       value.tag === SqValueTag.Void ? bindings.asValue() : value | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|       <SquiggleViewer | ||||
|         result={result} | ||||
|         result={resultToRender} | ||||
|         width={width} | ||||
|         height={height} | ||||
|         distributionPlotSettings={distributionPlotSettings} | ||||
|  |  | |||
|  | @ -1,11 +1,8 @@ | |||
| import React from "react"; | ||||
| import { CodeEditor } from "./CodeEditor"; | ||||
| import { environment, bindings, jsImports } from "@quri/squiggle-lang"; | ||||
| import { defaultImports, defaultBindings } from "@quri/squiggle-lang"; | ||||
| import { SquiggleContainer } from "./SquiggleContainer"; | ||||
| import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | ||||
| import { useSquigglePartial, useMaybeControlledValue } from "../lib/hooks"; | ||||
| import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; | ||||
| import { useMaybeControlledValue } from "../lib/hooks"; | ||||
| 
 | ||||
| const WrappedCodeEditor: React.FC<{ | ||||
|   code: string; | ||||
|  | @ -42,51 +39,3 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => { | |||
|     </SquiggleContainer> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export interface SquigglePartialProps { | ||||
|   /** The text inside the input (controlled) */ | ||||
|   code?: string; | ||||
|   /** The default text inside the input (unControlled) */ | ||||
|   defaultCode?: string; | ||||
|   /** when the environment changes. Used again for notebook magic*/ | ||||
|   onChange?(expr: bindings | undefined): void; | ||||
|   /** When the code changes */ | ||||
|   onCodeChange?(code: string): void; | ||||
|   /** Previously declared variables */ | ||||
|   bindings?: bindings; | ||||
|   /** If the output requires monte carlo sampling, the amount of samples */ | ||||
|   environment?: environment; | ||||
|   /** Variables imported from js */ | ||||
|   jsImports?: jsImports; | ||||
| } | ||||
| 
 | ||||
| export const SquigglePartial: React.FC<SquigglePartialProps> = ({ | ||||
|   code: controlledCode, | ||||
|   defaultCode = "", | ||||
|   onChange, | ||||
|   onCodeChange, | ||||
|   bindings = defaultBindings, | ||||
|   environment, | ||||
|   jsImports = defaultImports, | ||||
| }: SquigglePartialProps) => { | ||||
|   const [code, setCode] = useMaybeControlledValue<string>({ | ||||
|     value: controlledCode, | ||||
|     defaultValue: defaultCode, | ||||
|     onChange: onCodeChange, | ||||
|   }); | ||||
| 
 | ||||
|   const result = useSquigglePartial({ | ||||
|     code, | ||||
|     bindings, | ||||
|     environment, | ||||
|     jsImports, | ||||
|     onChange, | ||||
|   }); | ||||
| 
 | ||||
|   return ( | ||||
|     <SquiggleContainer> | ||||
|       <WrappedCodeEditor code={code} setCode={setCode} /> | ||||
|       {result.tag !== "Ok" ? <SquiggleErrorAlert error={result.value} /> : null} | ||||
|     </SquiggleContainer> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
|  | @ -1,52 +0,0 @@ | |||
| import React from "react"; | ||||
| import { SquiggleEditor } from "./SquiggleEditor"; | ||||
| import type { SquiggleEditorProps } from "./SquiggleEditor"; | ||||
| import { runPartial, defaultBindings } from "@quri/squiggle-lang"; | ||||
| import type { | ||||
|   result, | ||||
|   errorValue, | ||||
|   bindings as bindingsType, | ||||
| } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| function resultDefault(x: result<bindingsType, errorValue>): bindingsType { | ||||
|   switch (x.tag) { | ||||
|     case "Ok": | ||||
|       return x.value; | ||||
|     case "Error": | ||||
|       return defaultBindings; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & { | ||||
|   bindingsImportUrl: string; | ||||
| }; | ||||
| 
 | ||||
| export const SquiggleEditorWithImportedBindings: React.FC< | ||||
|   SquiggleEditorWithImportedBindingsProps | ||||
| > = (props) => { | ||||
|   const { bindingsImportUrl, ...editorProps } = props; | ||||
|   const [bindingsResult, setBindingsResult] = React.useState({ | ||||
|     tag: "Ok", | ||||
|     value: defaultBindings, | ||||
|   } as result<bindingsType, errorValue>); | ||||
|   React.useEffect(() => { | ||||
|     async function retrieveBindings(fileName: string) { | ||||
|       let contents = await fetch(fileName).then((response) => { | ||||
|         return response.text(); | ||||
|       }); | ||||
|       setBindingsResult( | ||||
|         runPartial( | ||||
|           contents, | ||||
|           editorProps.bindings, | ||||
|           editorProps.environment, | ||||
|           editorProps.jsImports | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|     retrieveBindings(bindingsImportUrl); | ||||
|   }, [bindingsImportUrl]); | ||||
|   const deliveredBindings = resultDefault(bindingsResult); | ||||
|   return ( | ||||
|     <SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} /> | ||||
|   ); | ||||
| }; | ||||
|  | @ -1,11 +1,11 @@ | |||
| import { errorValue, errorValueToString } from "@quri/squiggle-lang"; | ||||
| import { SqError } from "@quri/squiggle-lang"; | ||||
| import React from "react"; | ||||
| import { ErrorAlert } from "./Alert"; | ||||
| 
 | ||||
| type Props = { | ||||
|   error: errorValue; | ||||
|   error: SqError; | ||||
| }; | ||||
| 
 | ||||
| export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => { | ||||
|   return <ErrorAlert heading="Error">{errorValueToString(error)}</ErrorAlert>; | ||||
|   return <ErrorAlert heading="Error">{error.toString()}</ErrorAlert>; | ||||
| }; | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import { | |||
| } from "@heroicons/react/solid"; | ||||
| import clsx from "clsx"; | ||||
| 
 | ||||
| import { defaultBindings, environment } from "@quri/squiggle-lang"; | ||||
| import { environment } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | ||||
| import { CodeEditor } from "./CodeEditor"; | ||||
|  | @ -39,6 +39,7 @@ import { ViewSettings, viewSettingsSchema } from "./ViewSettings"; | |||
| import { HeadedSection } from "./ui/HeadedSection"; | ||||
| import { defaultTickFormat } from "../lib/distributionSpecBuilder"; | ||||
| import { Button } from "./ui/Button"; | ||||
| import { JsImports } from "../lib/jsImports"; | ||||
| 
 | ||||
| type PlaygroundProps = SquiggleChartProps & { | ||||
|   /** The initial squiggle string to put in the playground */ | ||||
|  | @ -112,8 +113,8 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({ | |||
| ); | ||||
| 
 | ||||
| const InputVariablesSettings: React.FC<{ | ||||
|   initialImports: any; // TODO - any json type
 | ||||
|   setImports: (imports: any) => void; | ||||
|   initialImports: JsImports; | ||||
|   setImports: (imports: JsImports) => void; | ||||
| }> = ({ initialImports, setImports }) => { | ||||
|   const [importString, setImportString] = useState(() => | ||||
|     JSON.stringify(initialImports) | ||||
|  | @ -122,7 +123,7 @@ const InputVariablesSettings: React.FC<{ | |||
| 
 | ||||
|   const onChange = (value: string) => { | ||||
|     setImportString(value); | ||||
|     let imports = {} as any; | ||||
|     let imports = {}; | ||||
|     try { | ||||
|       imports = JSON.parse(value); | ||||
|       setImportsAreValid(true); | ||||
|  | @ -251,7 +252,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|     onChange: onCodeChange, | ||||
|   }); | ||||
| 
 | ||||
|   const [imports, setImports] = useState({}); | ||||
|   const [imports, setImports] = useState<JsImports>({}); | ||||
| 
 | ||||
|   const { register, control } = useForm({ | ||||
|     resolver: yupResolver(schema), | ||||
|  | @ -309,7 +310,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|           executionId={executionId} | ||||
|           environment={env} | ||||
|           {...vars} | ||||
|           bindings={defaultBindings} | ||||
|           jsImports={imports} | ||||
|           enableLocalSettings={true} | ||||
|         /> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import React from "react"; | ||||
| import { squiggleExpression, declaration } from "@quri/squiggle-lang"; | ||||
| import React, { useContext } from "react"; | ||||
| import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang"; | ||||
| import { NumberShower } from "../NumberShower"; | ||||
| import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; | ||||
| import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; | ||||
|  | @ -8,7 +8,10 @@ import { VariableBox } from "./VariableBox"; | |||
| import { ItemSettingsMenu } from "./ItemSettingsMenu"; | ||||
| import { hasMassBelowZero } from "../../lib/distributionUtils"; | ||||
| import { MergedItemSettings } from "./utils"; | ||||
| import { ViewerContext } from "./ViewerContext"; | ||||
| 
 | ||||
| /* | ||||
| // DISABLED FOR 0.4 branch, for now
 | ||||
| function getRange<a>(x: declaration<a>) { | ||||
|   const first = x.args[0]; | ||||
|   switch (first.tag) { | ||||
|  | @ -31,15 +34,21 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings { | |||
|     count: 20, | ||||
|   }; | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| const VariableList: React.FC<{ | ||||
|   path: string[]; | ||||
|   value: SqValue; | ||||
|   heading: string; | ||||
|   children: (settings: MergedItemSettings) => React.ReactNode; | ||||
| }> = ({ path, heading, children }) => ( | ||||
|   <VariableBox path={path} heading={heading}> | ||||
| }> = ({ value, heading, children }) => ( | ||||
|   <VariableBox value={value} heading={heading}> | ||||
|     {(settings) => ( | ||||
|       <div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}> | ||||
|       <div | ||||
|         className={clsx( | ||||
|           "space-y-3", | ||||
|           value.location.path.items.length ? "pt-1 mt-1" : null | ||||
|         )} | ||||
|       > | ||||
|         {children(settings)} | ||||
|       </div> | ||||
|     )} | ||||
|  | @ -48,51 +57,44 @@ const VariableList: React.FC<{ | |||
| 
 | ||||
| export interface Props { | ||||
|   /** The output of squiggle's run */ | ||||
|   expression: squiggleExpression; | ||||
|   /** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */ | ||||
|   path: string[]; | ||||
|   value: SqValue; | ||||
|   width?: number; | ||||
| } | ||||
| 
 | ||||
| export const ExpressionViewer: React.FC<Props> = ({ | ||||
|   path, | ||||
|   expression, | ||||
|   width, | ||||
| }) => { | ||||
|   if (typeof expression !== "object") { | ||||
| export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | ||||
|   const { getMergedSettings } = useContext(ViewerContext); | ||||
| 
 | ||||
|   switch (value.tag) { | ||||
|     case SqValueTag.Number: | ||||
|       return ( | ||||
|       <VariableList path={path} heading="Error"> | ||||
|         {() => `Unknown expression: ${expression}`} | ||||
|       </VariableList> | ||||
|     ); | ||||
|   } | ||||
|   switch (expression.tag) { | ||||
|     case "number": | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Number"> | ||||
|         <VariableBox value={value} heading="Number"> | ||||
|           {() => ( | ||||
|             <div className="font-semibold text-slate-600"> | ||||
|               <NumberShower precision={3} number={expression.value} /> | ||||
|               <NumberShower precision={3} number={value.value} /> | ||||
|             </div> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "distribution": { | ||||
|       const distType = expression.value.type(); | ||||
|     case SqValueTag.Distribution: { | ||||
|       const distType = value.value.tag; | ||||
|       return ( | ||||
|         <VariableBox | ||||
|           path={path} | ||||
|           value={value} | ||||
|           heading={`Distribution (${distType})\n${ | ||||
|             distType === "Symbolic" ? expression.value.toString() : "" | ||||
|             distType === SqDistributionTag.Symbolic | ||||
|               ? value.value.toString() | ||||
|               : "" | ||||
|           }`}
 | ||||
|           renderSettingsMenu={({ onChange }) => { | ||||
|             const shape = expression.value.pointSet(); | ||||
|             const shape = value.value.pointSet( | ||||
|               getMergedSettings(value.location).environment | ||||
|             ); | ||||
|             return ( | ||||
|               <ItemSettingsMenu | ||||
|                 path={path} | ||||
|                 value={value} | ||||
|                 onChange={onChange} | ||||
|                 disableLogX={ | ||||
|                   shape.tag === "Ok" && hasMassBelowZero(shape.value) | ||||
|                   shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape()) | ||||
|                 } | ||||
|                 withFunctionSettings={false} | ||||
|               /> | ||||
|  | @ -102,7 +104,8 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|           {(settings) => { | ||||
|             return ( | ||||
|               <DistributionChart | ||||
|                 plot={defaultPlot(expression.value)} | ||||
|                 plot={defaultPlot(value.value)} | ||||
|                 environment={settings.environment} | ||||
|                 {...settings.distributionPlotSettings} | ||||
|                 height={settings.height} | ||||
|                 width={width} | ||||
|  | @ -112,77 +115,77 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|         </VariableBox> | ||||
|       ); | ||||
|     } | ||||
|     case "string": | ||||
|     case SqValueTag.String: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="String"> | ||||
|         <VariableBox value={value} heading="String"> | ||||
|           {() => ( | ||||
|             <> | ||||
|               <span className="text-slate-400">"</span> | ||||
|               <span className="text-slate-600 font-semibold font-mono"> | ||||
|                 {expression.value} | ||||
|                 {value.value} | ||||
|               </span> | ||||
|               <span className="text-slate-400">"</span> | ||||
|             </> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "boolean": | ||||
|     case SqValueTag.Bool: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Boolean"> | ||||
|           {() => expression.value.toString()} | ||||
|         <VariableBox value={value} heading="Boolean"> | ||||
|           {() => value.value.toString()} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "symbol": | ||||
|     case SqValueTag.Symbol: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Symbol"> | ||||
|         <VariableBox value={value} heading="Symbol"> | ||||
|           {() => ( | ||||
|             <> | ||||
|               <span className="text-slate-500 mr-2">Undefined Symbol:</span> | ||||
|               <span className="text-slate-600">{expression.value}</span> | ||||
|               <span className="text-slate-600">{value.value}</span> | ||||
|             </> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "call": | ||||
|     case SqValueTag.Call: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Call"> | ||||
|           {() => expression.value} | ||||
|         <VariableBox value={value} heading="Call"> | ||||
|           {() => value.value} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "arraystring": | ||||
|     case SqValueTag.ArrayString: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Array String"> | ||||
|           {() => expression.value.map((r) => `"${r}"`).join(", ")} | ||||
|         <VariableBox value={value} heading="Array String"> | ||||
|           {() => value.value.map((r) => `"${r}"`).join(", ")} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "date": | ||||
|     case SqValueTag.Date: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Date"> | ||||
|           {() => expression.value.toDateString()} | ||||
|         <VariableBox value={value} heading="Date"> | ||||
|           {() => value.value.toDateString()} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "void": | ||||
|     case SqValueTag.Void: | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Void"> | ||||
|         <VariableBox value={value} heading="Void"> | ||||
|           {() => "Void"} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "timeDuration": { | ||||
|     case SqValueTag.TimeDuration: { | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Time Duration"> | ||||
|           {() => <NumberShower precision={3} number={expression.value} />} | ||||
|         <VariableBox value={value} heading="Time Duration"> | ||||
|           {() => <NumberShower precision={3} number={value.value} />} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     } | ||||
|     case "lambda": | ||||
|     case SqValueTag.Lambda: | ||||
|       return ( | ||||
|         <VariableBox | ||||
|           path={path} | ||||
|           value={value} | ||||
|           heading="Function" | ||||
|           renderSettingsMenu={({ onChange }) => { | ||||
|             return ( | ||||
|               <ItemSettingsMenu | ||||
|                 path={path} | ||||
|                 value={value} | ||||
|                 onChange={onChange} | ||||
|                 withFunctionSettings={true} | ||||
|               /> | ||||
|  | @ -191,11 +194,11 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|         > | ||||
|           {(settings) => ( | ||||
|             <> | ||||
|               <div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join( | ||||
|                 "," | ||||
|               )})`}</div>
 | ||||
|               <div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value | ||||
|                 .parameters() | ||||
|                 .join(",")})`}</div>
 | ||||
|               <FunctionChart | ||||
|                 fn={expression.value} | ||||
|                 fn={value.value} | ||||
|                 chartSettings={settings.chartSettings} | ||||
|                 distributionPlotSettings={settings.distributionPlotSettings} | ||||
|                 height={settings.height} | ||||
|  | @ -208,47 +211,48 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "lambdaDeclaration": { | ||||
|     case SqValueTag.Declaration: { | ||||
|       return ( | ||||
|         <VariableBox | ||||
|           path={path} | ||||
|           value={value} | ||||
|           heading="Function Declaration" | ||||
|           renderSettingsMenu={({ onChange }) => { | ||||
|             return ( | ||||
|               <ItemSettingsMenu | ||||
|                 onChange={onChange} | ||||
|                 path={path} | ||||
|                 value={value} | ||||
|                 withFunctionSettings={true} | ||||
|               /> | ||||
|             ); | ||||
|           }} | ||||
|         > | ||||
|           {(settings) => ( | ||||
|             <FunctionChart | ||||
|               fn={expression.value.fn} | ||||
|               chartSettings={getChartSettings(expression.value)} | ||||
|               distributionPlotSettings={settings.distributionPlotSettings} | ||||
|               height={settings.height} | ||||
|               environment={{ | ||||
|                 sampleCount: settings.environment.sampleCount / 10, | ||||
|                 xyPointLength: settings.environment.xyPointLength / 10, | ||||
|               }} | ||||
|             /> | ||||
|             <div>NOT IMPLEMENTED IN 0.4 YET</div> | ||||
|             // <FunctionChart
 | ||||
|             //   fn={expression.value.fn}
 | ||||
|             //   chartSettings={getChartSettings(expression.value)}
 | ||||
|             //   distributionPlotSettings={settings.distributionPlotSettings}
 | ||||
|             //   height={settings.height}
 | ||||
|             //   environment={{
 | ||||
|             //     sampleCount: settings.environment.sampleCount / 10,
 | ||||
|             //     xyPointLength: settings.environment.xyPointLength / 10,
 | ||||
|             //   }}
 | ||||
|             // />
 | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     } | ||||
|     case "module": { | ||||
|     case SqValueTag.Module: { | ||||
|       return ( | ||||
|         <VariableList path={path} heading="Module"> | ||||
|         <VariableList value={value} heading="Module"> | ||||
|           {(_) => | ||||
|             Object.entries(expression.value) | ||||
|               .filter(([key, _]) => !key.match(/^(Math|System)\./)) | ||||
|             value.value | ||||
|               .entries() | ||||
|               .filter(([key, _]) => !key.match(/^(__result__)$/)) | ||||
|               .map(([key, r]) => ( | ||||
|                 <ExpressionViewer | ||||
|                   key={key} | ||||
|                   path={[...path, key]} | ||||
|                   expression={r} | ||||
|                   value={r} | ||||
|                   width={width !== undefined ? width - 20 : width} | ||||
|                 /> | ||||
|               )) | ||||
|  | @ -256,23 +260,26 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|         </VariableList> | ||||
|       ); | ||||
|     } | ||||
|     case "record": | ||||
|       const plot = makePlot(expression.value); | ||||
|     case SqValueTag.Record: | ||||
|       const plot = makePlot(value.value); | ||||
|       if (plot) { | ||||
|         return ( | ||||
|           <VariableBox | ||||
|             path={path} | ||||
|             heading={"Plot"} | ||||
|             value={value} | ||||
|             heading="Plot" | ||||
|             renderSettingsMenu={({ onChange }) => { | ||||
|               let disableLogX = plot.distributions.some((x) => { | ||||
|                 let pointSet = x.distribution.pointSet(); | ||||
|                 let pointSet = x.distribution.pointSet( | ||||
|                   getMergedSettings(value.location).environment | ||||
|                 ); | ||||
|                 return ( | ||||
|                   pointSet.tag === "Ok" && hasMassBelowZero(pointSet.value) | ||||
|                   pointSet.tag === "Ok" && | ||||
|                   hasMassBelowZero(pointSet.value.asShape()) | ||||
|                 ); | ||||
|               }); | ||||
|               return ( | ||||
|                 <ItemSettingsMenu | ||||
|                   path={path} | ||||
|                   value={value} | ||||
|                   onChange={onChange} | ||||
|                   disableLogX={disableLogX} | ||||
|                   withFunctionSettings={false} | ||||
|  | @ -284,6 +291,7 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|               return ( | ||||
|                 <DistributionChart | ||||
|                   plot={plot} | ||||
|                   environment={settings.environment} | ||||
|                   {...settings.distributionPlotSettings} | ||||
|                   height={settings.height} | ||||
|                   width={width} | ||||
|  | @ -294,13 +302,14 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|         ); | ||||
|       } else { | ||||
|         return ( | ||||
|           <VariableList path={path} heading="Record"> | ||||
|           <VariableList value={value} heading="Record"> | ||||
|             {(_) => | ||||
|               Object.entries(expression.value).map(([key, r]) => ( | ||||
|               value.value | ||||
|                 .entries() | ||||
|                 .map(([key, r]) => ( | ||||
|                   <ExpressionViewer | ||||
|                     key={key} | ||||
|                   path={[...path, key]} | ||||
|                   expression={r} | ||||
|                     value={r} | ||||
|                     width={width !== undefined ? width - 20 : width} | ||||
|                   /> | ||||
|                 )) | ||||
|  | @ -308,15 +317,16 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|           </VariableList> | ||||
|         ); | ||||
|       } | ||||
|     case "array": | ||||
|     case SqValueTag.Array: | ||||
|       return ( | ||||
|         <VariableList path={path} heading="Array"> | ||||
|         <VariableList value={value} heading="Array"> | ||||
|           {(_) => | ||||
|             expression.value.map((r, i) => ( | ||||
|             value.value | ||||
|               .getValues() | ||||
|               .map((r, i) => ( | ||||
|                 <ExpressionViewer | ||||
|                   key={i} | ||||
|                 path={[...path, String(i)]} | ||||
|                 expression={r} | ||||
|                   value={r} | ||||
|                   width={width !== undefined ? width - 20 : width} | ||||
|                 /> | ||||
|               )) | ||||
|  | @ -325,13 +335,11 @@ export const ExpressionViewer: React.FC<Props> = ({ | |||
|       ); | ||||
|     default: { | ||||
|       return ( | ||||
|         <VariableList path={path} heading="Error"> | ||||
|         <VariableList value={value} heading="Error"> | ||||
|           {() => ( | ||||
|             <div> | ||||
|               <span>No display for type: </span>{" "} | ||||
|               <span className="font-semibold text-slate-600"> | ||||
|                 {expression.tag} | ||||
|               </span> | ||||
|               <span className="font-semibold text-slate-600">{value.tag}</span> | ||||
|             </div> | ||||
|           )} | ||||
|         </VariableList> | ||||
|  |  | |||
|  | @ -4,13 +4,14 @@ import { useForm } from "react-hook-form"; | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | ||||
| import { Modal } from "../ui/Modal"; | ||||
| import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; | ||||
| import { Path, pathAsString } from "./utils"; | ||||
| import { ViewerContext } from "./ViewerContext"; | ||||
| import { defaultTickFormat } from "../../lib/distributionSpecBuilder"; | ||||
| import { PlaygroundContext } from "../SquigglePlayground"; | ||||
| import { SqValue } from "@quri/squiggle-lang"; | ||||
| import { locationAsString } from "./utils"; | ||||
| 
 | ||||
| type Props = { | ||||
|   path: Path; | ||||
|   value: SqValue; | ||||
|   onChange: () => void; | ||||
|   disableLogX?: boolean; | ||||
|   withFunctionSettings: boolean; | ||||
|  | @ -19,7 +20,7 @@ type Props = { | |||
| const ItemSettingsModal: React.FC< | ||||
|   Props & { close: () => void; resetScroll: () => void } | ||||
| > = ({ | ||||
|   path, | ||||
|   value, | ||||
|   onChange, | ||||
|   disableLogX, | ||||
|   withFunctionSettings, | ||||
|  | @ -29,7 +30,7 @@ const ItemSettingsModal: React.FC< | |||
|   const { setSettings, getSettings, getMergedSettings } = | ||||
|     useContext(ViewerContext); | ||||
| 
 | ||||
|   const mergedSettings = getMergedSettings(path); | ||||
|   const mergedSettings = getMergedSettings(value.location); | ||||
| 
 | ||||
|   const { register, watch } = useForm({ | ||||
|     resolver: yupResolver(viewSettingsSchema), | ||||
|  | @ -53,8 +54,8 @@ const ItemSettingsModal: React.FC< | |||
|   }); | ||||
|   useEffect(() => { | ||||
|     const subscription = watch((vars) => { | ||||
|       const settings = getSettings(path); // get the latest version
 | ||||
|       setSettings(path, { | ||||
|       const settings = getSettings(value.location); // get the latest version
 | ||||
|       setSettings(value.location, { | ||||
|         ...settings, | ||||
|         distributionPlotSettings: { | ||||
|           showSummary: vars.showSummary, | ||||
|  | @ -75,7 +76,7 @@ const ItemSettingsModal: React.FC< | |||
|       onChange(); | ||||
|     }); | ||||
|     return () => subscription.unsubscribe(); | ||||
|   }, [getSettings, setSettings, onChange, path, watch]); | ||||
|   }, [getSettings, setSettings, onChange, value.location, watch]); | ||||
| 
 | ||||
|   const { getLeftPanelElement } = useContext(PlaygroundContext); | ||||
| 
 | ||||
|  | @ -83,7 +84,7 @@ const ItemSettingsModal: React.FC< | |||
|     <Modal container={getLeftPanelElement()} close={close}> | ||||
|       <Modal.Header> | ||||
|         Chart settings | ||||
|         {path.length ? ( | ||||
|         {value.location.path.items.length ? ( | ||||
|           <> | ||||
|             {" for "} | ||||
|             <span | ||||
|  | @ -91,7 +92,7 @@ const ItemSettingsModal: React.FC< | |||
|               className="cursor-pointer" | ||||
|               onClick={resetScroll} | ||||
|             > | ||||
|               {pathAsString(path)} | ||||
|               {locationAsString(value.location)} | ||||
|             </span>{" "} | ||||
|           </> | ||||
|         ) : ( | ||||
|  | @ -120,7 +121,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => { | |||
|   if (!enableLocalSettings) { | ||||
|     return null; | ||||
|   } | ||||
|   const settings = getSettings(props.path); | ||||
|   const settings = getSettings(props.value.location); | ||||
| 
 | ||||
|   const resetScroll = () => { | ||||
|     if (!ref.current) return; | ||||
|  | @ -139,7 +140,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => { | |||
|       {settings.distributionPlotSettings || settings.chartSettings ? ( | ||||
|         <button | ||||
|           onClick={() => { | ||||
|             setSettings(props.path, { | ||||
|             setSettings(props.value.location, { | ||||
|               ...settings, | ||||
|               distributionPlotSettings: undefined, | ||||
|               chartSettings: undefined, | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import { SqValue, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| import React, { useContext, useReducer } from "react"; | ||||
| import { Tooltip } from "../ui/Tooltip"; | ||||
| import { LocalItemSettings, MergedItemSettings } from "./utils"; | ||||
|  | @ -8,14 +9,14 @@ type SettingsMenuParams = { | |||
| }; | ||||
| 
 | ||||
| type VariableBoxProps = { | ||||
|   path: string[]; | ||||
|   value: SqValue; | ||||
|   heading: string; | ||||
|   renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; | ||||
|   children: (settings: MergedItemSettings) => React.ReactNode; | ||||
| }; | ||||
| 
 | ||||
| export const VariableBox: React.FC<VariableBoxProps> = ({ | ||||
|   path, | ||||
|   value: { location }, | ||||
|   heading = "Error", | ||||
|   renderSettingsMenu, | ||||
|   children, | ||||
|  | @ -27,10 +28,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | |||
|   // So we use `forceUpdate` to force rerendering.
 | ||||
|   const [_, forceUpdate] = useReducer((x) => x + 1, 0); | ||||
| 
 | ||||
|   const settings = getSettings(path); | ||||
|   const settings = getSettings(location); | ||||
| 
 | ||||
|   const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { | ||||
|     setSettings(path, newSettings); | ||||
|     setSettings(location, newSettings); | ||||
|     forceUpdate(); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -38,8 +39,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | |||
|     setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); | ||||
|   }; | ||||
| 
 | ||||
|   const isTopLevel = path.length === 0; | ||||
|   const name = isTopLevel ? "Result" : path[path.length - 1]; | ||||
|   const isTopLevel = location.path.items.length === 0; | ||||
|   const name = isTopLevel | ||||
|     ? { result: "Result", bindings: "Bindings" }[location.path.root] | ||||
|     : location.path.items[location.path.items.length - 1]; | ||||
| 
 | ||||
|   return ( | ||||
|     <div> | ||||
|  | @ -65,13 +68,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | |||
|       </header> | ||||
|       {settings.collapsed ? null : ( | ||||
|         <div className="flex w-full"> | ||||
|           {path.length ? ( | ||||
|           {location.path.items.length ? ( | ||||
|             <div | ||||
|               className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" | ||||
|               onClick={toggleCollapsed} | ||||
|             ></div> | ||||
|           ) : null} | ||||
|           <div className="grow">{children(getMergedSettings(path))}</div> | ||||
|           <div className="grow">{children(getMergedSettings(location))}</div> | ||||
|         </div> | ||||
|       )} | ||||
|     </div> | ||||
|  |  | |||
|  | @ -1,14 +1,14 @@ | |||
| import { defaultEnvironment } from "@quri/squiggle-lang"; | ||||
| import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| import React from "react"; | ||||
| import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; | ||||
| import { LocalItemSettings, MergedItemSettings } from "./utils"; | ||||
| 
 | ||||
| type ViewerContextShape = { | ||||
|   // Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
 | ||||
|   // Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
 | ||||
|   // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
 | ||||
|   getSettings(path: Path): LocalItemSettings; | ||||
|   getMergedSettings(path: Path): MergedItemSettings; | ||||
|   setSettings(path: Path, value: LocalItemSettings): void; | ||||
|   getSettings(location: SqValueLocation): LocalItemSettings; | ||||
|   getMergedSettings(location: SqValueLocation): MergedItemSettings; | ||||
|   setSettings(location: SqValueLocation, value: LocalItemSettings): void; | ||||
|   enableLocalSettings: boolean; // show local settings icon in the UI
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,21 +1,20 @@ | |||
| import React, { useCallback, useRef } from "react"; | ||||
| import { environment } from "@quri/squiggle-lang"; | ||||
| import { environment, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| import { DistributionPlottingSettings } from "../DistributionChart"; | ||||
| import { FunctionChartSettings } from "../FunctionChart"; | ||||
| import { ExpressionViewer } from "./ExpressionViewer"; | ||||
| import { ViewerContext } from "./ViewerContext"; | ||||
| import { | ||||
|   LocalItemSettings, | ||||
|   locationAsString, | ||||
|   MergedItemSettings, | ||||
|   Path, | ||||
|   pathAsString, | ||||
| } from "./utils"; | ||||
| import { useSquiggle } from "../../lib/hooks"; | ||||
| import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; | ||||
| 
 | ||||
| type Props = { | ||||
|   /** The output of squiggle's run */ | ||||
|   result: ReturnType<typeof useSquiggle>; | ||||
|   result: ReturnType<typeof useSquiggle>["result"]; | ||||
|   width?: number; | ||||
|   height: number; | ||||
|   distributionPlotSettings: DistributionPlottingSettings; | ||||
|  | @ -45,22 +44,22 @@ export const SquiggleViewer: React.FC<Props> = ({ | |||
|   const settingsRef = useRef<Settings>({}); | ||||
| 
 | ||||
|   const getSettings = useCallback( | ||||
|     (path: Path) => { | ||||
|       return settingsRef.current[pathAsString(path)] || defaultSettings; | ||||
|     (location: SqValueLocation) => { | ||||
|       return settingsRef.current[locationAsString(location)] || defaultSettings; | ||||
|     }, | ||||
|     [settingsRef] | ||||
|   ); | ||||
| 
 | ||||
|   const setSettings = useCallback( | ||||
|     (path: Path, value: LocalItemSettings) => { | ||||
|       settingsRef.current[pathAsString(path)] = value; | ||||
|     (location: SqValueLocation, value: LocalItemSettings) => { | ||||
|       settingsRef.current[locationAsString(location)] = value; | ||||
|     }, | ||||
|     [settingsRef] | ||||
|   ); | ||||
| 
 | ||||
|   const getMergedSettings = useCallback( | ||||
|     (path: Path) => { | ||||
|       const localSettings = getSettings(path); | ||||
|     (location: SqValueLocation) => { | ||||
|       const localSettings = getSettings(location); | ||||
|       const result: MergedItemSettings = { | ||||
|         distributionPlotSettings: { | ||||
|           ...distributionPlotSettings, | ||||
|  | @ -91,7 +90,7 @@ export const SquiggleViewer: React.FC<Props> = ({ | |||
|       }} | ||||
|     > | ||||
|       {result.tag === "Ok" ? ( | ||||
|         <ExpressionViewer path={[]} expression={result.value} width={width} /> | ||||
|         <ExpressionViewer value={result.value} width={width} /> | ||||
|       ) : ( | ||||
|         <SquiggleErrorAlert error={result.value} /> | ||||
|       )} | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { DistributionPlottingSettings } from "../DistributionChart"; | ||||
| import { FunctionChartSettings } from "../FunctionChart"; | ||||
| import { environment } from "@quri/squiggle-lang"; | ||||
| import { environment, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export type LocalItemSettings = { | ||||
|   collapsed: boolean; | ||||
|  | @ -17,6 +17,5 @@ export type MergedItemSettings = { | |||
|   environment: environment; | ||||
| }; | ||||
| 
 | ||||
| export type Path = string[]; | ||||
| 
 | ||||
| export const pathAsString = (path: Path) => path.join("."); | ||||
| export const locationAsString = (location: SqValueLocation) => | ||||
|   location.path.items.join("."); | ||||
|  |  | |||
|  | @ -1,7 +1,4 @@ | |||
| export { SquiggleChart } from "./components/SquiggleChart"; | ||||
| export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor"; | ||||
| export { SquiggleEditor } from "./components/SquiggleEditor"; | ||||
| export { SquigglePlayground } from "./components/SquigglePlayground"; | ||||
| export { SquiggleContainer } from "./components/SquiggleContainer"; | ||||
| export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings"; | ||||
| 
 | ||||
| export { mergeBindings } from "@quri/squiggle-lang"; | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { shape } from "@quri/squiggle-lang"; | ||||
| import { SqShape } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export const hasMassBelowZero = (shape: shape) => | ||||
| export const hasMassBelowZero = (shape: SqShape) => | ||||
|   shape.continuous.some((x) => x.x <= 0) || | ||||
|   shape.discrete.some((x) => x.x <= 0); | ||||
|  |  | |||
|  | @ -1,3 +1,3 @@ | |||
| export { useMaybeControlledValue } from "./useMaybeControlledValue"; | ||||
| export { useSquiggle, useSquigglePartial } from "./useSquiggle"; | ||||
| export { useSquiggle } from "./useSquiggle"; | ||||
| export { useRunnerState } from "./useRunnerState"; | ||||
|  |  | |||
|  | @ -1,53 +1,42 @@ | |||
| import { | ||||
|   bindings, | ||||
|   environment, | ||||
|   jsImports, | ||||
|   run, | ||||
|   runPartial, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { environment, SqProject, SqValue } from "@quri/squiggle-lang"; | ||||
| import { useEffect, useMemo } from "react"; | ||||
| import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; | ||||
| 
 | ||||
| type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = { | ||||
| type SquiggleArgs = { | ||||
|   code: string; | ||||
|   executionId?: number; | ||||
|   bindings?: bindings; | ||||
|   jsImports?: jsImports; | ||||
|   jsImports?: JsImports; | ||||
|   environment?: environment; | ||||
|   onChange?: (expr: Extract<T, { tag: "Ok" }>["value"] | undefined) => void; | ||||
|   onChange?: (expr: SqValue | undefined) => void; | ||||
| }; | ||||
| 
 | ||||
| const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>( | ||||
|   args: SquiggleArgs<T>, | ||||
|   f: (...args: Parameters<typeof run>) => T | ||||
| ) => { | ||||
|   const result: T = useMemo<T>( | ||||
|     () => f(args.code, args.bindings, args.environment, args.jsImports), | ||||
| export const useSquiggle = (args: SquiggleArgs) => { | ||||
|   const result = useMemo( | ||||
|     () => { | ||||
|       const project = SqProject.create(); | ||||
|       project.setSource("main", args.code); | ||||
|       if (args.environment) { | ||||
|         project.setEnvironment(args.environment); | ||||
|       } | ||||
|       if (args.jsImports && Object.keys(args.jsImports).length) { | ||||
|         const importsSource = jsImportsToSquiggleCode(args.jsImports); | ||||
|         project.setSource("imports", importsSource); | ||||
|         project.setContinues("main", ["imports"]); | ||||
|       } | ||||
|       project.run("main"); | ||||
|       const result = project.getResult("main"); | ||||
|       const bindings = project.getBindings("main"); | ||||
|       return { result, bindings }; | ||||
|     }, | ||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     [ | ||||
|       f, | ||||
|       args.code, | ||||
|       args.bindings, | ||||
|       args.environment, | ||||
|       args.jsImports, | ||||
|       args.executionId, | ||||
|     ] | ||||
|     [args.code, args.environment, args.jsImports, args.executionId] | ||||
|   ); | ||||
| 
 | ||||
|   const { onChange } = args; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     onChange?.(result.tag === "Ok" ? result.value : undefined); | ||||
|     onChange?.(result.result.tag === "Ok" ? result.result.value : undefined); | ||||
|   }, [result, onChange]); | ||||
| 
 | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| export const useSquigglePartial = ( | ||||
|   args: SquiggleArgs<ReturnType<typeof runPartial>> | ||||
| ) => { | ||||
|   return useSquiggleAny(args, runPartial); | ||||
| }; | ||||
| 
 | ||||
| export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => { | ||||
|   return useSquiggleAny(args, run); | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										51
									
								
								packages/components/src/lib/jsImports.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								packages/components/src/lib/jsImports.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| type JsImportsValue = | ||||
|   | number | ||||
|   | string | ||||
|   | JsImportsValue[] | ||||
|   | { | ||||
|       [k: string]: JsImportsValue; | ||||
|     }; | ||||
| 
 | ||||
| export type JsImports = { | ||||
|   [k: string]: JsImportsValue; | ||||
| }; | ||||
| 
 | ||||
| const quote = (arg: string) => `"${arg.replace(new RegExp('"', "g"), '\\"')}"`; | ||||
| 
 | ||||
| const jsImportsValueToSquiggleCode = (v: JsImportsValue): string => { | ||||
|   if (typeof v === "number") { | ||||
|     return String(v); | ||||
|   } else if (typeof v === "string") { | ||||
|     return quote(v); | ||||
|   } else if (v instanceof Array) { | ||||
|     return "[" + v.map((x) => jsImportsValueToSquiggleCode(x)) + "]"; | ||||
|   } else { | ||||
|     if (Object.keys(v).length) { | ||||
|       return ( | ||||
|         "{" + | ||||
|         Object.entries(v) | ||||
|           .map(([k, v]) => `${quote(k)}:${jsImportsValueToSquiggleCode(v)},`) | ||||
|           .join("") + | ||||
|         "}" | ||||
|       ); | ||||
|     } else { | ||||
|       return "0"; // squiggle doesn't support empty `{}`
 | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export const jsImportsToSquiggleCode = (v: JsImports) => { | ||||
|   const validId = new RegExp("[a-zA-Z][[a-zA-Z0-9]*"); | ||||
|   let result = Object.entries(v) | ||||
|     .map(([k, v]) => { | ||||
|       if (!k.match(validId)) { | ||||
|         return ""; // skipping without warnings; can be improved
 | ||||
|       } | ||||
|       return `$${k} = ${jsImportsValueToSquiggleCode(v)}\n`; | ||||
|     }) | ||||
|     .join(""); | ||||
|   if (!result) { | ||||
|     result = "$__no_valid_imports__ = 1"; // without this generated squiggle code can be invalid
 | ||||
|   } | ||||
|   return result; | ||||
| }; | ||||
|  | @ -1,9 +1,9 @@ | |||
| import * as yup from "yup"; | ||||
| import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang"; | ||||
| import { SqDistribution, result, SqRecord } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export type LabeledDistribution = { | ||||
|   name: string; | ||||
|   distribution: Distribution; | ||||
|   distribution: SqDistribution; | ||||
|   color?: string; | ||||
| }; | ||||
| 
 | ||||
|  | @ -53,9 +53,7 @@ const schema = yup | |||
|     }), | ||||
|   }); | ||||
| 
 | ||||
| export function parsePlot(record: { | ||||
|   [key: string]: squiggleExpression; | ||||
| }): result<Plot, string> { | ||||
| export function parsePlot(record: SqRecord): result<Plot, string> { | ||||
|   try { | ||||
|     const plotRecord = schema.validateSync(record); | ||||
|     return ok({ | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("Name Space", () => { | ||||
|   let value = InternalExpressionValue.IEvNumber(1967.0) | ||||
|   let nameSpace = Bindings.emptyNameSpace->Bindings.set("value", value) | ||||
|   test("get", () => { | ||||
|     expect(Bindings.get(nameSpace, "value")) == Some(value) | ||||
|   }) | ||||
| 
 | ||||
|   test("chain and get", () => { | ||||
|     let mainNameSpace = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace]) | ||||
|     expect(Bindings.get(mainNameSpace, "value")) == Some(value) | ||||
|   }) | ||||
| 
 | ||||
|   test("chain and set", () => { | ||||
|     let mainNameSpace0 = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace]) | ||||
|     let mainNameSpace = | ||||
|       mainNameSpace0->Bindings.set("value", InternalExpressionValue.IEvNumber(1968.0)) | ||||
|     expect(Bindings.get(mainNameSpace, "value")) == Some(InternalExpressionValue.IEvNumber(1968.0)) | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,10 +1,14 @@ | |||
| module ExpressionValue = ReducerInterface.ExternalExpressionValue | ||||
| module ExpressionValue = ReducerInterface.InternalExpressionValue | ||||
| module Expression = Reducer_Expression | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| let expectEvalToBe = (expr: string, answer: string) => | ||||
|   Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) | ||||
| let expectEvalToBe = (sourceCode: string, answer: string) => | ||||
|   Expression.BackCompatible.evaluateString(sourceCode) | ||||
|   ->ExpressionValue.toStringResult | ||||
|   ->expect | ||||
|   ->toBe(answer) | ||||
| 
 | ||||
| let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| // Reducer_Helpers | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue | ||||
| module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
|  | @ -15,8 +14,4 @@ let removeDefaultsInternal = (iev: InternalExpressionValue.t) => { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| let removeDefaultsExternal = (ev: ExternalExpressionValue.t): ExternalExpressionValue.t => | ||||
|   ev->InternalExpressionValue.toInternal->removeDefaultsInternal->InternalExpressionValue.toExternal | ||||
| 
 | ||||
| let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal) | ||||
| let rRemoveDefaultsExternal = r => Belt.Result.map(r, removeDefaultsExternal) | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| module MathJs = Reducer_MathJs | ||||
| module ErrorValue = Reducer.ErrorValue | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| 
 | ||||
| open Jest | ||||
| open ExpectJs | ||||
|  |  | |||
|  | @ -3,241 +3,346 @@ open Reducer_Peggy_TestHelpers | |||
| 
 | ||||
| describe("Peggy parse", () => { | ||||
|   describe("float", () => { | ||||
|     testParse("1.", "{1}") | ||||
|     testParse("1.1", "{1.1}") | ||||
|     testParse(".1", "{0.1}") | ||||
|     testParse("0.1", "{0.1}") | ||||
|     testParse("1e1", "{10}") | ||||
|     testParse("1e-1", "{0.1}") | ||||
|     testParse(".1e1", "{1}") | ||||
|     testParse("0.1e1", "{1}") | ||||
|     testParse("1.", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("1.1", "{(::$_endOfOuterBlock_$ () 1.1)}") | ||||
|     testParse(".1", "{(::$_endOfOuterBlock_$ () 0.1)}") | ||||
|     testParse("0.1", "{(::$_endOfOuterBlock_$ () 0.1)}") | ||||
|     testParse("1e1", "{(::$_endOfOuterBlock_$ () 10)}") | ||||
|     testParse("1e-1", "{(::$_endOfOuterBlock_$ () 0.1)}") | ||||
|     testParse(".1e1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("0.1e1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|   }) | ||||
| 
 | ||||
|   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("'hello'", "{'hello'}") | ||||
|     testParse("true", "{true}") | ||||
|     testParse("1+2", "{(::add 1 2)}") | ||||
|     testParse("add(1,2)", "{(::add 1 2)}") | ||||
|     testParse("(1)", "{1}") | ||||
|     testParse("(1+2)", "{(::add 1 2)}") | ||||
|     testParse("1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("'hello'", "{(::$_endOfOuterBlock_$ () 'hello')}") | ||||
|     testParse("true", "{(::$_endOfOuterBlock_$ () true)}") | ||||
|     testParse("1+2", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||
|     testParse("add(1,2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||
|     testParse("(1)", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("(1+2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("unary", () => { | ||||
|     testParse("-1", "{(::unaryMinus 1)}") | ||||
|     testParse("!true", "{(::not true)}") | ||||
|     testParse("1 + -1", "{(::add 1 (::unaryMinus 1))}") | ||||
|     testParse("-a[0]", "{(::unaryMinus (::$_atIndex_$ :a 0))}") | ||||
|     testParse("!a[0]", "{(::not (::$_atIndex_$ :a 0))}") | ||||
|     testParse("-1", "{(::$_endOfOuterBlock_$ () (::unaryMinus 1))}") | ||||
|     testParse("!true", "{(::$_endOfOuterBlock_$ () (::not true))}") | ||||
|     testParse("1 + -1", "{(::$_endOfOuterBlock_$ () (::add 1 (::unaryMinus 1)))}") | ||||
|     testParse("-a[0]", "{(::$_endOfOuterBlock_$ () (::unaryMinus (::$_atIndex_$ :a 0)))}") | ||||
|     testParse("!a[0]", "{(::$_endOfOuterBlock_$ () (::not (::$_atIndex_$ :a 0)))}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("multiplicative", () => { | ||||
|     testParse("1 * 2", "{(::multiply 1 2)}") | ||||
|     testParse("1 / 2", "{(::divide 1 2)}") | ||||
|     testParse("1 * 2 * 3", "{(::multiply (::multiply 1 2) 3)}") | ||||
|     testParse("1 * 2 / 3", "{(::divide (::multiply 1 2) 3)}") | ||||
|     testParse("1 / 2 * 3", "{(::multiply (::divide 1 2) 3)}") | ||||
|     testParse("1 / 2 / 3", "{(::divide (::divide 1 2) 3)}") | ||||
|     testParse("1 * 2 + 3 * 4", "{(::add (::multiply 1 2) (::multiply 3 4))}") | ||||
|     testParse("1 * 2 - 3 * 4", "{(::subtract (::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("1 * 2 + 3 .* 4", "{(::add (::multiply 1 2) (::dotMultiply 3 4))}") | ||||
|     testParse("1 * 2 + 3 / 4", "{(::add (::multiply 1 2) (::divide 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("1 * 2 - 3 / 4", "{(::subtract (::multiply 1 2) (::divide 3 4))}") | ||||
|     testParse("1 * 2 - 3 ./ 4", "{(::subtract (::multiply 1 2) (::dotDivide 3 4))}") | ||||
|     testParse("1 * 2 - 3 * 4^5", "{(::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5)))}") | ||||
|     testParse("1 * 2", "{(::$_endOfOuterBlock_$ () (::multiply 1 2))}") | ||||
|     testParse("1 / 2", "{(::$_endOfOuterBlock_$ () (::divide 1 2))}") | ||||
|     testParse("1 * 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::multiply 1 2) 3))}") | ||||
|     testParse("1 * 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::multiply 1 2) 3))}") | ||||
|     testParse("1 / 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::divide 1 2) 3))}") | ||||
|     testParse("1 / 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::divide 1 2) 3))}") | ||||
|     testParse( | ||||
|       "1 * 2 + 3 * 4", | ||||
|       "{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::multiply 3 4)))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "1 * 2 - 3 * 4", | ||||
|       "{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 4)))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "1 * 2 .+ 3 * 4", | ||||
|       "{(::$_endOfOuterBlock_$ () (::dotAdd (::multiply 1 2) (::multiply 3 4)))}", | ||||
|     ) | ||||
|     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( | ||||
|       "1 * 2 - 3 * 4^5^6", | ||||
|       "{(::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6)))}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::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", () => { | ||||
|     testParse("x=1; 2", "{:x = {1}; 2}") | ||||
|     testParse("x=1; y=2", "{:x = {1}; :y = {2}}") | ||||
|     testParse("x=1; 2", "{:x = {1}; (::$_endOfOuterBlock_$ () 2)}") | ||||
|     testParse("x=1; y=2", "{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("variables", () => { | ||||
|     testParse("x = 1", "{:x = {1}}") | ||||
|     testParse("x", "{:x}") | ||||
|     testParse("x = 1; x", "{:x = {1}; :x}") | ||||
|     testParse("x = 1", "{:x = {1}; (::$_endOfOuterBlock_$ () ())}") | ||||
|     testParse("x", "{(::$_endOfOuterBlock_$ () :x)}") | ||||
|     testParse("x = 1; x", "{:x = {1}; (::$_endOfOuterBlock_$ () :x)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("functions", () => { | ||||
|     testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments | ||||
|     testParse("identity(x)", "{(::identity :x)}") | ||||
|     testParse("identity(x) = x", "{:identity = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions become lambda assignments | ||||
|     testParse("identity(x)", "{(::$_endOfOuterBlock_$ () (::identity :x))}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("arrays", () => { | ||||
|     testParse("[]", "{(::$_constructArray_$ ())}") | ||||
|     testParse("[0, 1, 2]", "{(::$_constructArray_$ (0 1 2))}") | ||||
|     testParse("['hello', 'world']", "{(::$_constructArray_$ ('hello' 'world'))}") | ||||
|     testParse("([0,1,2])[1]", "{(::$_atIndex_$ (::$_constructArray_$ (0 1 2)) 1)}") | ||||
|     testParse("[]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ ()))}") | ||||
|     testParse("[0, 1, 2]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ (0 1 2)))}") | ||||
|     testParse( | ||||
|       "['hello', 'world']", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ ('hello' 'world')))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "([0,1,2])[1]", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$_atIndex_$ (::$_constructArray_$ (0 1 2)) 1))}", | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("records", () => { | ||||
|     testParse("{a: 1, b: 2}", "{(::$_constructRecord_$ ('a': 1 'b': 2))}") | ||||
|     testParse("{1+0: 1, 2+0: 2}", "{(::$_constructRecord_$ ((::add 1 0): 1 (::add 2 0): 2))}") // key can be any expression | ||||
|     testParse("record.property", "{(::$_atIndex_$ :record 'property')}") | ||||
|     testParse( | ||||
|       "{a: 1, b: 2}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$_constructRecord_$ ('a': 1 'b': 2)))}", | ||||
|     ) | ||||
|     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", () => { | ||||
|     //function call, array and record access are post operators with higher priority than unary operators | ||||
|     testParse("a==!b(1)", "{(::equal :a (::not (::b 1)))}") | ||||
|     testParse("a==!b[1]", "{(::equal :a (::not (::$_atIndex_$ :b 1)))}") | ||||
|     testParse("a==!b.one", "{(::equal :a (::not (::$_atIndex_$ :b 'one')))}") | ||||
|     testParse("a==!b(1)", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::b 1))))}") | ||||
|     testParse("a==!b[1]", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 1))))}") | ||||
|     testParse( | ||||
|       "a==!b.one", | ||||
|       "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 'one'))))}", | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("comments", () => { | ||||
|     testParse("1 # This is a line comment", "{1}") | ||||
|     testParse("1 // This is a line comment", "{1}") | ||||
|     testParse("1 /* This is a multi line comment */", "{1}") | ||||
|     testParse("/* This is a multi line comment */ 1", "{1}") | ||||
|     testParse("1 # This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("1 // This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("1 /* This is a multi line comment */", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse("/* This is a multi line comment */ 1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||
|     testParse( | ||||
|       ` | ||||
|   /* This is  | ||||
|   a multi line  | ||||
|   comment */ | ||||
|   1`, | ||||
|       "{1}", | ||||
|       "{(::$_endOfOuterBlock_$ () 1)}", | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("ternary operator", () => { | ||||
|     testParse("true ? 2 : 3", "{(::$$_ternary_$$ true 2 3)}") | ||||
|     testParse("true ? 2 : 3", "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true 2 3))}") | ||||
|     testParse( | ||||
|       "false ? 2 : false ? 4 : 5", | ||||
|       "{(::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5))}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5)))}", | ||||
|     ) // nested ternary | ||||
|   }) | ||||
| 
 | ||||
|   describe("if then else", () => { | ||||
|     testParse("if true then 2 else 3", "{(::$$_ternary_$$ true {2} {3})}") | ||||
|     testParse("if false then {2} else {3}", "{(::$$_ternary_$$ false {2} {3})}") | ||||
|     testParse( | ||||
|       "if true then 2 else 3", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true {2} {3}))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "if false then {2} else {3}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} {3}))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "if false then {2} else if false then {4} else {5}", | ||||
|       "{(::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5}))}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5})))}", | ||||
|     ) //nested if | ||||
|   }) | ||||
| 
 | ||||
|   describe("logical", () => { | ||||
|     testParse("true || false", "{(::or true false)}") | ||||
|     testParse("true && false", "{(::and true false)}") | ||||
|     testParse("a * b + c", "{(::add (::multiply :a :b) :c)}") // for comparison | ||||
|     testParse("a && b || c", "{(::or (::and :a :b) :c)}") | ||||
|     testParse("a && b || c && d", "{(::or (::and :a :b) (::and :c :d))}") | ||||
|     testParse("a && !b || c", "{(::or (::and :a (::not :b)) :c)}") | ||||
|     testParse("a && b==c || d", "{(::or (::and :a (::equal :b :c)) :d)}") | ||||
|     testParse("a && b!=c || d", "{(::or (::and :a (::unequal :b :c)) :d)}") | ||||
|     testParse("a && !(b==c) || d", "{(::or (::and :a (::not (::equal :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("a && b<=c || d", "{(::or (::and :a (::smallerEq :b :c)) :d)}") | ||||
|     testParse("a && b>c || d", "{(::or (::and :a (::larger :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("a && b<c.i || d", "{(::or (::and :a (::smaller :b (::$_atIndex_$ :c 'i'))) :d)}") | ||||
|     testParse("a && b<c(i) || d", "{(::or (::and :a (::smaller :b (::c :i))) :d)}") | ||||
|     testParse("a && b<1+2 || d", "{(::or (::and :a (::smaller :b (::add 1 2))) :d)}") | ||||
|     testParse("true || false", "{(::$_endOfOuterBlock_$ () (::or true false))}") | ||||
|     testParse("true && false", "{(::$_endOfOuterBlock_$ () (::and true false))}") | ||||
|     testParse("a * b + c", "{(::$_endOfOuterBlock_$ () (::add (::multiply :a :b) :c))}") // for comparison | ||||
|     testParse("a && b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) :c))}") | ||||
|     testParse("a && b || c && d", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) (::and :c :d)))}") | ||||
|     testParse("a && !b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::not :b)) :c))}") | ||||
|     testParse("a && b==c || d", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::equal :b :c)) :d))}") | ||||
|     testParse( | ||||
|       "a && b!=c || d", | ||||
|       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::unequal :b :c)) :d))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "a && !(b==c) || d", | ||||
|       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::not (::equal :b :c))) :d))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "a && b>=c || d", | ||||
|       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::largerEq :b :c)) :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", | ||||
|       "{(::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d)}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "a && b<1+2*-3+4 || d", | ||||
|       "{(::or (::and :a (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4))) :d)}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4))) :d))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "a && b<1+2*3 || d ? true : false", | ||||
|       "{(::$$_ternary_$$ (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d) true false)}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d) true false))}", | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("pipe", () => { | ||||
|     testParse("1 -> add(2)", "{(::add 1 2)}") | ||||
|     testParse("-1 -> add(2)", "{(::add (::unaryMinus 1) 2)}") | ||||
|     testParse("-a[1] -> add(2)", "{(::add (::unaryMinus (::$_atIndex_$ :a 1)) 2)}") | ||||
|     testParse("-f(1) -> add(2)", "{(::add (::unaryMinus (::f 1)) 2)}") | ||||
|     testParse("1 + 2 -> add(3)", "{(::add 1 (::add 2 3))}") | ||||
|     testParse("1 -> add(2) * 3", "{(::multiply (::add 1 2) 3)}") | ||||
|     testParse("1 -> subtract(2)", "{(::subtract 1 2)}") | ||||
|     testParse("-1 -> subtract(2)", "{(::subtract (::unaryMinus 1) 2)}") | ||||
|     testParse("1 -> subtract(2) * 3", "{(::multiply (::subtract 1 2) 3)}") | ||||
|     testParse("1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||
|     testParse("-1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus 1) 2))}") | ||||
|     testParse( | ||||
|       "-a[1] -> add(2)", | ||||
|       "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::$_atIndex_$ :a 1)) 2))}", | ||||
|     ) | ||||
|     testParse("-f(1) -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::f 1)) 2))}") | ||||
|     testParse("1 + 2 -> add(3)", "{(::$_endOfOuterBlock_$ () (::add 1 (::add 2 3)))}") | ||||
|     testParse("1 -> add(2) * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::add 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", () => { | ||||
|     //handled together with -> so there is no need for seperate tests | ||||
|     testParse("1 |> add(2)", "{(::add 1 2)}") | ||||
|     testParse("1 |> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("to", () => { | ||||
|     testParse("1 to 2", "{(::credibleIntervalToDistribution 1 2)}") | ||||
|     testParse("-1 to -2", "{(::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2))}") // lower than unary | ||||
|     testParse("1 to 2", "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution 1 2))}") | ||||
|     testParse( | ||||
|       "-1 to -2", | ||||
|       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2)))}", | ||||
|     ) // lower than unary | ||||
|     testParse( | ||||
|       "a[1] to a[2]", | ||||
|       "{(::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2))}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2)))}", | ||||
|     ) // lower than post | ||||
|     testParse( | ||||
|       "a.p1 to a.p2", | ||||
|       "{(::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2'))}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2')))}", | ||||
|     ) // lower than post | ||||
|     testParse("1 to 2 + 3", "{(::add (::credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators | ||||
|     testParse( | ||||
|       "1 to 2 + 3", | ||||
|       "{(::$_endOfOuterBlock_$ () (::add (::credibleIntervalToDistribution 1 2) 3))}", | ||||
|     ) // higher than binary operators | ||||
|     testParse( | ||||
|       "1->add(2) to 3->add(4) -> add(4)", | ||||
|       "{(::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4))}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4)))}", | ||||
|     ) // lower than chain | ||||
|   }) | ||||
| 
 | ||||
|   describe("inner block", () => { | ||||
|     // inner blocks are 0 argument lambdas. They can be used whenever a value is required. | ||||
|     // Like lambdas they have a local scope. | ||||
|     testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; :x}") | ||||
|     testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; (::$_endOfOuterBlock_$ () :x)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("lambda", () => { | ||||
|     testParse("{|x| x}", "{{|:x| {:x}}}") | ||||
|     testParse("f={|x| x}", "{:f = {{|:x| {:x}}}}") | ||||
|     testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments | ||||
|     testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments | ||||
|     testParse("{|x| x}", "{(::$_endOfOuterBlock_$ () {|:x| {:x}})}") | ||||
|     testParse("f={|x| x}", "{:f = {{|:x| {:x}}}; (::$_endOfOuterBlock_$ () ())}") | ||||
|     testParse("f(x)=x", "{:f = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions are lambda assignments | ||||
|     testParse( | ||||
|       "f(x)=x ? 1 : 0", | ||||
|       "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}; (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) // Function definitions are lambda assignments | ||||
|   }) | ||||
| 
 | ||||
|   describe("Using lambda as value", () => { | ||||
|     testParse( | ||||
|       "myadd(x,y)=x+y; z=myadd; z", | ||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; :z}", | ||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; (::$_endOfOuterBlock_$ () :z)}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "myadd(x,y)=x+y; z=[myadd]; z", | ||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ (:myadd))}; :z}", | ||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ (:myadd))}; (::$_endOfOuterBlock_$ () :z)}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "myaddd(x,y)=x+y; z={x: myaddd}; z", | ||||
|       "{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; :z}", | ||||
|       "{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; (::$_endOfOuterBlock_$ () :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("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 (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}}))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "[1,2,3]->map({|x| x+1})", | ||||
|       "{(::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}", | ||||
|       "{(::$_endOfOuterBlock_$ () (::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}}))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("unit", () => { | ||||
|     testParse("1m", "{(::fromUnit_m 1)}") | ||||
|     testParse("1M", "{(::fromUnit_M 1)}") | ||||
|     testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}") | ||||
|     testParse("1m", "{(::$_endOfOuterBlock_$ () (::fromUnit_m 1))}") | ||||
|     testParse("1M", "{(::$_endOfOuterBlock_$ () (::fromUnit_M 1))}") | ||||
|     testParse("1m+2cm", "{(::$_endOfOuterBlock_$ () (::add (::fromUnit_m 1) (::fromUnit_cm 2)))}") | ||||
|   }) | ||||
|   describe("Module", () => { | ||||
|     testParse("x", "{:x}") | ||||
|     testParse("Math.pi", "{:Math.pi}") | ||||
|     testParse("x", "{(::$_endOfOuterBlock_$ () :x)}") | ||||
|     testParse("Math.pi", "{(::$_endOfOuterBlock_$ () :Math.pi)}") | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
|  | @ -246,19 +351,19 @@ describe("parsing new line", () => { | |||
|     ` | ||||
|  a +  | ||||
|  b`, | ||||
|     "{(::add :a :b)}", | ||||
|     "{(::$_endOfOuterBlock_$ () (::add :a :b))}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  x= | ||||
|  1`, | ||||
|     "{:x = {1}}", | ||||
|     "{:x = {1}; (::$_endOfOuterBlock_$ () ())}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  x=1 | ||||
|  y=2`, | ||||
|     "{:x = {1}; :y = {2}}", | ||||
|     "{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -266,7 +371,7 @@ describe("parsing new line", () => { | |||
|   y=2; | ||||
|   y } | ||||
|  x`, | ||||
|     "{:x = {:y = {2}; :y}; :x}", | ||||
|     "{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -274,7 +379,7 @@ describe("parsing new line", () => { | |||
|   y=2 | ||||
|   y } | ||||
|  x`, | ||||
|     "{:x = {:y = {2}; :y}; :x}", | ||||
|     "{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -283,7 +388,7 @@ describe("parsing new line", () => { | |||
|   y  | ||||
|   } | ||||
|  x`, | ||||
|     "{:x = {:y = {2}; :y}; :x}", | ||||
|     "{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -291,7 +396,7 @@ describe("parsing new line", () => { | |||
|  y=2 | ||||
|  z=3 | ||||
|  `, | ||||
|     "{:x = {1}; :y = {2}; :z = {3}}", | ||||
|     "{:x = {1}; :y = {2}; :z = {3}; (::$_endOfOuterBlock_$ () ())}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -302,7 +407,7 @@ describe("parsing new line", () => { | |||
|   x+y+z | ||||
|  } | ||||
|  `, | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}", | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; (::$_endOfOuterBlock_$ () ())}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -315,7 +420,7 @@ describe("parsing new line", () => { | |||
|  g=f+4 | ||||
|  g | ||||
|  `, | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; :g}", | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () :g)}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -337,7 +442,7 @@ describe("parsing new line", () => { | |||
|   p -> | ||||
|   q  | ||||
|  `, | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::q (::p (::h :g)))}", | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () (::q (::p (::h :g))))}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -346,7 +451,7 @@ describe("parsing new line", () => { | |||
|   c |> | ||||
|   d  | ||||
|  `, | ||||
|     "{(::d (::c (::b :a)))}", | ||||
|     "{(::$_endOfOuterBlock_$ () (::d (::c (::b :a))))}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -356,6 +461,6 @@ describe("parsing new line", () => { | |||
|   d + | ||||
|   e | ||||
|  `, | ||||
|     "{(::add (::d (::c (::b :a))) :e)}", | ||||
|     "{(::$_endOfOuterBlock_$ () (::add (::d (::c (::b :a))) :e))}", | ||||
|   ) | ||||
| }) | ||||
|  |  | |||
|  | @ -3,77 +3,83 @@ open Reducer_Peggy_TestHelpers | |||
| 
 | ||||
| describe("Peggy parse type", () => { | ||||
|   describe("type of", () => { | ||||
|     testParse("p: number", "{(::$_typeOf_$ :p #number)}") | ||||
|     testParse("p: number", "{(::$_typeOf_$ :p #number); (::$_endOfOuterBlock_$ () ())}") | ||||
|   }) | ||||
|   describe("type alias", () => { | ||||
|     testParse("type index=number", "{(::$_typeAlias_$ #index #number)}") | ||||
|     testParse( | ||||
|       "type index=number", | ||||
|       "{(::$_typeAlias_$ #index #number); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type or", () => { | ||||
|     testParse( | ||||
|       "answer: number|string", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type function", () => { | ||||
|     testParse( | ||||
|       "f: number=>number=>number", | ||||
|       "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", | ||||
|       "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("high priority contract", () => { | ||||
|     testParse( | ||||
|       "answer: number<-min<-max(100)|string", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "answer: number<-memberOf([1,3,5])", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("low priority contract", () => { | ||||
|     testParse( | ||||
|       "answer: number | string $ opaque", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string))))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type array", () => { | ||||
|     testParse("answer: [number]", "{(::$_typeOf_$ :answer (::$_typeArray_$ #number))}") | ||||
|     testParse( | ||||
|       "answer: [number]", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeArray_$ #number)); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type record", () => { | ||||
|     testParse( | ||||
|       "answer: {a: number, b: string}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type constructor", () => { | ||||
|     testParse( | ||||
|       "answer: Age(number)", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "answer: Complex(number, number)", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "answer: Person({age: number, name: string})", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string))))))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "weekend: Saturday | Sunday", | ||||
|       "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}", | ||||
|       "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ())))))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type parenthesis", () => { | ||||
|     //$ is introduced to avoid parenthesis | ||||
|     testParse( | ||||
|       "answer: (number|string)<-opaque", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string))))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("squiggle expressions in type contracts", () => { | ||||
|     testParse( | ||||
|       "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", | ||||
|       "{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}", | ||||
|       "{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2))); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -23,13 +23,7 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { | |||
|   } else { | ||||
|     let a2 = | ||||
|       rExpr | ||||
|       ->Result.flatMap(expr => | ||||
|         Expression.reduceExpression( | ||||
|           expr, | ||||
|           ReducerInterface_StdLib.internalStdLib, | ||||
|           ExpressionValue.defaultEnvironment, | ||||
|         ) | ||||
|       ) | ||||
|       ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) | ||||
|       ->Reducer_Helpers.rRemoveDefaultsInternal | ||||
|       ->ExpressionValue.toStringResultOkless | ||||
|     (a1, a2)->expect->toEqual((answer, v)) | ||||
|  |  | |||
|  | @ -0,0 +1,23 @@ | |||
| module Bindings = Reducer_Bindings | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| 
 | ||||
| open Jest | ||||
| open Reducer_Peggy_TestHelpers | ||||
| 
 | ||||
| describe("Peggy Outer Block", () => { | ||||
|   testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||
|   testToExpression("x=1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ~v="()", ()) | ||||
|   testToExpression( | ||||
|     "x=1; y=2", | ||||
|     "{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}", | ||||
|     ~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", | ||||
|     (), | ||||
|   ) | ||||
| }) | ||||
|  | @ -7,101 +7,138 @@ open Reducer_Peggy_TestHelpers | |||
| describe("Peggy to Expression", () => { | ||||
|   describe("literals operators parenthesis", () => { | ||||
|     // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement | ||||
|     testToExpression("1", "{1}", ~v="1", ()) | ||||
|     testToExpression("'hello'", "{'hello'}", ~v="'hello'", ()) | ||||
|     testToExpression("true", "{true}", ~v="true", ()) | ||||
|     testToExpression("1+2", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("add(1,2)", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("(1)", "{1}", ()) | ||||
|     testToExpression("(1+2)", "{(:add 1 2)}", ()) | ||||
|     testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||
|     testToExpression("'hello'", "{(:$_endOfOuterBlock_$ () 'hello')}", ~v="'hello'", ()) | ||||
|     testToExpression("true", "{(:$_endOfOuterBlock_$ () true)}", ~v="true", ()) | ||||
|     testToExpression("1+2", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||
|     testToExpression("add(1,2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||
|     testToExpression("(1)", "{(:$_endOfOuterBlock_$ () 1)}", ()) | ||||
|     testToExpression("(1+2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("unary", () => { | ||||
|     testToExpression("-1", "{(:unaryMinus 1)}", ~v="-1", ()) | ||||
|     testToExpression("!true", "{(:not true)}", ~v="false", ()) | ||||
|     testToExpression("1 + -1", "{(:add 1 (:unaryMinus 1))}", ~v="0", ()) | ||||
|     testToExpression("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}", ()) | ||||
|     testToExpression("-1", "{(:$_endOfOuterBlock_$ () (:unaryMinus 1))}", ~v="-1", ()) | ||||
|     testToExpression("!true", "{(:$_endOfOuterBlock_$ () (:not true))}", ~v="false", ()) | ||||
|     testToExpression("1 + -1", "{(:$_endOfOuterBlock_$ () (:add 1 (:unaryMinus 1)))}", ~v="0", ()) | ||||
|     testToExpression("-a[0]", "{(:$_endOfOuterBlock_$ () (:unaryMinus (:$_atIndex_$ :a 0)))}", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("multi-line", () => { | ||||
|     testToExpression("x=1; 2", "{(:$_let_$ :x {1}); 2}", ~v="2", ()) | ||||
|     testToExpression("x=1; y=2", "{(:$_let_$ :x {1}); (:$_let_$ :y {2})}", ~v="@{x: 1,y: 2}", ()) | ||||
|     testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ()) | ||||
|     testToExpression( | ||||
|       "x=1; y=2", | ||||
|       "{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("variables", () => { | ||||
|     testToExpression("x = 1", "{(:$_let_$ :x {1})}", ~v="@{x: 1}", ()) | ||||
|     testToExpression("x", "{:x}", ~v=":x", ()) //TODO: value should return error | ||||
|     testToExpression("x = 1; x", "{(:$_let_$ :x {1}); :x}", ~v="1", ()) | ||||
|     testToExpression("x = 1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ()) | ||||
|     testToExpression("x", "{(:$_endOfOuterBlock_$ () :x)}", ~v="Error(x is not defined)", ()) //TODO: value should return error | ||||
|     testToExpression("x = 1; x", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () :x)}", ~v="1", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("functions", () => { | ||||
|     testToExpression( | ||||
|       "identity(x) = x", | ||||
|       "{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x}))}", | ||||
|       ~v="@{identity: lambda(x=>internal code)}", | ||||
|       "{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}", | ||||
|       (), | ||||
|     ) // Function definitions become lambda assignments | ||||
|     testToExpression("identity(x)", "{(:identity :x)}", ()) // Note value returns error properly | ||||
|     testToExpression("identity(x)", "{(:$_endOfOuterBlock_$ () (:identity :x))}", ()) // Note value returns error properly | ||||
|     testToExpression( | ||||
|       "f(x) = x> 2 ? 0 : 1; f(3)", | ||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:f 3)}", | ||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:$_endOfOuterBlock_$ () (:f 3))}", | ||||
|       ~v="0", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("arrays", () => { | ||||
|     testToExpression("[]", "{(:$_constructArray_$ ())}", ~v="[]", ()) | ||||
|     testToExpression("[0, 1, 2]", "{(:$_constructArray_$ (0 1 2))}", ~v="[0,1,2]", ()) | ||||
|     testToExpression("[]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ ()))}", ~v="[]", ()) | ||||
|     testToExpression( | ||||
|       "[0, 1, 2]", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ (0 1 2)))}", | ||||
|       ~v="[0,1,2]", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "['hello', 'world']", | ||||
|       "{(:$_constructArray_$ ('hello' 'world'))}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ ('hello' 'world')))}", | ||||
|       ~v="['hello','world']", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression("([0,1,2])[1]", "{(:$_atIndex_$ (:$_constructArray_$ (0 1 2)) 1)}", ~v="1", ()) | ||||
|     testToExpression( | ||||
|       "([0,1,2])[1]", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$_atIndex_$ (:$_constructArray_$ (0 1 2)) 1))}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("records", () => { | ||||
|     testToExpression( | ||||
|       "{a: 1, b: 2}", | ||||
|       "{(:$_constructRecord_$ (('a' 1) ('b' 2)))}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (('a' 1) ('b' 2))))}", | ||||
|       ~v="{a: 1,b: 2}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "{1+0: 1, 2+0: 2}", | ||||
|       "{(:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2)))}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2))))}", | ||||
|       (), | ||||
|     ) // key can be any expression | ||||
|     testToExpression("record.property", "{(:$_atIndex_$ :record 'property')}", ()) | ||||
|     testToExpression( | ||||
|       "record.property", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "record={property: 1}; record.property", | ||||
|       "{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_atIndex_$ :record 'property')}", | ||||
|       "{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("comments", () => { | ||||
|     testToExpression("1 # This is a line comment", "{1}", ~v="1", ()) | ||||
|     testToExpression("1 // This is a line comment", "{1}", ~v="1", ()) | ||||
|     testToExpression("1 /* This is a multi line comment */", "{1}", ~v="1", ()) | ||||
|     testToExpression("/* This is a multi line comment */ 1", "{1}", ~v="1", ()) | ||||
|     testToExpression("1 # This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||
|     testToExpression("1 // This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||
|     testToExpression( | ||||
|       "1 /* This is a multi line comment */", | ||||
|       "{(:$_endOfOuterBlock_$ () 1)}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "/* This is a multi line comment */ 1", | ||||
|       "{(:$_endOfOuterBlock_$ () 1)}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("ternary operator", () => { | ||||
|     testToExpression("true ? 1 : 0", "{(:$$_ternary_$$ true 1 0)}", ~v="1", ()) | ||||
|     testToExpression("false ? 1 : 0", "{(:$$_ternary_$$ false 1 0)}", ~v="0", ()) | ||||
|     testToExpression( | ||||
|       "true ? 1 : 0", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 0))}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "false ? 1 : 0", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 0))}", | ||||
|       ~v="0", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "true ? 1 : false ? 2 : 0", | ||||
|       "{(:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0))}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0)))}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) // nested ternary | ||||
|     testToExpression( | ||||
|       "false ? 1 : false ? 2 : 0", | ||||
|       "{(:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0))}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0)))}", | ||||
|       ~v="0", | ||||
|       (), | ||||
|     ) // nested ternary | ||||
|  | @ -109,21 +146,21 @@ describe("Peggy to Expression", () => { | |||
|       testToExpression( | ||||
|         // expression binding | ||||
|         "f(a) = a > 5 ? 1 : 0; f(6)", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:f 6)}", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:$_endOfOuterBlock_$ () (:f 6))}", | ||||
|         ~v="1", | ||||
|         (), | ||||
|       ) | ||||
|       testToExpression( | ||||
|         // when true binding | ||||
|         "f(a) = a > 5 ? a : 0; f(6)", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:f 6)}", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:$_endOfOuterBlock_$ () (:f 6))}", | ||||
|         ~v="6", | ||||
|         (), | ||||
|       ) | ||||
|       testToExpression( | ||||
|         // when false binding | ||||
|         "f(a) = a < 5 ? 1 : a; f(6)", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:f 6)}", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:$_endOfOuterBlock_$ () (:f 6))}", | ||||
|         ~v="6", | ||||
|         (), | ||||
|       ) | ||||
|  | @ -131,23 +168,41 @@ describe("Peggy to Expression", () => { | |||
|   }) | ||||
| 
 | ||||
|   describe("if then else", () => { | ||||
|     testToExpression("if true then 2 else 3", "{(:$$_ternary_$$ true {2} {3})}", ()) | ||||
|     testToExpression("if true then {2} else {3}", "{(:$$_ternary_$$ true {2} {3})}", ()) | ||||
|     testToExpression( | ||||
|       "if true then 2 else 3", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "if true then {2} else {3}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "if false then {2} else if false then {4} else {5}", | ||||
|       "{(:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5}))}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5})))}", | ||||
|       (), | ||||
|     ) //nested if | ||||
|   }) | ||||
| 
 | ||||
|   describe("pipe", () => { | ||||
|     testToExpression("1 -> add(2)", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}", ~v="1", ()) // note that unary has higher priority naturally | ||||
|     testToExpression("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}", ~v="9", ()) | ||||
|     testToExpression("1 -> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||
|     testToExpression( | ||||
|       "-1 -> add(2)", | ||||
|       "{(:$_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", () => { | ||||
|     testToExpression("1 |> add(2)", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("1 |> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||
|   }) | ||||
| 
 | ||||
|   // see testParse for priorities of to and credibleIntervalToDistribution | ||||
|  | @ -157,30 +212,31 @@ describe("Peggy to Expression", () => { | |||
|     // Like lambdas they have a local scope. | ||||
|     testToExpression( | ||||
|       "y=99; x={y=1; y}", | ||||
|       "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y})}", | ||||
|       ~v="@{x: 1,y: 99}", | ||||
|       "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("lambda", () => { | ||||
|     testToExpression("{|x| x}", "{(:$$_lambda_$$ [x] {:x})}", ~v="lambda(x=>internal code)", ()) | ||||
|     testToExpression( | ||||
|       "{|x| x}", | ||||
|       "{(:$_endOfOuterBlock_$ () (:$$_lambda_$$ [x] {:x}))}", | ||||
|       ~v="lambda(x=>internal code)", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "f={|x| x}", | ||||
|       "{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})})}", | ||||
|       ~v="@{f: lambda(x=>internal code)}", | ||||
|       "{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})}); (:$_endOfOuterBlock_$ () ())}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "f(x)=x", | ||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))}", | ||||
|       ~v="@{f: lambda(x=>internal code)}", | ||||
|       "{(:$_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)}))}", | ||||
|       ~v="@{f: lambda(x=>internal code)}", | ||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)})); (:$_endOfOuterBlock_$ () ())}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|  | @ -194,6 +250,6 @@ describe("Peggy to Expression", () => { | |||
|     //  ->expect | ||||
|     //  ->toBe("") | ||||
|     // }) | ||||
|     testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ()) | ||||
|     testToExpression("Math.pi", "{(:$_endOfOuterBlock_$ () :Math.pi)}", ~v="3.141592653589793", ()) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -5,92 +5,92 @@ describe("Peggy Types to Expression", () => { | |||
|   describe("type of", () => { | ||||
|     testToExpression( | ||||
|       "p: number", | ||||
|       "{(:$_typeOf_$ :p #number)}", | ||||
|       ~v="@{_typeReferences_: {p: #number}}", | ||||
|       "{(:$_typeOf_$ :p #number); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {p: #number}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("type alias", () => { | ||||
|     testToExpression( | ||||
|       "type index=number", | ||||
|       "{(:$_typeAlias_$ #index #number)}", | ||||
|       ~v="@{_typeAliases_: {index: #number}}", | ||||
|       "{(:$_typeAlias_$ #index #number); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeAliases_: {index: #number}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("type or", () => { | ||||
|     testToExpression( | ||||
|       "answer: number|string|distribution", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution)))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("type function", () => { | ||||
|     testToExpression( | ||||
|       "f: number=>number=>number", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number))))}", | ||||
|       ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number)))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "f: number=>number", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number))))}", | ||||
|       ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number)))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("high priority contract", () => { | ||||
|     testToExpression( | ||||
|       "answer: number<-min(1)<-max(100)|string", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string)))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-memberOf([1,3,5])", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5)))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-min(1)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1))}", | ||||
|       ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-max(10)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10))}", | ||||
|       ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-min(1)<-max(10)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10))}", | ||||
|       ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-max(10)<-min(1)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1))}", | ||||
|       ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("low priority contract", () => { | ||||
|     testToExpression( | ||||
|       "answer: number | string $ opaque", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string))))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("squiggle expressions in type contracts", () => { | ||||
|     testToExpression( | ||||
|       "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", | ||||
|       "{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}", | ||||
|       ~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}", | ||||
|       "{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|  |  | |||
|  | @ -3,18 +3,23 @@ open Reducer_Peggy_TestHelpers | |||
| 
 | ||||
| describe("Peggy void", () => { | ||||
|   //literal | ||||
|   testToExpression("()", "{()}", ~v="()", ()) | ||||
|   testToExpression("()", "{(:$_endOfOuterBlock_$ () ())}", ~v="()", ()) | ||||
|   testToExpression( | ||||
|     "fn()=1", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1}))}", | ||||
|     ~v="@{fn: lambda(_=>internal code)}", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () ())}", | ||||
|     // ~v="@{fn: lambda(_=>internal code)}", | ||||
|     (), | ||||
|   ) | ||||
|   testToExpression( | ||||
|     "fn()=1; fn()", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () (:fn ()))}", | ||||
|     ~v="1", | ||||
|     (), | ||||
|   ) | ||||
|   testToExpression("fn()=1; fn()", "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:fn ())}", ~v="1", ()) | ||||
|   testToExpression( | ||||
|     "fn(a)=(); call fn(1)", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)})}", | ||||
|     ~v="@{_: (),fn: lambda(a=>internal code)}", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)}); (:$_endOfOuterBlock_$ () ())}", | ||||
|     // ~v="@{_: (),fn: lambda(a=>internal code)}", | ||||
|     (), | ||||
|   ) | ||||
| }) | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| module ExpressionT = Reducer_Expression_T | ||||
| module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
|  | @ -8,30 +9,26 @@ open Expect | |||
| let unwrapRecord = rValue => | ||||
|   rValue->Belt.Result.flatMap(value => | ||||
|     switch value { | ||||
|     | ExternalExpressionValue.EvRecord(aRecord) => Ok(aRecord) | ||||
|     | _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error | ||||
|     | InternalExpressionValue.IEvRecord(aRecord) => Ok(aRecord) | ||||
|     | _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error | ||||
|     } | ||||
|   ) | ||||
| 
 | ||||
| let expectParseToBe = (expr: string, answer: string) => | ||||
|   Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer) | ||||
| let expectParseToBe = (code: string, answer: string) => | ||||
|   Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer) | ||||
| 
 | ||||
| let expectEvalToBe = (expr: string, answer: string) => | ||||
|   Reducer.evaluate(expr) | ||||
|   ->Reducer_Helpers.rRemoveDefaultsExternal | ||||
|   ->ExternalExpressionValue.toStringResult | ||||
| let expectEvalToBe = (code: string, answer: string) => | ||||
|   Expression.BackCompatible.evaluateString(code) | ||||
|   ->Reducer_Helpers.rRemoveDefaultsInternal | ||||
|   ->InternalExpressionValue.toStringResult | ||||
|   ->expect | ||||
|   ->toBe(answer) | ||||
| 
 | ||||
| let expectEvalError = (expr: string) => | ||||
|   Reducer.evaluate(expr)->ExternalExpressionValue.toStringResult->expect->toMatch("Error\(") | ||||
| 
 | ||||
| let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) => | ||||
|   Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None) | ||||
|   ->Reducer_Helpers.rRemoveDefaultsExternal | ||||
|   ->ExternalExpressionValue.toStringResult | ||||
| let expectEvalError = (code: string) => | ||||
|   Expression.BackCompatible.evaluateString(code) | ||||
|   ->InternalExpressionValue.toStringResult | ||||
|   ->expect | ||||
|   ->toBe(answer) | ||||
|   ->toMatch("Error\(") | ||||
| 
 | ||||
| let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) | ||||
| let testDescriptionParseToBe = (desc, expr, answer) => | ||||
|  | @ -40,18 +37,12 @@ let testDescriptionParseToBe = (desc, expr, answer) => | |||
| let testEvalError = expr => test(expr, () => expectEvalError(expr)) | ||||
| let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) | ||||
| let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer)) | ||||
| let testEvalBindingsToBe = (expr, bindingsList, answer) => | ||||
|   test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) | ||||
| 
 | ||||
| module MySkip = { | ||||
|   let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) | ||||
|   let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer)) | ||||
|   let testEvalBindingsToBe = (expr, bindingsList, answer) => | ||||
|     Skip.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) | ||||
| } | ||||
| module MyOnly = { | ||||
|   let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) | ||||
|   let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer)) | ||||
|   let testEvalBindingsToBe = (expr, bindingsList, answer) => | ||||
|     Only.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer)) | ||||
| } | ||||
|  |  | |||
|  | @ -1,14 +1,14 @@ | |||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| module Bindings = Reducer_Bindings | ||||
| module BindingsReplacer = Reducer_Expression_BindingsReplacer | ||||
| module Expression = Reducer_Expression | ||||
| // module ExpressionValue = ReducerInterface.ExpressionValue | ||||
| module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||
| module ExpressionWithContext = Reducer_ExpressionWithContext | ||||
| module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||
| module Macro = Reducer_Expression_Macro | ||||
| module ProjectAccessorsT = ReducerProject_ProjectAccessors_T | ||||
| module T = Reducer_Expression_T | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| let testMacro_ = ( | ||||
|   tester, | ||||
|  | @ -21,8 +21,8 @@ let testMacro_ = ( | |||
|     expr | ||||
|     ->Macro.expandMacroCall( | ||||
|       bindings, | ||||
|       InternalExpressionValue.defaultEnvironment, | ||||
|       Expression.reduceExpression, | ||||
|       ProjectAccessorsT.identityAccessors, | ||||
|       Expression.reduceExpressionInProject, | ||||
|     ) | ||||
|     ->ExpressionWithContext.toStringResult | ||||
|     ->expect | ||||
|  | @ -41,8 +41,8 @@ let testMacroEval_ = ( | |||
|     expr | ||||
|     ->Macro.doMacroCall( | ||||
|       bindings, | ||||
|       InternalExpressionValue.defaultEnvironment, | ||||
|       Expression.reduceExpression, | ||||
|       ProjectAccessorsT.identityAccessors, | ||||
|       Expression.reduceExpressionInProject, | ||||
|     ) | ||||
|     ->InternalExpressionValue.toStringResult | ||||
|     ->expect | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ open Jest | |||
| open Expect | ||||
| 
 | ||||
| let myIevEval = (aTypeSourceCode: string) => | ||||
|   TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression) | ||||
|   TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject) | ||||
| let myIevEvalToString = (aTypeSourceCode: string) => | ||||
|   myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult | ||||
| 
 | ||||
|  | @ -19,7 +19,7 @@ let myIevTest = (test, aTypeSourceCode, answer) => | |||
|   test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer)) | ||||
| 
 | ||||
| let myTypeEval = (aTypeSourceCode: string) => | ||||
|   TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression) | ||||
|   TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject) | ||||
| let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult | ||||
| 
 | ||||
| let myTypeExpectEqual = (aTypeSourceCode, answer) => | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| module Bindings = Reducer_Bindings | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Bindings = Reducer_Bindings | ||||
| module ProjectAccessorsT = ReducerProject_ProjectAccessors_T | ||||
| module T = Reducer_Type_T | ||||
| module TypeChecker = Reducer_Type_TypeChecker | ||||
| 
 | ||||
|  | @ -13,10 +14,10 @@ let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): re | |||
|   'v, | ||||
|   ErrorValue.t, | ||||
| > => { | ||||
|   let reducerFn = Expression.reduceExpression | ||||
|   let reducerFn = Expression.reduceExpressionInProject | ||||
|   let rResult = | ||||
|     Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||
|       reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) | ||||
|     Expression.BackCompatible.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||
|       reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors) | ||||
|     ) | ||||
|   rResult->Belt.Result.flatMap(result => | ||||
|     switch result { | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ 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 | ||||
|  | @ -16,10 +17,10 @@ let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result< | |||
|   'v, | ||||
|   ErrorValue.t, | ||||
| > => { | ||||
|   let reducerFn = Expression.reduceExpression | ||||
|   let reducerFn = Expression.reduceExpressionInProject | ||||
|   let rResult = | ||||
|     Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||
|       reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) | ||||
|     Expression.BackCompatible.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||
|       reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors) | ||||
|     ) | ||||
|   rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn)) | ||||
| } | ||||
|  |  | |||
|  | @ -4,8 +4,11 @@ open Expect | |||
| module DispatchT = Reducer_Dispatch_T | ||||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module TypeCompile = Reducer_Type_Compile | ||||
| 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 | ||||
|  | @ -14,13 +17,14 @@ type errorValue = Reducer_ErrorValue.errorValue | |||
| // 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, environment): option<result<internalExpressionValue, errorValue>> | ||||
| // 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: ExpressionT.reducerFn): DispatchT.dispatchChainPiece => { | ||||
| 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) | ||||
|  | @ -45,15 +49,15 @@ let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispa | |||
| 
 | ||||
|   // Let's bridge the pure implementation to expression values | ||||
|   module Bridge = { | ||||
|     let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => { | ||||
|     let stringConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => { | ||||
|       let (a, b) = extractStringString(args) | ||||
|       Implementation.stringConcat(a, b)->IEvString->Ok | ||||
|     } | ||||
|     let arrayConcat: DispatchT.genericIEvFunction = (args, _environment) => { | ||||
|     let arrayConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => { | ||||
|       let (a, b) = extractArrayArray(args) | ||||
|       Implementation.arrayConcat(a, b)->IEvArray->Ok | ||||
|     } | ||||
|     let plot: DispatchT.genericIEvFunction = (args, _environment) => { | ||||
|     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 | ||||
|  | @ -98,12 +102,12 @@ let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispa | |||
| // Exactly the same as the one used in real life | ||||
| let _dispatch = ( | ||||
|   call: functionCall, | ||||
|   environment, | ||||
|   reducer: Reducer_Expression_T.reducerFn, | ||||
|   accessors: ProjectAccessorsT.t, | ||||
|   reducer: ProjectReducerFnT.t, | ||||
|   chain, | ||||
| ): result<internalExpressionValue, 'e> => { | ||||
|   let dispatchChainPiece = makeMyDispatchChainPiece(reducer) | ||||
|   dispatchChainPiece(call, environment)->E.O2.defaultFn(() => chain(call, environment, reducer)) | ||||
|   dispatchChainPiece(call, accessors)->E.O2.defaultFn(() => chain(call, accessors, reducer)) | ||||
| } | ||||
| 
 | ||||
| // What is important about this implementation? | ||||
|  | @ -112,12 +116,12 @@ let _dispatch = ( | |||
| // B) Complicated recursive record types are not a problem. | ||||
| 
 | ||||
| describe("Type Dispatch", () => { | ||||
|   let reducerFn = Expression.reduceExpression | ||||
|   let reducerFn = Expression.reduceExpressionInProject | ||||
|   let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn) | ||||
|   test("stringConcat", () => { | ||||
|     let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")]) | ||||
| 
 | ||||
|     let result = dispatchChainPiece(call, defaultEnvironment) | ||||
|     let result = dispatchChainPiece(call, ProjectAccessorsT.identityAccessors) | ||||
|     expect(result)->toEqual(Some(Ok(IEvString("helloworld")))) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -1,14 +0,0 @@ | |||
| open Jest | ||||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| describe("Eval with Bindings", () => { | ||||
|   testEvalBindingsToBe("x", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(1)") | ||||
|   testEvalBindingsToBe("x+1", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(2)") | ||||
|   testParseToBe("y = x+1; y", "Ok({(:$_let_$ :y {(:add :x 1)}); :y})") | ||||
|   testEvalBindingsToBe("y = x+1; y", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(2)") | ||||
|   testEvalBindingsToBe( | ||||
|     "y = x+1", | ||||
|     list{("x", ExternalExpressionValue.EvNumber(1.))}, | ||||
|     "Ok(@{x: 1,y: 2})", | ||||
|   ) | ||||
| }) | ||||
|  | @ -2,8 +2,14 @@ open Jest | |||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| describe("Parse function assignment", () => { | ||||
|   testParseToBe("f(x)=x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))})") | ||||
|   testParseToBe("f(x)=2*x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)}))})") | ||||
|   testParseToBe( | ||||
|     "f(x)=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 | ||||
| }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,33 +39,27 @@ describe("symbol not defined", () => { | |||
| }) | ||||
| 
 | ||||
| describe("call and bindings", () => { | ||||
|   testEvalToBe("f(x)=x+1", "Ok(@{f: lambda(x=>internal code)})") | ||||
|   testEvalToBe("f(x)=x+1; f(0)", "Ok(1)") | ||||
|   testEvalToBe("f(x)=x+1; f(1)", "Ok(2)") | ||||
|   testEvalToBe("f=1;y=2", "Ok(@{f: 1,y: 2})") | ||||
|   testEvalToBe("f(x)=x+1; y=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2})") | ||||
|   testEvalToBe("f=1;y=2", "Ok(())") | ||||
|   testEvalToBe("f(x)=x+1; y=f(1); y", "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)", "Ok(@{f: lambda(x=>internal code),y: 2,z: 2})") | ||||
|   testEvalToBe( | ||||
|     "f(x)=x+1; g(x)=f(x)+1", | ||||
|     "Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code)})", | ||||
|   ) | ||||
|   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)") | ||||
|   testParseToBe( | ||||
|     "f=99; g(x)=f; g(2)", | ||||
|     "Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [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(x)=x; g(x)=f(x); g(2)", "Ok(2)") | ||||
|   testEvalToBe( | ||||
|     "f(x)=x+1; g(x)=f(x)+1; y=g(2)", | ||||
|     "Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code),y: 4})", | ||||
|   ) | ||||
|   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; g(2)", "Ok(4)") | ||||
| }) | ||||
| 
 | ||||
| describe("function tricks", () => { | ||||
|   testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed | ||||
|   testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)") | ||||
|   testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok(@{g: lambda(x=>internal code),y: 2})") | ||||
|   testEvalToBe("y=2;g(x)=inspect(y)+1;y", "Ok(2)") | ||||
|   MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout? | ||||
|   MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters | ||||
|   testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))") | ||||
|  | @ -73,10 +67,7 @@ describe("function tricks", () => { | |||
| }) | ||||
| 
 | ||||
| describe("lambda in structures", () => { | ||||
|   testEvalToBe( | ||||
|     "myadd(x,y)=x+y; z=[myadd]", | ||||
|     "Ok(@{myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})", | ||||
|   ) | ||||
|   testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok(())") | ||||
|   testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))") | ||||
|   testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)") | ||||
|   testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})") | ||||
|  |  | |||
|  | @ -2,7 +2,10 @@ open Jest | |||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| describe("Parse ternary operator", () => { | ||||
|   testParseToBe("true ? 'YES' : 'NO'", "Ok({(:$$_ternary_$$ true 'YES' 'NO')})") | ||||
|   testParseToBe( | ||||
|     "true ? 'YES' : 'NO'", | ||||
|     "Ok({(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 'YES' 'NO'))})", | ||||
|   ) | ||||
| }) | ||||
| 
 | ||||
| describe("Evaluate ternary operator", () => { | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ describe("eval", () => { | |||
|     testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") | ||||
|     testEvalError("1; x=1") | ||||
|     testEvalError("1; 1") | ||||
|     testEvalToBe("x=1; x=1", "Ok(@{x: 1})") | ||||
|     testEvalToBe("x=1; x=1; x", "Ok(1)") | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -119,28 +119,40 @@ describe("eval on distribution functions", () => { | |||
| 
 | ||||
| describe("parse on distribution functions", () => { | ||||
|   describe("power", () => { | ||||
|     testParse("normal(5,2) ^ normal(5,1)", "Ok({(:pow (:normal 5 2) (:normal 5 1))})") | ||||
|     testParse("3 ^ normal(5,1)", "Ok({(:pow 3 (:normal 5 1))})") | ||||
|     testParse("normal(5,2) ^ 3", "Ok({(:pow (:normal 5 2) 3)})") | ||||
|     testParse( | ||||
|       "normal(5,2) ^ normal(5,1)", | ||||
|       "Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) (:normal 5 1)))})", | ||||
|     ) | ||||
|     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", () => { | ||||
|     testParse("10 - normal(5,1)", "Ok({(:subtract 10 (:normal 5 1))})") | ||||
|     testParse("normal(5,1) - 10", "Ok({(:subtract (:normal 5 1) 10)})") | ||||
|     testParse("10 - normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:subtract 10 (:normal 5 1)))})") | ||||
|     testParse("normal(5,1) - 10", "Ok({(:$_endOfOuterBlock_$ () (:subtract (:normal 5 1) 10))})") | ||||
|   }) | ||||
|   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((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))", | ||||
|       "Ok((:$_endOfOuterBlock_$ () (:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1)))))", | ||||
|       // TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})" | ||||
|     ) | ||||
|     testParse("normal(5,2) .* normal(5,1)", "Ok({(:dotMultiply (:normal 5 2) (:normal 5 1))})") | ||||
|     testParse("normal(5,2) ./ normal(5,1)", "Ok({(:dotDivide (: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_$ () (:dotMultiply (: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", () => { | ||||
|     testParse("5 == normal(5,2)", "Ok({(:equal 5 (:normal 5 2))})") | ||||
|     testParse("5 == normal(5,2)", "Ok({(:$_endOfOuterBlock_$ () (:equal 5 (:normal 5 2)))})") | ||||
|   }) | ||||
|   describe("pointwise adding two normals", () => { | ||||
|     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| open ReducerInterface.ExternalExpressionValue | ||||
| open ReducerInterface.InternalExpressionValue | ||||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| describe("ExpressionValue", () => { | ||||
|   test("argsToString", () => expect([EvNumber(1.), EvString("a")]->argsToString)->toBe("1,'a'")) | ||||
|   test("argsToString", () => expect([IEvNumber(1.), IEvString("a")]->argsToString)->toBe("1,'a'")) | ||||
| 
 | ||||
|   test("toStringFunctionCall", () => | ||||
|     expect(("fn", [EvNumber(1.), EvString("a")])->toStringFunctionCall)->toBe("fn(1,'a')") | ||||
|     expect(("fn", [IEvNumber(1.), IEvString("a")])->toStringFunctionCall)->toBe("fn(1,'a')") | ||||
|   ) | ||||
| }) | ||||
|  |  | |||
|  | @ -0,0 +1,36 @@ | |||
| @@warning("-44") | ||||
| module Topology = ReducerProject_Topology | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("Topology Diff", () => { | ||||
|   test("when equal 1x", () => { | ||||
|     Topology.runOrderDiff(["a"], ["a"])->expect == [] | ||||
|   }) | ||||
| 
 | ||||
|   test("when equal 3x", () => { | ||||
|     Topology.runOrderDiff(["a", "b", "c"], ["a", "b", "c"])->expect == [] | ||||
|   }) | ||||
| 
 | ||||
|   test("less dependents", () => { | ||||
|     Topology.runOrderDiff(["a", "b"], ["a", "b", "c", "d"])->expect == [] | ||||
|   }) | ||||
| 
 | ||||
|   test("more dependents", () => { | ||||
|     Topology.runOrderDiff(["a", "b", "c", "d"], ["a", "b"])->expect == ["c", "d"] | ||||
|   }) | ||||
| 
 | ||||
|   test("change midway", () => { | ||||
|     Topology.runOrderDiff(["a", "b", "bb", "c", "d"], ["a", "b", "c", "d"])->expect == [ | ||||
|         "bb", | ||||
|         "c", | ||||
|         "d", | ||||
|       ] | ||||
|   }) | ||||
| 
 | ||||
|   test("swap", () => { | ||||
|     Topology.runOrderDiff(["a", "b", "c", "d"], ["a", "c", "b", "d"])->expect == ["b", "c", "d"] | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,121 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("Parse includes", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource( | ||||
|     project, | ||||
|     "main", | ||||
|     ` | ||||
| #include 'common' | ||||
| x=1`, | ||||
|   ) | ||||
|   Project.parseIncludes(project, "main") | ||||
|   test("dependencies", () => { | ||||
|     expect(Project.getDependencies(project, "main")) == ["common"] | ||||
|   }) | ||||
|   test("dependents", () => { | ||||
|     expect(Project.getDependents(project, "main")) == [] | ||||
|   }) | ||||
|   test("getIncludes", () => { | ||||
|     let mainIncludes = Project.getIncludes(project, "main") | ||||
|     switch mainIncludes { | ||||
|     | Ok(includes) => expect(includes) == ["common"] | ||||
|     | Error(error) => fail(error->Reducer_ErrorValue.errorToString) | ||||
|     } | ||||
|   }) | ||||
|   let internalProject = project->Project.T.Private.castToInternalProject | ||||
|   test("past chain", () => { | ||||
|     expect(Project.Private.getPastChain(internalProject, "main")) == ["common"] | ||||
|   }) | ||||
|   test("import as variables", () => { | ||||
|     expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [] | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| describe("Parse includes", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource( | ||||
|     project, | ||||
|     "main", | ||||
|     ` | ||||
| #include 'common' | ||||
| #include 'myModule' as myVariable | ||||
| x=1`, | ||||
|   ) | ||||
|   Project.parseIncludes(project, "main") | ||||
| 
 | ||||
|   test("dependencies", () => { | ||||
|     expect(Project.getDependencies(project, "main")) == ["common", "myModule"] | ||||
|   }) | ||||
| 
 | ||||
|   test("dependents", () => { | ||||
|     expect(Project.getDependents(project, "main")) == [] | ||||
|   }) | ||||
| 
 | ||||
|   test("getIncludes", () => { | ||||
|     let mainIncludes = Project.getIncludes(project, "main") | ||||
|     switch mainIncludes { | ||||
|     | Ok(includes) => expect(includes) == ["common", "myModule"] | ||||
|     | Error(error) => fail(error->Reducer_ErrorValue.errorToString) | ||||
|     } | ||||
|   }) | ||||
| 
 | ||||
|   let internalProject = project->Project.T.Private.castToInternalProject | ||||
| 
 | ||||
|   test("direct past chain", () => { | ||||
|     expect(Project.Private.getPastChain(internalProject, "main")) == ["common"] | ||||
|   }) | ||||
| 
 | ||||
|   test("direct includes", () => { | ||||
|     expect(Project.Private.getDirectIncludes(internalProject, "main")) == ["common"] | ||||
|   }) | ||||
| 
 | ||||
|   test("include as variables", () => { | ||||
|     expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [ | ||||
|         ("myVariable", "myModule"), | ||||
|       ] | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| describe("Parse multiple direct includes", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource( | ||||
|     project, | ||||
|     "main", | ||||
|     ` | ||||
| #include 'common'  | ||||
| #include 'common2' | ||||
| #include 'myModule' as myVariable | ||||
| x=1`, | ||||
|   ) | ||||
|   Project.parseIncludes(project, "main") | ||||
|   test("dependencies", () => { | ||||
|     expect(Project.getDependencies(project, "main")) == ["common", "common2", "myModule"] | ||||
|   }) | ||||
|   test("dependents", () => { | ||||
|     expect(Project.getDependents(project, "main")) == [] | ||||
|   }) | ||||
|   test("getIncludes", () => { | ||||
|     let mainIncludes = Project.getIncludes(project, "main") | ||||
|     switch mainIncludes { | ||||
|     | Ok(includes) => expect(includes) == ["common", "common2", "myModule"] | ||||
|     | Error(error) => fail(error->Reducer_ErrorValue.errorToString) | ||||
|     } | ||||
|   }) | ||||
|   let internalProject = project->Project.T.Private.castToInternalProject | ||||
|   test("direct past chain", () => { | ||||
|     expect(Project.getPastChain(project, "main")) == ["common", "common2"] | ||||
|   }) | ||||
|   test("include as variables", () => { | ||||
|     expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [ | ||||
|         ("myVariable", "myModule"), | ||||
|       ] | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,195 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| // test("", () => expect(1)->toBe(1)) | ||||
| 
 | ||||
| let runFetchResult = (project, sourceId) => { | ||||
|   Project.run(project, sourceId) | ||||
|   Project.getResult(project, sourceId)->InternalExpressionValue.toStringResult | ||||
| } | ||||
| 
 | ||||
| let runFetchFlatBindings = (project, sourceId) => { | ||||
|   Project.run(project, sourceId) | ||||
|   Project.getBindings(project, sourceId) | ||||
|   ->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", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "main", "true") | ||||
|   runFetchResult(project, "main")->expect->toBe("Ok(true)") | ||||
| }) | ||||
| 
 | ||||
| test("test result false", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "main", "false") | ||||
|   runFetchResult(project, "main")->expect->toBe("Ok(false)") | ||||
| }) | ||||
| 
 | ||||
| test("test library", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "main", "x=Math.pi; x") | ||||
|   runFetchResult(project, "main")->expect->toBe("Ok(3.141592653589793)") | ||||
| }) | ||||
| 
 | ||||
| test("test bindings", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "variables", "myVariable=666") | ||||
|   runFetchFlatBindings(project, "variables")->expect->toBe("@{myVariable: 666}") | ||||
| }) | ||||
| 
 | ||||
| describe("project1", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "first", "x=1") | ||||
|   Project.setSource(project, "main", "x") | ||||
|   Project.setContinues(project, "main", ["first"]) | ||||
|   let internalProject = project->Project.T.Private.castToInternalProject | ||||
| 
 | ||||
|   test("runOrder", () => { | ||||
|     expect(Project.getRunOrder(project)) == ["first", "main"] | ||||
|   }) | ||||
|   test("dependents first", () => { | ||||
|     expect(Project.getDependents(project, "first")) == ["main"] | ||||
|   }) | ||||
|   test("dependencies first", () => { | ||||
|     expect(Project.getDependencies(project, "first")) == [] | ||||
|   }) | ||||
|   test("dependents main", () => { | ||||
|     expect(Project.getDependents(project, "main")) == [] | ||||
|   }) | ||||
|   test("dependencies main", () => { | ||||
|     expect(Project.getDependencies(project, "main")) == ["first"] | ||||
|   }) | ||||
| 
 | ||||
|   test("past chain first", () => { | ||||
|     expect(Project.Private.getPastChain(internalProject, "first")) == [] | ||||
|   }) | ||||
|   test("past chain main", () => { | ||||
|     expect(Project.Private.getPastChain(internalProject, "main")) == ["first"] | ||||
|   }) | ||||
| 
 | ||||
|   test("test result", () => { | ||||
|     runFetchResult(project, "main")->expect->toBe("Ok(1)") | ||||
|   }) | ||||
|   test("test bindings", () => { | ||||
|     runFetchFlatBindings(project, "first")->expect->toBe("@{x: 1}") | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| describe("project2", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setContinues(project, "main", ["second"]) | ||||
|   Project.setContinues(project, "second", ["first"]) | ||||
|   Project.setSource(project, "first", "x=1") | ||||
|   Project.setSource(project, "second", "y=2") | ||||
|   Project.setSource(project, "main", "y") | ||||
| 
 | ||||
|   test("runOrder", () => { | ||||
|     expect(Project.getRunOrder(project)) == ["first", "second", "main"] | ||||
|   }) | ||||
|   test("runOrderFor", () => { | ||||
|     expect(Project.getRunOrderFor(project, "first")) == ["first"] | ||||
|   }) | ||||
|   test("dependencies first", () => { | ||||
|     expect(Project.getDependencies(project, "first")) == [] | ||||
|   }) | ||||
|   test("dependents first", () => { | ||||
|     expect(Project.getDependents(project, "first")) == ["second", "main"] | ||||
|   }) | ||||
|   test("dependents main", () => { | ||||
|     expect(Project.getDependents(project, "main")) == [] | ||||
|   }) | ||||
|   test("dependencies main", () => { | ||||
|     expect(Project.getDependencies(project, "main")) == ["first", "second"] | ||||
|   }) | ||||
|   test("test result", () => { | ||||
|     runFetchResult(project, "main")->expect->toBe("Ok(2)") | ||||
|   }) | ||||
|   test("test bindings", () => { | ||||
|     runFetchFlatBindings(project, "main")->expect->toBe("@{x: 1,y: 2}") | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| describe("project with include", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setContinues(project, "main", ["second"]) | ||||
|   Project.setContinues(project, "second", ["first"]) | ||||
| 
 | ||||
|   Project.setSource( | ||||
|     project, | ||||
|     "first", | ||||
|     ` | ||||
|   #include 'common' | ||||
|   x=1`, | ||||
|   ) | ||||
|   Project.parseIncludes(project, "first") | ||||
|   Project.parseIncludes(project, "first") //The only way of setting includes | ||||
|   //Don't forget to parse includes after changing the source | ||||
| 
 | ||||
|   Project.setSource(project, "common", "common=0") | ||||
|   Project.setSource( | ||||
|     project, | ||||
|     "second", | ||||
|     ` | ||||
|   #include 'common' | ||||
|   y=2`, | ||||
|   ) | ||||
|   Project.parseIncludes(project, "second") //The only way of setting includes | ||||
| 
 | ||||
|   Project.setSource(project, "main", "y") | ||||
| 
 | ||||
|   test("runOrder", () => { | ||||
|     expect(Project.getRunOrder(project)) == ["common", "first", "second", "main"] | ||||
|   }) | ||||
| 
 | ||||
|   test("runOrderFor", () => { | ||||
|     expect(Project.getRunOrderFor(project, "first")) == ["common", "first"] | ||||
|   }) | ||||
| 
 | ||||
|   test("dependencies first", () => { | ||||
|     expect(Project.getDependencies(project, "first")) == ["common"] | ||||
|   }) | ||||
|   test("dependents first", () => { | ||||
|     expect(Project.getDependents(project, "first")) == ["second", "main"] | ||||
|   }) | ||||
|   test("dependents main", () => { | ||||
|     expect(Project.getDependents(project, "main")) == [] | ||||
|   }) | ||||
|   test("dependencies main", () => { | ||||
|     expect(Project.getDependencies(project, "main")) == ["common", "first", "second"] | ||||
|   }) | ||||
|   test("test result", () => { | ||||
|     runFetchResult(project, "main")->expect->toBe("Ok(2)") | ||||
|   }) | ||||
|   test("test bindings", () => { | ||||
|     runFetchFlatBindings(project, "main")->expect->toBe("@{common: 0,x: 1,y: 2}") | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| describe("project with independent sources", () => { | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "first", "1") | ||||
|   Project.setSource(project, "second", "2") | ||||
|   test("run order of first", () => { | ||||
|     expect(Project.getRunOrderFor(project, "first")) == ["first"] | ||||
|   }) | ||||
|   test("run order of second", () => { | ||||
|     expect(Project.getRunOrderFor(project, "second")) == ["second"] | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,109 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   describe("Single source", () => { | ||||
|     /* | ||||
| Case "Running a single source".  | ||||
| */ | ||||
|     test("run", () => { | ||||
|       /* 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.  | ||||
|        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. | ||||
|        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. | ||||
|  */ | ||||
|       let project = Project.createProject() | ||||
|       /* Every source has a name. This is used for debugging, dependencies and error messages. */ | ||||
|       Project.setSource(project, "main", "1 + 2") | ||||
|       /* Let's run "main" source. */ | ||||
|       project->Project.run("main") | ||||
|       /* Now you have a result for "main" source.  | ||||
|        Running one by one is necessary for UI to navigate among the sources and to see the results by source.  | ||||
|        And you're free to run any source you want.  | ||||
|        You will look at the results of this source and you don't want to run the others if not required. | ||||
|  */ | ||||
| 
 | ||||
|       /* However, you could also run the whole project. | ||||
|        If you have all the sources, you can always run the whole project.  | ||||
|        Dependencies and recompiling on demand will be taken care of by the project.  | ||||
|  */ | ||||
|       project->Project.runAll | ||||
| 
 | ||||
|       /* Either with run or runAll you executed the project.  | ||||
|        You can get the result of a specific source by calling getResult for that source.  | ||||
|        You can get the bindings of a specific source by calling getBindings for that source.  | ||||
|        If there is any runtime error, getResult will return the error. | ||||
| 
 | ||||
|        Note that getResult returns None if the source has not been run. | ||||
|        Getting None means you have forgotten to run the source. | ||||
|  */ | ||||
|       let result = project->Project.getResult("main") | ||||
|       let bindings = project->Project.getBindings("main")->Bindings.removeResult | ||||
| 
 | ||||
|       /* Let's display the result and bindings */ | ||||
|       ( | ||||
|         result->InternalExpressionValue.toStringResult, | ||||
|         bindings->InternalExpressionValue.toStringBindings, | ||||
|       )->expect == ("Ok(3)", "@{}") | ||||
|       /* You've got 3 with empty bindings. */ | ||||
|     }) | ||||
| 
 | ||||
|     test("run summary", () => { | ||||
|       let project = Project.createProject() | ||||
|       Project.setSource(project, "main", "1 + 2") | ||||
|       Project.runAll(project) | ||||
|       let result = Project.getResult(project, "main") | ||||
|       let bindings = Project.getBindings(project, "main")->Bindings.removeResult | ||||
|       /* Now you have external bindings and external result. */ | ||||
|       ( | ||||
|         result->InternalExpressionValue.toStringResult, | ||||
|         bindings->InternalExpressionValue.IEvBindings->InternalExpressionValue.toString, | ||||
|       )->expect == ("Ok(3)", "@{}") | ||||
|     }) | ||||
| 
 | ||||
|     test("run with an environment", () => { | ||||
|       /* Running the source code like above allows you to set a custom environment */ | ||||
|       let project = Project.createProject() | ||||
| 
 | ||||
|       /* Optional. Set your custom environment anytime before running */ | ||||
|       Project.setEnvironment(project, InternalExpressionValue.defaultEnvironment) | ||||
| 
 | ||||
|       Project.setSource(project, "main", "1 + 2") | ||||
|       Project.runAll(project) | ||||
|       let result = Project.getResult(project, "main") | ||||
|       let _bindings = Project.getBindings(project, "main") | ||||
|       result->InternalExpressionValue.toStringResult->expect == "Ok(3)" | ||||
|     }) | ||||
| 
 | ||||
|     test("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. */ | ||||
|       let (result, bindings) = Project.evaluate("1+2") | ||||
|       ( | ||||
|         result->InternalExpressionValue.toStringResult, | ||||
|         bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings, | ||||
|       )->expect == ("Ok(3)", "@{}") | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| //TODO multiple sources | ||||
| //TODO multiple sources with includes. Introduction to includes | ||||
| //TODO multiple sources with multi level includes. Cycle detection | ||||
| //TODO | ||||
| //TODO: Implement a runOrder consideration - clean results based on run order. | ||||
| //TODO: runOrder vs setSource/touchSource | ||||
| //TODO: Advanced details: (below) | ||||
| //TODO  runOrder. includes vs continues. Run order based reexecution | ||||
| //TODO: dependents and reexecution | ||||
| //TODO: dependencies and reexecution | ||||
| //TODO: cleanAllResults clean | ||||
| //TODO: cleanAll clean  | ||||
|  | @ -0,0 +1,112 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   describe("Multi source", () => { | ||||
|     /* | ||||
|      Case "Running multiple sources" */ | ||||
|     test("Chaining", () => { | ||||
|       let project = Project.createProject() | ||||
|       /* This time let's add 3 sources and chain them together */ | ||||
|       Project.setSource(project, "source1", "x=1") | ||||
| 
 | ||||
|       Project.setSource(project, "source2", "y=2") | ||||
|       /* To run, source2 depends on source1 */ | ||||
|       Project.setContinues(project, "source2", ["source1"]) | ||||
| 
 | ||||
|       Project.setSource(project, "source3", "z=3") | ||||
|       /* To run, source3 depends on source2 */ | ||||
|       Project.setContinues(project, "source3", ["source2"]) | ||||
| 
 | ||||
|       /* Now we can run the project */ | ||||
|       Project.runAll(project) | ||||
| 
 | ||||
|       /* And let's check the result and bindings of source3 */ | ||||
|       let result3 = Project.getResult(project, "source3") | ||||
|       let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult | ||||
| 
 | ||||
|       ( | ||||
|         result3->InternalExpressionValue.toStringResult, | ||||
|         bindings3->InternalExpressionValue.toStringBindings, | ||||
|       )->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}") | ||||
|     }) | ||||
| 
 | ||||
|     test("Depending", () => { | ||||
|       /* Instead of chaining the sources, we could have a dependency tree */ | ||||
|       /* The point here is that any source can depend on multiple sources */ | ||||
|       let project = Project.createProject() | ||||
| 
 | ||||
|       /* This time source1 and source2 are not depending on anything */ | ||||
|       Project.setSource(project, "source1", "x=1") | ||||
|       Project.setSource(project, "source2", "y=2") | ||||
| 
 | ||||
|       Project.setSource(project, "source3", "z=3") | ||||
|       /* To run, source3 depends on source1 and source3 together */ | ||||
|       Project.setContinues(project, "source3", ["source1", "source2"]) | ||||
| 
 | ||||
|       /* Now we can run the project */ | ||||
|       Project.runAll(project) | ||||
| 
 | ||||
|       /* And let's check the result and bindings of source3 */ | ||||
|       let result3 = Project.getResult(project, "source3") | ||||
|       let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult | ||||
| 
 | ||||
|       ( | ||||
|         result3->InternalExpressionValue.toStringResult, | ||||
|         bindings3->InternalExpressionValue.toStringBindings, | ||||
|       )->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}") | ||||
|     }) | ||||
| 
 | ||||
|     test("Intro to including", () => { | ||||
|       /* Though it would not be practical for a storybook,  | ||||
|         let's write the same project above with includes. | ||||
|         You will see that parsing includes is setting the dependencies the same way as before. */ | ||||
|       let project = Project.createProject() | ||||
| 
 | ||||
|       /* This time source1 and source2 are not depending on anything */ | ||||
|       Project.setSource(project, "source1", "x=1") | ||||
|       Project.setSource(project, "source2", "y=2") | ||||
| 
 | ||||
|       Project.setSource( | ||||
|         project, | ||||
|         "source3", | ||||
|         ` | ||||
|       #include "source1" | ||||
|       #include "source2" | ||||
|       z=3`, | ||||
|       ) | ||||
|       /* We need to parse the includes to set the dependencies */ | ||||
|       Project.parseIncludes(project, "source3") | ||||
| 
 | ||||
|       /* Now we can run the project */ | ||||
|       Project.runAll(project) | ||||
| 
 | ||||
|       /* 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  | ||||
|       Behind the scenes parseIncludes is setting the dependencies */ | ||||
|       let result3 = Project.getResult(project, "source3") | ||||
|       let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult | ||||
| 
 | ||||
|       ( | ||||
|         result3->InternalExpressionValue.toStringResult, | ||||
|         bindings3->InternalExpressionValue.toStringBindings, | ||||
|       )->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}") | ||||
|       /* | ||||
|       Doing it like this is too verbose for a storybook  | ||||
|       But I hope you have seen the relation of setContinues and parseIncludes */ | ||||
|       /* | ||||
|          Dealing with includes needs more.  | ||||
|          - There are parse errors | ||||
|          - There are cyclic includes | ||||
|          - And the depended source1 and source2 is not already there in the project | ||||
|          - If you knew the includes before hand there would not be point of the include directive. | ||||
|          More on those on the next section. */ | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,179 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   /* Case: Includes | ||||
| In the previous tutorial we have set the similarity between setContinues and parseIncludes. | ||||
| Here we will finally proceed to a real life scenario. */ | ||||
| 
 | ||||
|   describe("parseIncludes", () => { | ||||
|     /* 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 */ | ||||
|     let project = Project.createProject() | ||||
|     Project.setSource( | ||||
|       project, | ||||
|       "main", | ||||
|       ` | ||||
|     #include "common" | ||||
|     x=1 | ||||
|     `, | ||||
|     ) | ||||
|     /* We need to parse includes after changing the source */ | ||||
|     Project.parseIncludes(project, "main") | ||||
|     test("getDependencies", () => { | ||||
|       /* Parse includes has set the dependencies */ | ||||
|       Project.getDependencies(project, "main")->expect == ["common"] | ||||
|       /* 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 */ | ||||
|       /* Therefore looking at dependencies is not the right way to load includes */ | ||||
|       /* getDependencies does not distinguish between setContinues or parseIncludes */ | ||||
|     }) | ||||
|     test("getIncludes", () => { | ||||
|       /* Parse includes has set the includes */ | ||||
|       switch Project.getIncludes(project, "main") { | ||||
|       | Ok(includes) => includes->expect == ["common"] | ||||
|       | Error(err) => err->Reducer_ErrorValue.errorToString->fail | ||||
|       } | ||||
|       /* If the includes cannot be parsed then you get a syntax error. | ||||
|       Otherwise you get the includes. | ||||
|       If there is no syntax error then you can load that file and use setSource to add it to the project. | ||||
|       And so on recursively... */ | ||||
|     }) | ||||
|     test("getDependents", () => { | ||||
|       /* For any reason, you are able to query what other sources | ||||
|         include or depend on the current source. | ||||
|         But you don't need to use this to execute the projects. | ||||
|         It is provided for completeness of information. */ | ||||
|       Project.getDependents(project, "main")->expect == [] | ||||
|       /* Nothing is depending on or including main */ | ||||
|     }) | ||||
| 
 | ||||
|     describe("Real Like", () => { | ||||
|       /* Now let's look at recursive and possibly cyclic includes */ | ||||
|       /* There is no function provided to load the include files. | ||||
|     Because we have no idea if will it be an ordinary function or will it use promises. | ||||
|     Therefore one has to write a function to load sources recursively and and setSources | ||||
|     while checking for dependencies */ | ||||
| 
 | ||||
|       /* Let's make a dummy loader */ | ||||
|       let loadSource = (sourceName: string) => | ||||
|         switch sourceName { | ||||
|         | "source1" => "x=1" | ||||
|         | "source2" => ` | ||||
|             #include "source1" | ||||
|             y=2` | ||||
|         | "source3" => ` | ||||
|             #include "source2" | ||||
|             z=3` | ||||
|         | _ => `source ${sourceName} not found`->Js.Exn.raiseError | ||||
|         } | ||||
| 
 | ||||
|       /* let's recursively load the sources */ | ||||
|       let rec loadIncludesRecursively = (project, sourceName, visited) => { | ||||
|         if Js.Array2.includes(visited, sourceName) { | ||||
|           /* Oh we have already visited this source. There is an include cycle */ | ||||
|           "Cyclic include ${sourceName}"->Js.Exn.raiseError | ||||
|         } else { | ||||
|           let newVisited = Js.Array2.copy(visited) | ||||
|           let _ = Js.Array2.push(newVisited, sourceName) | ||||
|           /* Let's parse the includes and dive into them */ | ||||
|           Project.parseIncludes(project, sourceName) | ||||
|           let rIncludes = Project.getIncludes(project, sourceName) | ||||
|           switch rIncludes { | ||||
|           /* Maybe there is an include syntax error */ | ||||
|           | Error(err) => err->Reducer_ErrorValue.errorToString->Js.Exn.raiseError | ||||
| 
 | ||||
|           | Ok(includes) => | ||||
|             Belt.Array.forEach(includes, newIncludeName => { | ||||
|               /* We have got one of the new includes. | ||||
|                Let's load it and add it to the project */ | ||||
|               let newSource = loadSource(newIncludeName) | ||||
|               Project.setSource(project, newIncludeName, newSource) | ||||
|               /* The new source is loaded and added to the project. */ | ||||
|               /* Of course the new source might have includes too. */ | ||||
|               /* Let's recursively load them */ | ||||
|               loadIncludesRecursively(project, newIncludeName, newVisited) | ||||
|             }) | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       /* As we have a fake source loader and a recursive include handler, | ||||
|        We can not set up a real project */ | ||||
| 
 | ||||
|       /* * Here starts our real life project! * */ | ||||
| 
 | ||||
|       let project = Project.createProject() | ||||
| 
 | ||||
|       /* main includes source3 which includes source2 which includes source1 */ | ||||
|       Project.setSource( | ||||
|         project, | ||||
|         "main", | ||||
|         ` | ||||
|         #include "source3" | ||||
|         x+y+z | ||||
|         `, | ||||
|       ) | ||||
|       /* Setting source requires parsing and loading the includes recursively */ | ||||
|       loadIncludesRecursively(project, "main", []) //No visited yet | ||||
| 
 | ||||
|       /* 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 */ | ||||
|       Project.setSource( | ||||
|         project, | ||||
|         "doubleX", | ||||
|         ` | ||||
|         #include "source1" | ||||
|         doubleX = x * 2 | ||||
|         `, | ||||
|       ) | ||||
|       loadIncludesRecursively(project, "doubleX", []) | ||||
|       /* 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. | ||||
|        So we link it to the project as a dependency */ | ||||
|       Project.setContinues(project, "main", ["doubleX"]) | ||||
| 
 | ||||
|       /* Let's run the project */ | ||||
|       Project.runAll(project) | ||||
|       let result = Project.getResult(project, "main") | ||||
|       let bindings = Project.getBindings(project, "main") | ||||
|       /* And see the result and bindings.. */ | ||||
|       test("recursive includes", () => { | ||||
|         ( | ||||
|           result->InternalExpressionValue.toStringResult, | ||||
|           bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings, | ||||
|         )->expect == ("Ok(6)", "@{doubleX: 2,x: 1,y: 2,z: 3}") | ||||
|         /* Everything as expected */ | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
| 
 | ||||
|   describe("Includes myFile as myVariable", () => { | ||||
|     /* Instead of including into global space you can also put a module into a record variable */ | ||||
|     let project = Project.createProject() | ||||
|     Project.setSource( | ||||
|       project, | ||||
|       "main", | ||||
|       ` | ||||
|     #include "common" as common | ||||
|     x=1 | ||||
|     `, | ||||
|     ) | ||||
|     Project.parseIncludes(project, "main") | ||||
|     test("getDependencies", () => { | ||||
|       Project.getDependencies(project, "main")->expect == ["common"] | ||||
|     }) | ||||
|     test("getIncludes", () => { | ||||
|       switch Project.getIncludes(project, "main") { | ||||
|       | Ok(includes) => includes->expect == ["common"] | ||||
|       | Error(err) => err->Reducer_ErrorValue.errorToString->fail | ||||
|       } | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,39 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   /* Let's build a project that depends on values from the UI */ | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "main", "x+y+z") | ||||
|   /* x, y and z is not defined in the project but they has to come from the user */ | ||||
|   test("Injecting user values", () => { | ||||
|     /* User has input the values */ | ||||
|     let x = 1 | ||||
|     let y = 2 | ||||
|     let z = 3 | ||||
|     /* Then we construct a source code to define those values */ | ||||
|     let userCode = ` | ||||
|       x = ${x->Js.Int.toString} | ||||
|       y = ${y->Js.Int.toString} | ||||
|       z = ${z->Js.Int.toString} | ||||
|     ` | ||||
|     /* We inject the user code into the project */ | ||||
|     Project.setSource(project, "userCode", userCode) | ||||
|     /* "main" is depending on the user code */ | ||||
|     Project.setContinues(project, "main", ["userCode"]) | ||||
|     /* We can now run the project */ | ||||
|     Project.runAll(project) | ||||
|     let result = Project.getResult(project, "main") | ||||
|     result->InternalExpressionValue.toStringResult->expect == "Ok(6)" | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| /* Note that this is not final version of the project */ | ||||
| /* In the future, for safety, we will provide a way to inject values instead of a source code */ | ||||
| /* But time is limited for now... */ | ||||
|  | @ -0,0 +1,39 @@ | |||
| @@warning("-44") | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   /* Let's build a project to provide a function. */ | ||||
|   /* But we will call that function on an array of user input. */ | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "library", "double(x) = x * 2") | ||||
|   /* userCode is not here yet but its dependency is fixed. So we can set it once and for all */ | ||||
|   Project.setContinues(project, "userCode", ["library"]) | ||||
| 
 | ||||
|   let userValues = [1, 2, 3, 4, 5] | ||||
| 
 | ||||
|   let userResults = Belt.Array.map(userValues, aUserValue => { | ||||
|     let userCode = `double(${aUserValue->Js.Int.toString})` | ||||
|     /* Put the constructed source in the project */ | ||||
|     /* We have already set that it depends on "library" */ | ||||
|     Project.setSource(project, "userCode", userCode) | ||||
|     /* Run the project */ | ||||
|     Project.runAll(project) | ||||
|     /* Get the result */ | ||||
|     Project.getResult(project, "userCode") | ||||
|     /* I have to remind you that the "library" is run only once and for all. | ||||
|      The library is not run for each user value. */ | ||||
|   }) | ||||
| 
 | ||||
|   test("userResults", () => { | ||||
|     let userResultsAsString = Belt.Array.map(userResults, aResult => | ||||
|       aResult->InternalExpressionValue.toStringResult | ||||
|     ) | ||||
|     userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"] | ||||
|   }) | ||||
| }) | ||||
|  | @ -2,8 +2,12 @@ open Jest | |||
| open Expect | ||||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| let expectEvalToBeOk = (expr: string) => | ||||
|   Reducer.evaluate(expr)->Reducer_Helpers.rRemoveDefaultsExternal->E.R.isOk->expect->toBe(true) | ||||
| let expectEvalToBeOk = (code: string) => | ||||
|   Reducer_Expression.BackCompatible.evaluateString(code) | ||||
|   ->Reducer_Helpers.rRemoveDefaultsInternal | ||||
|   ->E.R.isOk | ||||
|   ->expect | ||||
|   ->toBe(true) | ||||
| 
 | ||||
| let registry = FunctionRegistry_Library.registry | ||||
| let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry)) | ||||
|  | @ -63,9 +67,15 @@ describe("FunctionRegistry Library", () => { | |||
|     testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)") | ||||
|     testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)") | ||||
|     testEvalToBe("SampleSet.fromFn({|| sample(normal(5,2))})", "Ok(Sample Set Distribution)") | ||||
|     testEvalToBe("SampleSet.min(SampleSet.fromDist(normal(50,2)), 2)", "Ok(Sample Set Distribution)") | ||||
|     testEvalToBe( | ||||
|       "SampleSet.min(SampleSet.fromDist(normal(50,2)), 2)", | ||||
|       "Ok(Sample Set Distribution)", | ||||
|     ) | ||||
|     testEvalToBe("mean(SampleSet.min(SampleSet.fromDist(normal(50,2)), 2))", "Ok(2)") | ||||
|     testEvalToBe("SampleSet.max(SampleSet.fromDist(normal(50,2)), 10)", "Ok(Sample Set Distribution)") | ||||
|     testEvalToBe( | ||||
|       "SampleSet.max(SampleSet.fromDist(normal(50,2)), 10)", | ||||
|       "Ok(Sample Set Distribution)", | ||||
|     ) | ||||
|     testEvalToBe( | ||||
|       "addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))", | ||||
|       "Ok([2,3,4,5,6,7])", | ||||
|  | @ -91,8 +101,8 @@ describe("FunctionRegistry Library", () => { | |||
|       ((fn, example)) => { | ||||
|         let responseType = | ||||
|           example | ||||
|           ->Reducer.evaluate | ||||
|           ->E.R2.fmap(ReducerInterface_InternalExpressionValue.externalValueToValueType) | ||||
|           ->Reducer_Expression.BackCompatible.evaluateString | ||||
|           ->E.R2.fmap(ReducerInterface_InternalExpressionValue.valueToValueType) | ||||
|         let expectedOutputType = fn.output |> E.O.toExn("") | ||||
|         expect(responseType)->toEqual(Ok(expectedOutputType)) | ||||
|       }, | ||||
|  |  | |||
|  | @ -1,10 +1,5 @@ | |||
| import { | ||||
|   Distribution, | ||||
|   resultMap, | ||||
|   defaultBindings, | ||||
|   mergeBindings, | ||||
| } from "../../src/js/index"; | ||||
| import { testRun, testRunPartial } from "./TestHelpers"; | ||||
| import { run, SqProject, SqValue, SqValueTag } from "../../src/js"; | ||||
| import { testRun } from "./TestHelpers"; | ||||
| 
 | ||||
| function Ok<b>(x: b) { | ||||
|   return { tag: "Ok", value: x }; | ||||
|  | @ -12,97 +7,57 @@ function Ok<b>(x: b) { | |||
| 
 | ||||
| describe("Simple calculations and results", () => { | ||||
|   test("mean(normal(5,2))", () => { | ||||
|     expect(testRun("mean(normal(5,2))")).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 5, | ||||
|     }); | ||||
|     const result = testRun("mean(normal(5,2))"); // FIXME
 | ||||
|     expect(result.toString()).toEqual("5"); | ||||
|   }); | ||||
|   test("10+10", () => { | ||||
|     let foo = testRun("10 + 10"); | ||||
|     expect(foo).toEqual({ tag: "number", value: 20 }); | ||||
|     let result = testRun("10 + 10"); | ||||
|     expect(result.toString()).toEqual("20"); | ||||
|   }); | ||||
| }); | ||||
| describe("Log function", () => { | ||||
|   test("log(1) = 0", () => { | ||||
|     let foo = testRun("log(1)"); | ||||
|     expect(foo).toEqual({ tag: "number", value: 0 }); | ||||
|     expect(foo.toString()).toEqual("0"); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("Array", () => { | ||||
|   test("nested Array", () => { | ||||
|     expect(testRun("[[1]]")).toEqual({ | ||||
|       tag: "array", | ||||
|       value: [ | ||||
|         { | ||||
|           tag: "array", | ||||
|           value: [ | ||||
|             { | ||||
|               tag: "number", | ||||
|               value: 1, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|     expect(testRun("[[ 1 ]]").toString()).toEqual("[[1]]"); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("Record", () => { | ||||
|   test("Return record", () => { | ||||
|     expect(testRun("{a: 1}")).toEqual({ | ||||
|       tag: "record", | ||||
|       value: { | ||||
|         a: { | ||||
|           tag: "number", | ||||
|           value: 1, | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
|     expect(testRun("{a:1}").toString()).toEqual("{a: 1}"); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("Partials", () => { | ||||
|   test("Can pass variables between partials and cells", () => { | ||||
|     let bindings = testRunPartial(`x = 5`); | ||||
|     let bindings2 = testRunPartial(`y = x + 2`, bindings); | ||||
|     expect(testRun(`y + 3`, bindings2)).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 10, | ||||
|     }); | ||||
| describe("Continues", () => { | ||||
|   test("Bindings from continues are accessible", () => { | ||||
|     const project = SqProject.create(); | ||||
|     project.setSource("p1", "x = 5"); | ||||
|     project.setSource("p2", "y = x + 2"); | ||||
|     project.setSource("main", "y + 3"); | ||||
|     project.setContinues("main", ["p2"]); | ||||
|     project.setContinues("p2", ["p1"]); | ||||
|     project.run("main"); | ||||
|     const result = project.getResult("main"); | ||||
|     expect(result.tag).toEqual("Ok"); | ||||
|     expect(result.value.toString()).toEqual("10"); | ||||
|   }); | ||||
|   test("Can merge bindings from three partials", () => { | ||||
|     let bindings1 = testRunPartial(`x = 1`); | ||||
|     let bindings2 = testRunPartial(`y = 2`); | ||||
|     let bindings3 = testRunPartial(`z = 3`); | ||||
|     expect( | ||||
|       testRun(`x + y + z`, mergeBindings([bindings1, bindings2, bindings3])) | ||||
|     ).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 6, | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("JS Imports", () => { | ||||
|   test("Can pass parameters into partials and cells", () => { | ||||
|     let bindings = testRunPartial(`y = $x + 2`, defaultBindings, { x: 1 }); | ||||
|     let bindings2 = testRunPartial(`z = y + $a`, bindings, { a: 3 }); | ||||
|     expect(testRun(`z`, bindings2)).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 6, | ||||
|     }); | ||||
|   }); | ||||
|   test("Complicated deep parameters", () => { | ||||
|     expect( | ||||
|       testRun(`$x.y[0][0].w + $x.z + $u.v`, defaultBindings, { | ||||
|         x: { y: [[{ w: 1 }]], z: 2 }, | ||||
|         u: { v: 3 }, | ||||
|       }) | ||||
|     ).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 6, | ||||
|     }); | ||||
|     const project = SqProject.create(); | ||||
|     project.setSource("p1", "x = 1"); | ||||
|     project.setSource("p2", "y = 2"); | ||||
|     project.setSource("p3", "z = 3"); | ||||
|     project.setSource("main", "x + y + z"); | ||||
|     project.setContinues("main", ["p1", "p2", "p3"]); | ||||
|     project.run("main"); | ||||
|     const result = project.getResult("main"); | ||||
|     expect(result.tag).toEqual("Ok"); | ||||
|     expect(result.value.toString()).toEqual("6"); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
|  | @ -112,50 +67,62 @@ describe("Distribution", () => { | |||
|   let env = { sampleCount: 8, xyPointLength: 100 }; | ||||
|   let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30]; | ||||
|   let dist1SampleCount = dist1Samples.length; | ||||
|   let dist = new Distribution( | ||||
|     { tag: "SampleSet", value: [3, 4, 5, 6, 6, 7, 10, 15, 30] }, | ||||
|     env | ||||
|   ); | ||||
|   let dist2 = new Distribution( | ||||
|     { tag: "SampleSet", value: [20, 22, 24, 29, 30, 35, 38, 44, 52] }, | ||||
|     env | ||||
| 
 | ||||
|   const buildDist = (samples: number[]) => { | ||||
|     const src = `SampleSet.fromList([${samples.join(",")}])`; | ||||
|     const { result } = run(src, { | ||||
|       environment: env, | ||||
|     }); | ||||
|     if (result.tag !== "Ok") { | ||||
|       throw new Error( | ||||
|         `Failed to build SampleSet: from ${src}: ${result.value}` | ||||
|       ); | ||||
|     } | ||||
|     const dist = result.value; | ||||
|     if (dist.tag !== SqValueTag.Distribution) { | ||||
|       throw new Error("Expected Distribution"); | ||||
|     } | ||||
|     return dist.value; | ||||
|   }; | ||||
| 
 | ||||
|   const dist = buildDist(dist1Samples); | ||||
|   const dist2 = buildDist([20, 22, 24, 29, 30, 35, 38, 44, 52]); | ||||
| 
 | ||||
|   test("mean", () => { | ||||
|     expect(dist.mean().value).toBeCloseTo(9.5555555); | ||||
|     expect(dist.mean(env).value).toBeCloseTo(9.5555555); | ||||
|   }); | ||||
|   test("pdf", () => { | ||||
|     expect(dist.pdf(5.0).value).toBeCloseTo(0.10499097598222966, 1); | ||||
|     expect(dist.pdf(env, 5.0).value).toBeCloseTo(0.10499097598222966, 1); | ||||
|   }); | ||||
|   test("cdf", () => { | ||||
|     expect(dist.cdf(5.0).value).toBeCloseTo( | ||||
|     expect(dist.cdf(env, 5.0).value).toBeCloseTo( | ||||
|       dist1Samples.filter((x) => x <= 5).length / dist1SampleCount, | ||||
|       1 | ||||
|     ); | ||||
|   }); | ||||
|   test("inv", () => { | ||||
|     expect(dist.inv(0.5).value).toBeCloseTo(6); | ||||
|   }); | ||||
|   test("toPointSet", () => { | ||||
|     expect( | ||||
|       resultMap(dist.toPointSet(), (r: Distribution) => r.toString()) | ||||
|     ).toEqual(Ok("Point Set Distribution")); | ||||
|   }); | ||||
|   test("toSparkline", () => { | ||||
|     expect(dist.toSparkline(20).value).toEqual("▁▁▃▇█▇▄▂▂▂▁▁▁▁▁▂▂▁▁▁"); | ||||
|   }); | ||||
|   test("algebraicAdd", () => { | ||||
|     expect( | ||||
|       resultMap(dist.algebraicAdd(dist2), (r: Distribution) => | ||||
|         r.toSparkline(20) | ||||
|       ).value | ||||
|     ).toEqual(Ok("▁▁▂▄▆████▇▆▄▄▃▃▃▂▁▁▁")); | ||||
|   }); | ||||
|   test("pointwiseAdd", () => { | ||||
|     expect( | ||||
|       resultMap(dist.pointwiseAdd(dist2), (r: Distribution) => | ||||
|         r.toSparkline(20) | ||||
|       ).value | ||||
|     ).toEqual(Ok("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁")); | ||||
|     expect(dist.inv(env, 0.5).value).toBeCloseTo(6); | ||||
|   }); | ||||
|   // test("toPointSet", () => {
 | ||||
|   //   expect(
 | ||||
|   //     resultMap(dist.toPointSet(), (r: Distribution) => r.toString())
 | ||||
|   //   ).toEqual(Ok("Point Set Distribution"));
 | ||||
|   // });
 | ||||
|   // test("toSparkline", () => {
 | ||||
|   //   expect(dist.toSparkline(20).value).toEqual("▁▁▃▇█▇▄▂▂▂▁▁▁▁▁▂▂▁▁▁");
 | ||||
|   // });
 | ||||
|   // test("algebraicAdd", () => {
 | ||||
|   //   expect(
 | ||||
|   //     resultMap(dist.algebraicAdd(dist2), (r: Distribution) =>
 | ||||
|   //       r.toSparkline(20)
 | ||||
|   //     ).value
 | ||||
|   //   ).toEqual(Ok("▁▁▂▄▆████▇▆▄▄▃▃▃▂▁▁▁"));
 | ||||
|   // });
 | ||||
|   // test("pointwiseAdd", () => {
 | ||||
|   //   expect(
 | ||||
|   //     resultMap(dist.pointwiseAdd(dist2), (r: Distribution) =>
 | ||||
|   //       r.toSparkline(20)
 | ||||
|   //     ).value
 | ||||
|   //   ).toEqual(Ok("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁"));
 | ||||
|   // });
 | ||||
| }); | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| // import { errorValueToString } from "../../src/js/index";
 | ||||
| import * as fc from "fast-check"; | ||||
| import { testRun } from "./TestHelpers"; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,3 @@ | |||
| import { | ||||
|   run, | ||||
|   squiggleExpression, | ||||
|   errorValue, | ||||
|   result, | ||||
| } from "../../src/js/index"; | ||||
| import { testRun } from "./TestHelpers"; | ||||
| import * as fc from "fast-check"; | ||||
| 
 | ||||
|  | @ -42,9 +36,9 @@ describe("Squiggle's parser is whitespace insensitive", () => { | |||
|         whitespaceGen(), | ||||
|         whitespaceGen(), | ||||
|         (a, b, c, d, e, f, g, h) => { | ||||
|           expect(testRun(squiggleString(a, b, c, d, e, f, g, h))).toEqual( | ||||
|             squiggleOutput | ||||
|           ); | ||||
|           expect( | ||||
|             testRun(squiggleString(a, b, c, d, e, f, g, h)) | ||||
|           ).toEqualSqValue(squiggleOutput); | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| // import { errorValueToString } from "../../src/js/index";
 | ||||
| import { testRun, expectErrorToBeBounded } from "./TestHelpers"; | ||||
| import { testRun, expectErrorToBeBounded, SqValueTag } from "./TestHelpers"; | ||||
| import * as fc from "fast-check"; | ||||
| 
 | ||||
| describe("Mean of mixture is weighted average of means", () => { | ||||
|  | @ -20,7 +19,7 @@ describe("Mean of mixture is weighted average of means", () => { | |||
|           let lognormalWeight = y / weightDenom; | ||||
|           let betaMean = 1 / (1 + b / a); | ||||
|           let lognormalMean = m + s ** 2 / 2; | ||||
|           if (res.tag == "number") { | ||||
|           if (res.tag === SqValueTag.Number) { | ||||
|             expectErrorToBeBounded( | ||||
|               res.value, | ||||
|               betaWeight * betaMean + lognormalWeight * lognormalMean, | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| import { Distribution } from "../../src/js/index"; | ||||
| import { expectErrorToBeBounded, failDefault, testRun } from "./TestHelpers"; | ||||
| import { expectErrorToBeBounded, testRun, SqValueTag } from "./TestHelpers"; | ||||
| import * as fc from "fast-check"; | ||||
| 
 | ||||
| // Beware: float64Array makes it appear in an infinite loop.
 | ||||
| let arrayGen = () => | ||||
|   fc | ||||
|     .float32Array({ | ||||
|     .float64Array({ | ||||
|       minLength: 10, | ||||
|       max: 999999999999999, | ||||
|       min: -999999999999999, | ||||
|       maxLength: 10000, | ||||
|       noDefaultInfinity: true, | ||||
|       noNaN: true, | ||||
|  | @ -14,36 +15,41 @@ let arrayGen = () => | |||
|     .filter( | ||||
|       (xs_) => Math.min(...Array.from(xs_)) != Math.max(...Array.from(xs_)) | ||||
|     ); | ||||
| describe("cumulative density function", () => { | ||||
|   let n = 10000; | ||||
| 
 | ||||
| let makeSampleSet = (samples: number[]) => { | ||||
|   let sampleList = samples.map((x) => x.toFixed(20)).join(","); | ||||
|   let result = testRun(`SampleSet.fromList([${sampleList}])`); | ||||
|   if (result.tag === SqValueTag.Distribution) { | ||||
|     return result.value; | ||||
|   } else { | ||||
|     fail("Expected to be distribution"); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const env = { sampleCount: 10000, xyPointLength: 100 }; | ||||
| 
 | ||||
| describe("cumulative density function", () => { | ||||
|   // We should fix this.
 | ||||
|   test.skip("'s codomain is bounded above", () => { | ||||
|     fc.assert( | ||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||
|         let xs = Array.from(xs_); | ||||
|         // Should compute with squiggle strings once interpreter has `sample`
 | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let cdfValue = dist.cdf(x).value; | ||||
|         let result = makeSampleSet(xs); | ||||
|         let cdfValue = result.cdf(env, x).value; | ||||
|         let epsilon = 5e-7; | ||||
|         expect(cdfValue).toBeLessThanOrEqual(1 + epsilon); | ||||
|       }) | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   test("'s codomain is bounded below", () => { | ||||
|   test.skip("'s codomain is bounded below", () => { | ||||
|     fc.assert( | ||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||
|         let xs = Array.from(xs_); | ||||
|         // Should compute with squiggle strings once interpreter has `sample`
 | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let cdfValue = dist.cdf(x).value; | ||||
|         let result = makeSampleSet(xs); | ||||
|         let cdfValue = result.cdf(env, x).value; | ||||
|         expect(cdfValue).toBeGreaterThanOrEqual(0); | ||||
|       }) | ||||
|     ); | ||||
|  | @ -57,11 +63,8 @@ describe("cumulative density function", () => { | |||
|         let xs = Array.from(xs_); | ||||
|         let max = Math.max(...xs); | ||||
|         // Should compute with squiggle strings once interpreter has `sample`
 | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let cdfValue = dist.cdf(max).value; | ||||
|         let result = makeSampleSet(xs); | ||||
|         let cdfValue = result.cdf(env, max).value; | ||||
|         expect(cdfValue).toBeCloseTo(1.0, 2); | ||||
|       }) | ||||
|     ); | ||||
|  | @ -74,11 +77,8 @@ describe("cumulative density function", () => { | |||
|         let xs = Array.from(xs_); | ||||
|         let min = Math.min(...xs); | ||||
|         // Should compute with squiggle strings once interpreter has `sample`
 | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let cdfValue = dist.cdf(min).value; | ||||
|         let result = makeSampleSet(xs); | ||||
|         let cdfValue = result.cdf(env, min).value; | ||||
|         let max = Math.max(...xs); | ||||
|         let epsilon = 5e-3; | ||||
|         if (max - min < epsilon) { | ||||
|  | @ -95,11 +95,8 @@ describe("cumulative density function", () => { | |||
|     fc.assert( | ||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||
|         let xs = Array.from(xs_); | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let cdfValue = dist.cdf(x).value; | ||||
|         let dist = makeSampleSet(xs); | ||||
|         let cdfValue = dist.cdf(env, x).value; | ||||
|         let max = Math.max(...xs); | ||||
|         if (x > max) { | ||||
|           let epsilon = (x - max) / x; | ||||
|  | @ -107,21 +104,18 @@ describe("cumulative density function", () => { | |||
|         } else if (typeof cdfValue == "number") { | ||||
|           expect(Math.round(1e5 * cdfValue) / 1e5).toBeLessThanOrEqual(1); | ||||
|         } else { | ||||
|           failDefault(); | ||||
|           fail(); | ||||
|         } | ||||
|       }) | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   test("is non-negative everywhere with zero when x is lower than the min", () => { | ||||
|   test.skip("is non-negative everywhere with zero when x is lower than the min", () => { | ||||
|     fc.assert( | ||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||
|         let xs = Array.from(xs_); | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let cdfValue = dist.cdf(x).value; | ||||
|         let dist = makeSampleSet(xs); | ||||
|         let cdfValue = dist.cdf(env, x).value; | ||||
|         expect(cdfValue).toBeGreaterThanOrEqual(0); | ||||
|       }) | ||||
|     ); | ||||
|  | @ -130,7 +124,7 @@ describe("cumulative density function", () => { | |||
| 
 | ||||
| // I no longer believe this is true.
 | ||||
| describe("probability density function", () => { | ||||
|   let n = 1000; | ||||
|   const env = { sampleCount: 1000, xyPointLength: 100 }; | ||||
| 
 | ||||
|   test.skip("assigns to the max at most the weight of the mean", () => { | ||||
|     fc.assert( | ||||
|  | @ -139,12 +133,9 @@ describe("probability density function", () => { | |||
|         let max = Math.max(...xs); | ||||
|         let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length; | ||||
|         // Should be from squiggleString once interpreter exposes sampleset
 | ||||
|         let dist = new Distribution( | ||||
|           { tag: "SampleSet", value: xs }, | ||||
|           { sampleCount: n, xyPointLength: 100 } | ||||
|         ); | ||||
|         let pdfValueMean = dist.pdf(mean).value; | ||||
|         let pdfValueMax = dist.pdf(max).value; | ||||
|         let dist = makeSampleSet(xs); | ||||
|         let pdfValueMean = dist.pdf(env, mean).value; | ||||
|         let pdfValueMax = dist.pdf(env, max).value; | ||||
|         if (typeof pdfValueMean == "number" && typeof pdfValueMax == "number") { | ||||
|           expect(pdfValueMax).toBeLessThanOrEqual(pdfValueMean); | ||||
|         } else { | ||||
|  | @ -164,11 +155,9 @@ describe("mean is mean", () => { | |||
|         (xs_) => { | ||||
|           let xs = Array.from(xs_); | ||||
|           let n = xs.length; | ||||
|           let dist = new Distribution( | ||||
|             { tag: "SampleSet", value: xs }, | ||||
|             { sampleCount: 2 * n, xyPointLength: 4 * n } | ||||
|           ); | ||||
|           let mean = dist.mean(); | ||||
|           let dist = makeSampleSet(xs); | ||||
|           let myEnv = { sampleCount: 2 * n, xyPointLength: 4 * n }; | ||||
|           let mean = dist.mean(myEnv); | ||||
|           if (typeof mean.value == "number") { | ||||
|             expectErrorToBeBounded( | ||||
|               mean.value, | ||||
|  | @ -177,7 +166,7 @@ describe("mean is mean", () => { | |||
|               1 | ||||
|             ); | ||||
|           } else { | ||||
|             failDefault(); | ||||
|             fail(); | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|  | @ -191,11 +180,9 @@ describe("mean is mean", () => { | |||
|         (xs_) => { | ||||
|           let xs = Array.from(xs_); | ||||
|           let n = xs.length; | ||||
|           let dist = new Distribution( | ||||
|             { tag: "SampleSet", value: xs }, | ||||
|             { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n } | ||||
|           ); | ||||
|           let mean = dist.mean(); | ||||
|           let dist = makeSampleSet(xs); | ||||
|           let myEnv = { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n }; | ||||
|           let mean = dist.mean(myEnv); | ||||
|           if (typeof mean.value == "number") { | ||||
|             expectErrorToBeBounded( | ||||
|               mean.value, | ||||
|  | @ -204,7 +191,7 @@ describe("mean is mean", () => { | |||
|               1 | ||||
|             ); | ||||
|           } else { | ||||
|             failDefault(); | ||||
|             fail(); | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import * as fc from "fast-check"; | |||
| describe("Scalar manipulation is well-modeled by javascript math", () => { | ||||
|   test("in the case of natural logarithms", () => { | ||||
|     fc.assert( | ||||
|       fc.property(fc.integer(), (x) => { | ||||
|       fc.property(fc.nat(), (x) => { | ||||
|         let squiggleString = `log(${x})`; | ||||
|         let squiggleResult = testRun(squiggleString); | ||||
|         if (x == 0) { | ||||
|  |  | |||
|  | @ -1,68 +1,31 @@ | |||
| import { | ||||
|   run, | ||||
|   runPartial, | ||||
|   bindings, | ||||
|   squiggleExpression, | ||||
|   errorValueToString, | ||||
|   defaultImports, | ||||
|   defaultBindings, | ||||
|   jsImports, | ||||
| } from "../../src/js/index"; | ||||
| import { run, SqValueTag } from "../../src/js"; | ||||
| export { SqValueTag }; | ||||
| 
 | ||||
| export function testRun( | ||||
|   x: string, | ||||
|   bindings: bindings = defaultBindings, | ||||
|   imports: jsImports = defaultImports | ||||
| ): squiggleExpression { | ||||
|   let squiggleResult = run( | ||||
|     x, | ||||
|     bindings, | ||||
|     { | ||||
| expect.extend({ | ||||
|   toEqualSqValue(x, y) { | ||||
|     // hack via https://github.com/facebook/jest/issues/10329#issuecomment-820656061
 | ||||
|     const { getMatchers } = require("expect/build/jestMatchersObject"); | ||||
|     return getMatchers().toEqual.call(this, x.toString(), y.toString()); | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| export function testRun(x: string) { | ||||
|   const { result, bindings } = run(x, { | ||||
|     environment: { | ||||
|       sampleCount: 1000, | ||||
|       xyPointLength: 100, | ||||
|     }, | ||||
|     imports | ||||
|   ); | ||||
|   if (squiggleResult.tag === "Ok") { | ||||
|     return squiggleResult.value; | ||||
|   }); | ||||
| 
 | ||||
|   if (result.tag === "Ok") { | ||||
|     return result.value; | ||||
|   } else { | ||||
|     throw new Error( | ||||
|       `Expected squiggle expression to evaluate but got error: ${errorValueToString( | ||||
|         squiggleResult.value | ||||
|       )}` | ||||
|       `Expected squiggle expression to evaluate but got error: ${result.value}` | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function testRunPartial( | ||||
|   x: string, | ||||
|   bindings: bindings = defaultBindings, | ||||
|   imports: jsImports = defaultImports | ||||
| ): bindings { | ||||
|   let squiggleResult = runPartial( | ||||
|     x, | ||||
|     bindings, | ||||
|     { | ||||
|       sampleCount: 1000, | ||||
|       xyPointLength: 100, | ||||
|     }, | ||||
|     imports | ||||
|   ); | ||||
|   if (squiggleResult.tag === "Ok") { | ||||
|     return squiggleResult.value; | ||||
|   } else { | ||||
|     throw new Error( | ||||
|       `Expected squiggle expression to evaluate but got error: ${errorValueToString( | ||||
|         squiggleResult.value | ||||
|       )}` | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function failDefault() { | ||||
|   expect("be reached").toBe("codepath should never"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * This appears also in `TestHelpers.res`. According to https://www.math.net/percent-error, it computes
 | ||||
|  * absolute error when numerical stability concerns make me not want to compute relative error. | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@quri/squiggle-lang", | ||||
|   "version": "0.3.1", | ||||
|   "version": "0.4.0-alpha.0", | ||||
|   "homepage": "https://squiggle-language.com", | ||||
|   "license": "MIT", | ||||
|   "scripts": { | ||||
|  |  | |||
							
								
								
									
										15
									
								
								packages/squiggle-lang/src/js/SqArray.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/squiggle-lang/src/js/SqArray.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| import * as RSArray from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Array.gen"; | ||||
| import { wrapValue } from "./SqValue"; | ||||
| import { SqValueLocation } from "./SqValueLocation"; | ||||
| 
 | ||||
| type T = RSArray.squiggleValue_Array; | ||||
| 
 | ||||
| export class SqArray { | ||||
|   constructor(private _value: T, public location: SqValueLocation) {} | ||||
| 
 | ||||
|   getValues() { | ||||
|     return RSArray.getValues(this._value).map((v, i) => | ||||
|       wrapValue(v, this.location.extend(i)) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										115
									
								
								packages/squiggle-lang/src/js/SqDistribution.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								packages/squiggle-lang/src/js/SqDistribution.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | |||
| import * as RSDistribution from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen"; | ||||
| import { distributionTag as Tag } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_tag"; | ||||
| import { environment } from "../rescript/ForTS/ForTS__Types.gen"; | ||||
| import { SqDistributionError } from "./SqDistributionError"; | ||||
| import { wrapPointSetDist } from "./SqPointSetDist"; | ||||
| import { resultMap2 } from "./types"; | ||||
| 
 | ||||
| type T = RSDistribution.distribution; | ||||
| export { Tag as SqDistributionTag }; | ||||
| 
 | ||||
| export const wrapDistribution = (value: T): SqDistribution => { | ||||
|   const tag = RSDistribution.getTag(value); | ||||
| 
 | ||||
|   return new tagToClass[tag](value); | ||||
| }; | ||||
| 
 | ||||
| abstract class SqAbstractDistribution { | ||||
|   abstract tag: Tag; | ||||
| 
 | ||||
|   constructor(private _value: T) {} | ||||
| 
 | ||||
|   protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => { | ||||
|     const value = rsMethod(this._value); | ||||
|     if (!value) throw new Error("Internal casting error"); | ||||
|     return value; | ||||
|   }; | ||||
| 
 | ||||
|   pointSet(env: environment) { | ||||
|     const innerResult = RSDistribution.toPointSet(this._value, env); | ||||
|     return resultMap2( | ||||
|       innerResult, | ||||
|       wrapPointSetDist, | ||||
|       (v: RSDistribution.distributionError) => new SqDistributionError(v) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   toString() { | ||||
|     RSDistribution.toString(this._value); | ||||
|   } | ||||
| 
 | ||||
|   mean(env: environment) { | ||||
|     return resultMap2( | ||||
|       RSDistribution.mean({ env }, this._value), | ||||
|       (v: number) => v, | ||||
|       (e: RSDistribution.distributionError) => new SqDistributionError(e) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pdf(env: environment, n: number) { | ||||
|     return resultMap2( | ||||
|       RSDistribution.pdf({ env }, this._value, n), | ||||
|       (v: number) => v, | ||||
|       (e: RSDistribution.distributionError) => new SqDistributionError(e) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   cdf(env: environment, n: number) { | ||||
|     return resultMap2( | ||||
|       RSDistribution.cdf({ env }, this._value, n), | ||||
|       (v: number) => v, | ||||
|       (e: RSDistribution.distributionError) => new SqDistributionError(e) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   inv(env: environment, n: number) { | ||||
|     return resultMap2( | ||||
|       RSDistribution.inv({ env }, this._value, n), | ||||
|       (v: number) => v, | ||||
|       (e: RSDistribution.distributionError) => new SqDistributionError(e) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   stdev(env: environment) { | ||||
|     return resultMap2( | ||||
|       RSDistribution.stdev({ env }, this._value), | ||||
|       (v: number) => v, | ||||
|       (e: RSDistribution.distributionError) => new SqDistributionError(e) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqPointSetDistribution extends SqAbstractDistribution { | ||||
|   tag = Tag.PointSet; | ||||
| 
 | ||||
|   value() { | ||||
|     return this.valueMethod(RSDistribution.getPointSet); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqSampleSetDistribution extends SqAbstractDistribution { | ||||
|   tag = Tag.SampleSet; | ||||
| 
 | ||||
|   value() { | ||||
|     return this.valueMethod(RSDistribution.getSampleSet); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqSymbolicDistribution extends SqAbstractDistribution { | ||||
|   tag = Tag.Symbolic; | ||||
| 
 | ||||
|   value() { | ||||
|     return this.valueMethod(RSDistribution.getSymbolic); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const tagToClass = { | ||||
|   [Tag.PointSet]: SqPointSetDistribution, | ||||
|   [Tag.SampleSet]: SqSampleSetDistribution, | ||||
|   [Tag.Symbolic]: SqSymbolicDistribution, | ||||
| } as const; | ||||
| 
 | ||||
| export type SqDistribution = | ||||
|   | SqPointSetDistribution | ||||
|   | SqSampleSetDistribution | ||||
|   | SqSymbolicDistribution; | ||||
							
								
								
									
										11
									
								
								packages/squiggle-lang/src/js/SqDistributionError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/squiggle-lang/src/js/SqDistributionError.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| import * as RSDistributionError from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Error.gen"; | ||||
| 
 | ||||
| type T = RSDistributionError.distributionError; | ||||
| 
 | ||||
| export class SqDistributionError { | ||||
|   constructor(private _value: T) {} | ||||
| 
 | ||||
|   toString() { | ||||
|     return RSDistributionError.toString(this._value); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										17
									
								
								packages/squiggle-lang/src/js/SqError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/squiggle-lang/src/js/SqError.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; | ||||
| 
 | ||||
| export class SqError { | ||||
|   constructor(private _value: RSErrorValue.reducerErrorValue) {} | ||||
| 
 | ||||
|   toString() { | ||||
|     return RSErrorValue.toString(this._value); | ||||
|   } | ||||
| 
 | ||||
|   static createTodoError(v: string) { | ||||
|     return new SqError(RSErrorValue.createTodoError(v)); | ||||
|   } | ||||
| 
 | ||||
|   static createOtherError(v: string) { | ||||
|     return new SqError(RSErrorValue.createOtherError(v)); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										48
									
								
								packages/squiggle-lang/src/js/SqLambda.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								packages/squiggle-lang/src/js/SqLambda.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| import * as RSLambda from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.gen"; | ||||
| import { SqError } from "./SqError"; | ||||
| import { SqValue } from "./SqValue"; | ||||
| import { SqValueLocation } from "./SqValueLocation"; | ||||
| import { result } from "./types"; | ||||
| 
 | ||||
| type T = RSLambda.squiggleValue_Lambda; | ||||
| 
 | ||||
| export class SqLambda { | ||||
|   constructor(private _value: T, public location: SqValueLocation) {} | ||||
| 
 | ||||
|   parameters() { | ||||
|     return RSLambda.parameters(this._value); | ||||
|   } | ||||
| 
 | ||||
|   call(args: (number | string)[]): result<SqValue, SqError> { | ||||
|     const { project, sourceId } = this.location; | ||||
|     // Might be good to use uuid instead, but there's no way to remove sources from projects.
 | ||||
|     // So this is not thread-safe.
 | ||||
|     const callId = "__lambda__"; | ||||
|     const quote = (arg: string) => | ||||
|       `"${arg.replace(new RegExp('"', "g"), '\\"')}"`; | ||||
|     const argsSource = args | ||||
|       .map((arg) => (typeof arg === "number" ? arg : quote(arg))) | ||||
|       .join(","); | ||||
| 
 | ||||
|     // end expression values are exposed in bindings via secret `__result__` variable and we can access them through it
 | ||||
|     const pathItems = [ | ||||
|       ...(this.location.path.root === "result" ? ["__result__"] : []), | ||||
|       ...this.location.path.items, | ||||
|     ]; | ||||
| 
 | ||||
|     // full function name, e.g. foo.bar[3].baz
 | ||||
|     const functionNameSource = pathItems | ||||
|       .map((item, i) => | ||||
|         typeof item === "string" ? (i ? "." + item : item) : `[${item}]` | ||||
|       ) | ||||
|       .join(""); | ||||
| 
 | ||||
|     // something like: foo.bar[3].baz(1,2,3)
 | ||||
|     const source = `${functionNameSource}(${argsSource})`; | ||||
| 
 | ||||
|     project.setSource(callId, source); | ||||
|     project.setContinues(callId, [sourceId]); | ||||
|     project.run(callId); | ||||
|     return project.getResult(callId); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								packages/squiggle-lang/src/js/SqLambdaDeclaration.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/squiggle-lang/src/js/SqLambdaDeclaration.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| import * as RSDeclaration from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Declaration.gen"; | ||||
| 
 | ||||
| type T = RSDeclaration.squiggleValue_Declaration; | ||||
| 
 | ||||
| export class SqLambdaDeclaration { | ||||
|   constructor(private _value: T) {} | ||||
| } | ||||
							
								
								
									
										30
									
								
								packages/squiggle-lang/src/js/SqModule.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/squiggle-lang/src/js/SqModule.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| 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)); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										101
									
								
								packages/squiggle-lang/src/js/SqPointSetDist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								packages/squiggle-lang/src/js/SqPointSetDist.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | |||
| import * as _ from "lodash"; | ||||
| import { wrapDistribution } from "./SqDistribution"; | ||||
| 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"; | ||||
| 
 | ||||
| type T = RSPointSetDist.pointSetDistribution; | ||||
| 
 | ||||
| export type SqPoint = { x: number; y: number }; | ||||
| export type SqShape = { | ||||
|   continuous: SqPoint[]; | ||||
|   discrete: SqPoint[]; | ||||
| }; | ||||
| 
 | ||||
| const shapePoints = ( | ||||
|   x: RSPointSetDist.continuousShape | RSPointSetDist.discreteShape | ||||
| ): SqPoint[] => { | ||||
|   let xs = x.xyShape.xs; | ||||
|   let ys = x.xyShape.ys; | ||||
|   return _.zipWith(xs, ys, (x, y) => ({ x, y })); | ||||
| }; | ||||
| 
 | ||||
| export const wrapPointSetDist = (value: T) => { | ||||
|   const tag = RSPointSetDist.getTag(value); | ||||
| 
 | ||||
|   return new tagToClass[tag](value); | ||||
| }; | ||||
| 
 | ||||
| abstract class SqAbstractPointSetDist { | ||||
|   constructor(private _value: T) {} | ||||
| 
 | ||||
|   abstract asShape(): SqShape; | ||||
| 
 | ||||
|   protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => { | ||||
|     const value = rsMethod(this._value); | ||||
|     if (!value) throw new Error("Internal casting error"); | ||||
|     return value; | ||||
|   }; | ||||
| 
 | ||||
|   asDistribution() { | ||||
|     return wrapDistribution(RSPointSetDist.toDistribution(this._value)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqMixedPointSetDist extends SqAbstractPointSetDist { | ||||
|   tag = Tag.Mixed as const; | ||||
| 
 | ||||
|   get value(): RSPointSetDist.mixedShape { | ||||
|     return this.valueMethod(RSPointSetDist.getMixed); | ||||
|   } | ||||
| 
 | ||||
|   asShape() { | ||||
|     const v = this.value; | ||||
|     return { | ||||
|       discrete: shapePoints(v.discrete), | ||||
|       continuous: shapePoints(v.continuous), | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqDiscretePointSetDist extends SqAbstractPointSetDist { | ||||
|   tag = Tag.Discrete as const; | ||||
| 
 | ||||
|   get value(): RSPointSetDist.discreteShape { | ||||
|     return this.valueMethod(RSPointSetDist.getDiscrete); | ||||
|   } | ||||
| 
 | ||||
|   asShape() { | ||||
|     const v = this.value; | ||||
|     return { | ||||
|       discrete: shapePoints(v), | ||||
|       continuous: [], | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqContinuousPointSetDist extends SqAbstractPointSetDist { | ||||
|   tag = Tag.Continuous as const; | ||||
| 
 | ||||
|   get value(): RSPointSetDist.continuousShape { | ||||
|     return this.valueMethod(RSPointSetDist.getContinues); | ||||
|   } | ||||
| 
 | ||||
|   asShape() { | ||||
|     const v = this.value; | ||||
|     return { | ||||
|       discrete: [], | ||||
|       continuous: shapePoints(v), | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const tagToClass = { | ||||
|   [Tag.Mixed]: SqMixedPointSetDist, | ||||
|   [Tag.Discrete]: SqDiscretePointSetDist, | ||||
|   [Tag.Continuous]: SqContinuousPointSetDist, | ||||
| } as const; | ||||
| 
 | ||||
| export type SqPointSetDist = | ||||
|   | SqMixedPointSetDist | ||||
|   | SqDiscretePointSetDist | ||||
|   | SqContinuousPointSetDist; | ||||
							
								
								
									
										114
									
								
								packages/squiggle-lang/src/js/SqProject.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								packages/squiggle-lang/src/js/SqProject.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | |||
| import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen"; | ||||
| import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; | ||||
| import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen"; | ||||
| import { SqError } from "./SqError"; | ||||
| import { SqModule } from "./SqModule"; | ||||
| import { wrapValue } from "./SqValue"; | ||||
| import { resultMap2 } from "./types"; | ||||
| import { SqValueLocation } from "./SqValueLocation"; | ||||
| 
 | ||||
| export class SqProject { | ||||
|   constructor(private _value: RSProject.reducerProject) {} | ||||
| 
 | ||||
|   static create() { | ||||
|     return new SqProject(RSProject.createProject()); | ||||
|   } | ||||
| 
 | ||||
|   getSourceIds() { | ||||
|     return RSProject.getSourceIds(this._value); | ||||
|   } | ||||
| 
 | ||||
|   setSource(sourceId: string, value: string) { | ||||
|     return RSProject.setSource(this._value, sourceId, value); | ||||
|   } | ||||
| 
 | ||||
|   getSource(sourceId: string) { | ||||
|     return RSProject.getSource(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   touchSource(sourceId: string) { | ||||
|     return RSProject.touchSource(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   clean(sourceId: string) { | ||||
|     return RSProject.clean(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   cleanAll() { | ||||
|     return RSProject.cleanAll(this._value); | ||||
|   } | ||||
| 
 | ||||
|   cleanResults(sourceId: string) { | ||||
|     return RSProject.cleanResults(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   cleanAllResults() { | ||||
|     return RSProject.cleanAllResults(this._value); | ||||
|   } | ||||
| 
 | ||||
|   getIncludes(sourceId: string) { | ||||
|     return resultMap2( | ||||
|       RSProject.getIncludes(this._value, sourceId), | ||||
|       (a) => a, | ||||
|       (v: reducerErrorValue) => new SqError(v) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getContinues(sourceId: string) { | ||||
|     return RSProject.getContinues(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   setContinues(sourceId: string, continues: string[]) { | ||||
|     return RSProject.setContinues(this._value, sourceId, continues); | ||||
|   } | ||||
| 
 | ||||
|   getRunOrder() { | ||||
|     return RSProject.getRunOrder(this._value); | ||||
|   } | ||||
| 
 | ||||
|   getRunOrderFor(sourceId: string) { | ||||
|     return RSProject.getRunOrderFor(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   parseIncludes(sourceId: string) { | ||||
|     return RSProject.parseIncludes(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   run(sourceId: string) { | ||||
|     return RSProject.run(this._value, sourceId); | ||||
|   } | ||||
| 
 | ||||
|   runAll() { | ||||
|     return RSProject.runAll(this._value); | ||||
|   } | ||||
| 
 | ||||
|   getBindings(sourceId: string) { | ||||
|     return new SqModule( | ||||
|       RSProject.getBindings(this._value, sourceId), | ||||
|       new SqValueLocation(this, sourceId, { | ||||
|         root: "bindings", | ||||
|         items: [], | ||||
|       }) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getResult(sourceId: string) { | ||||
|     const innerResult = RSProject.getResult(this._value, sourceId); | ||||
|     return resultMap2( | ||||
|       innerResult, | ||||
|       (v) => | ||||
|         wrapValue( | ||||
|           v, | ||||
|           new SqValueLocation(this, sourceId, { | ||||
|             root: "result", | ||||
|             items: [], | ||||
|           }) | ||||
|         ), | ||||
|       (v: reducerErrorValue) => new SqError(v) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   setEnvironment(environment: environment) { | ||||
|     RSProject.setEnvironment(this._value, environment); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										15
									
								
								packages/squiggle-lang/src/js/SqRecord.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/squiggle-lang/src/js/SqRecord.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen"; | ||||
| import { wrapValue } from "./SqValue"; | ||||
| import { SqValueLocation } from "./SqValueLocation"; | ||||
| 
 | ||||
| type T = RSRecord.squiggleValue_Record; | ||||
| 
 | ||||
| export class SqRecord { | ||||
|   constructor(private _value: T, public location: SqValueLocation) {} | ||||
| 
 | ||||
|   entries() { | ||||
|     return RSRecord.getKeyValuePairs(this._value).map( | ||||
|       ([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								packages/squiggle-lang/src/js/SqType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/squiggle-lang/src/js/SqType.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| import * as RSType from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Type.gen"; | ||||
| 
 | ||||
| type T = RSType.squiggleValue_Type; | ||||
| 
 | ||||
| export class SqType { | ||||
|   constructor(private _value: T) {} | ||||
| } | ||||
							
								
								
									
										216
									
								
								packages/squiggle-lang/src/js/SqValue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								packages/squiggle-lang/src/js/SqValue.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,216 @@ | |||
| import * as RSValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.gen"; | ||||
| import { squiggleValueTag as Tag } from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag"; | ||||
| import { wrapDistribution } from "./SqDistribution"; | ||||
| import { SqLambda } from "./SqLambda"; | ||||
| import { SqLambdaDeclaration } from "./SqLambdaDeclaration"; | ||||
| import { SqModule } from "./SqModule"; | ||||
| import { SqRecord } from "./SqRecord"; | ||||
| import { SqArray } from "./SqArray"; | ||||
| import { SqType } from "./SqType"; | ||||
| import { SqProject } from "./SqProject"; | ||||
| import { SqValueLocation } from "./SqValueLocation"; | ||||
| 
 | ||||
| export { Tag as SqValueTag }; | ||||
| 
 | ||||
| type T = RSValue.squiggleValue; | ||||
| 
 | ||||
| export const wrapValue = (value: T, location: SqValueLocation): SqValue => { | ||||
|   const tag = RSValue.getTag(value); | ||||
| 
 | ||||
|   return new tagToClass[tag](value, location); | ||||
| }; | ||||
| 
 | ||||
| export abstract class SqAbstractValue { | ||||
|   abstract tag: Tag; | ||||
| 
 | ||||
|   constructor(private _value: T, public location: SqValueLocation) {} | ||||
| 
 | ||||
|   protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => { | ||||
|     const value = rsMethod(this._value); | ||||
|     if (value === undefined || value === null) { | ||||
|       throw new Error("Internal casting error"); | ||||
|     } | ||||
|     return value; | ||||
|   }; | ||||
| 
 | ||||
|   toString() { | ||||
|     return RSValue.toString(this._value); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqArrayValue extends SqAbstractValue { | ||||
|   tag = Tag.Array as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return new SqArray(this.valueMethod(RSValue.getArray), this.location); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqArrayStringValue extends SqAbstractValue { | ||||
|   tag = Tag.ArrayString as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return this.valueMethod(RSValue.getArrayString); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqBoolValue extends SqAbstractValue { | ||||
|   tag = Tag.Bool as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return this.valueMethod(RSValue.getBool); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqCallValue extends SqAbstractValue { | ||||
|   tag = Tag.Call as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return this.valueMethod(RSValue.getCall); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqDateValue extends SqAbstractValue { | ||||
|   tag = Tag.Date as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return this.valueMethod(RSValue.getDate); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqDeclarationValue extends SqAbstractValue { | ||||
|   tag = Tag.Declaration as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return new SqLambdaDeclaration(this.valueMethod(RSValue.getDeclaration)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqDistributionValue extends SqAbstractValue { | ||||
|   tag = Tag.Distribution as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return wrapDistribution(this.valueMethod(RSValue.getDistribution)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqLambdaValue extends SqAbstractValue { | ||||
|   tag = Tag.Lambda as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return new SqLambda(this.valueMethod(RSValue.getLambda), this.location); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
|   tag = Tag.Number as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return this.valueMethod(RSValue.getNumber); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqRecordValue extends SqAbstractValue { | ||||
|   tag = Tag.Record as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return new SqRecord(this.valueMethod(RSValue.getRecord), this.location); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqStringValue extends SqAbstractValue { | ||||
|   tag = Tag.String as const; | ||||
| 
 | ||||
|   get value(): string { | ||||
|     return this.valueMethod(RSValue.getString); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqSymbolValue extends SqAbstractValue { | ||||
|   tag = Tag.Symbol as const; | ||||
| 
 | ||||
|   get value(): string { | ||||
|     return this.valueMethod(RSValue.getSymbol); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SqTimeDurationValue extends SqAbstractValue { | ||||
|   tag = Tag.TimeDuration as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return this.valueMethod(RSValue.getTimeDuration); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
|   tag = Tag.Void as const; | ||||
| 
 | ||||
|   get value() { | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const tagToClass = { | ||||
|   [Tag.Array]: SqArrayValue, | ||||
|   [Tag.ArrayString]: SqArrayStringValue, | ||||
|   [Tag.Bool]: SqBoolValue, | ||||
|   [Tag.Call]: SqCallValue, | ||||
|   [Tag.Date]: SqDateValue, | ||||
|   [Tag.Declaration]: SqDeclarationValue, | ||||
|   [Tag.Distribution]: SqDistributionValue, | ||||
|   [Tag.Lambda]: SqLambdaValue, | ||||
|   [Tag.Module]: SqModuleValue, | ||||
|   [Tag.Number]: SqNumberValue, | ||||
|   [Tag.Record]: SqRecordValue, | ||||
|   [Tag.String]: SqStringValue, | ||||
|   [Tag.Symbol]: SqSymbolValue, | ||||
|   [Tag.TimeDuration]: SqTimeDurationValue, | ||||
|   [Tag.Type]: SqTypeValue, | ||||
|   [Tag.TypeIdentifier]: SqTypeIdentifierValue, | ||||
|   [Tag.Void]: SqVoidValue, | ||||
| } as const; | ||||
| 
 | ||||
| // FIXME
 | ||||
| // type SqValue = typeof tagToClass[keyof typeof tagToClass];
 | ||||
| export type SqValue = | ||||
|   | SqArrayValue | ||||
|   | SqArrayStringValue | ||||
|   | SqBoolValue | ||||
|   | SqCallValue | ||||
|   | SqDateValue | ||||
|   | SqDeclarationValue | ||||
|   | SqDistributionValue | ||||
|   | SqLambdaValue | ||||
|   | SqModuleValue | ||||
|   | SqNumberValue | ||||
|   | SqRecordValue | ||||
|   | SqStringValue | ||||
|   | SqSymbolValue | ||||
|   | SqTimeDurationValue | ||||
|   | SqTypeValue | ||||
|   | SqTypeIdentifierValue | ||||
|   | SqVoidValue; | ||||
							
								
								
									
										24
									
								
								packages/squiggle-lang/src/js/SqValueLocation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/squiggle-lang/src/js/SqValueLocation.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| import { isParenthesisNode } from "mathjs"; | ||||
| import { SqProject } from "./SqProject"; | ||||
| 
 | ||||
| type PathItem = string | number; | ||||
| 
 | ||||
| type SqValuePath = { | ||||
|   root: "result" | "bindings"; | ||||
|   items: PathItem[]; | ||||
| }; | ||||
| 
 | ||||
| export class SqValueLocation { | ||||
|   constructor( | ||||
|     public project: SqProject, | ||||
|     public sourceId: string, | ||||
|     public path: SqValuePath | ||||
|   ) {} | ||||
| 
 | ||||
|   extend(item: PathItem) { | ||||
|     return new SqValueLocation(this.project, this.sourceId, { | ||||
|       root: this.path.root, | ||||
|       items: [...this.path.items, item], | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | @ -1,252 +0,0 @@ | |||
| import * as _ from "lodash"; | ||||
| import { | ||||
|   genericDist, | ||||
|   continuousShape, | ||||
|   discreteShape, | ||||
|   environment, | ||||
|   distributionError, | ||||
|   toPointSet, | ||||
|   distributionErrorToString, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| import { result, resultMap, Ok } from "./types"; | ||||
| import { | ||||
|   Constructors_mean, | ||||
|   Constructors_stdev, | ||||
|   Constructors_sample, | ||||
|   Constructors_pdf, | ||||
|   Constructors_cdf, | ||||
|   Constructors_inv, | ||||
|   Constructors_normalize, | ||||
|   Constructors_isNormalized, | ||||
|   Constructors_toPointSet, | ||||
|   Constructors_toSampleSet, | ||||
|   Constructors_truncate, | ||||
|   Constructors_inspect, | ||||
|   Constructors_toString, | ||||
|   Constructors_toSparkline, | ||||
|   Constructors_algebraicAdd, | ||||
|   Constructors_algebraicMultiply, | ||||
|   Constructors_algebraicDivide, | ||||
|   Constructors_algebraicSubtract, | ||||
|   Constructors_algebraicLogarithm, | ||||
|   Constructors_algebraicPower, | ||||
|   Constructors_pointwiseAdd, | ||||
|   Constructors_pointwiseMultiply, | ||||
|   Constructors_pointwiseDivide, | ||||
|   Constructors_pointwiseSubtract, | ||||
|   Constructors_pointwiseLogarithm, | ||||
|   Constructors_pointwisePower, | ||||
| } from "../rescript/Distributions/DistributionOperation.gen"; | ||||
| 
 | ||||
| export type point = { x: number; y: number }; | ||||
| 
 | ||||
| function shapePoints(x: continuousShape | discreteShape): point[] { | ||||
|   let xs = x.xyShape.xs; | ||||
|   let ys = x.xyShape.ys; | ||||
|   return _.zipWith(xs, ys, (x, y) => ({ x, y })); | ||||
| } | ||||
| export type shape = { | ||||
|   continuous: point[]; | ||||
|   discrete: point[]; | ||||
| }; | ||||
| 
 | ||||
| export class Distribution { | ||||
|   t: genericDist; | ||||
|   env: environment; | ||||
| 
 | ||||
|   constructor(t: genericDist, env: environment) { | ||||
|     this.t = t; | ||||
|     this.env = env; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   mapResultDist( | ||||
|     r: result<genericDist, distributionError> | ||||
|   ): result<Distribution, distributionError> { | ||||
|     return resultMap(r, (v: genericDist) => new Distribution(v, this.env)); | ||||
|   } | ||||
| 
 | ||||
|   mean(): result<number, distributionError> { | ||||
|     return Constructors_mean({ env: this.env }, this.t); | ||||
|   } | ||||
| 
 | ||||
|   stdev(): result<number, distributionError> { | ||||
|     return Constructors_stdev({ env: this.env }, this.t); | ||||
|   } | ||||
| 
 | ||||
|   sample(): result<number, distributionError> { | ||||
|     return Constructors_sample({ env: this.env }, this.t); | ||||
|   } | ||||
| 
 | ||||
|   pdf(n: number): result<number, distributionError> { | ||||
|     return Constructors_pdf({ env: this.env }, this.t, n); | ||||
|   } | ||||
| 
 | ||||
|   cdf(n: number): result<number, distributionError> { | ||||
|     return Constructors_cdf({ env: this.env }, this.t, n); | ||||
|   } | ||||
| 
 | ||||
|   inv(n: number): result<number, distributionError> { | ||||
|     return Constructors_inv({ env: this.env }, this.t, n); | ||||
|   } | ||||
| 
 | ||||
|   isNormalized(): result<boolean, distributionError> { | ||||
|     return Constructors_isNormalized({ env: this.env }, this.t); | ||||
|   } | ||||
| 
 | ||||
|   normalize(): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_normalize({ env: this.env }, this.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   type() { | ||||
|     return this.t.tag; | ||||
|   } | ||||
| 
 | ||||
|   pointSet(): result<shape, distributionError> { | ||||
|     let pointSet = toPointSet( | ||||
|       this.t, | ||||
|       { | ||||
|         xyPointLength: this.env.xyPointLength, | ||||
|         sampleCount: this.env.sampleCount, | ||||
|       }, | ||||
|       undefined | ||||
|     ); | ||||
|     if (pointSet.tag === "Ok") { | ||||
|       let distribution = pointSet.value; | ||||
|       if (distribution.tag === "Continuous") { | ||||
|         return Ok({ | ||||
|           continuous: shapePoints(distribution.value), | ||||
|           discrete: [], | ||||
|         }); | ||||
|       } else if (distribution.tag === "Discrete") { | ||||
|         return Ok({ | ||||
|           discrete: shapePoints(distribution.value), | ||||
|           continuous: [], | ||||
|         }); | ||||
|       } else { | ||||
|         return Ok({ | ||||
|           discrete: shapePoints(distribution.value.discrete), | ||||
|           continuous: shapePoints(distribution.value.continuous), | ||||
|         }); | ||||
|       } | ||||
|     } else { | ||||
|       return pointSet; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   toPointSet(): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_toPointSet({ env: this.env }, this.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   toSampleSet(n: number): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_toSampleSet({ env: this.env }, this.t, n) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   truncate( | ||||
|     left: number, | ||||
|     right: number | ||||
|   ): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_truncate({ env: this.env }, this.t, left, right) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   inspect(): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist(Constructors_inspect({ env: this.env }, this.t)); | ||||
|   } | ||||
| 
 | ||||
|   toString(): string { | ||||
|     let result = Constructors_toString({ env: this.env }, this.t); | ||||
|     if (result.tag === "Ok") { | ||||
|       return result.value; | ||||
|     } else { | ||||
|       return distributionErrorToString(result.value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   toSparkline(n: number): result<string, distributionError> { | ||||
|     return Constructors_toSparkline({ env: this.env }, this.t, n); | ||||
|   } | ||||
| 
 | ||||
|   algebraicAdd(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_algebraicAdd({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   algebraicMultiply(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_algebraicMultiply({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   algebraicDivide(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_algebraicDivide({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   algebraicSubtract(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_algebraicSubtract({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   algebraicLogarithm( | ||||
|     d2: Distribution | ||||
|   ): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_algebraicLogarithm({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   algebraicPower(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_algebraicPower({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pointwiseAdd(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_pointwiseAdd({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pointwiseMultiply(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_pointwiseMultiply({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pointwiseDivide(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_pointwiseDivide({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pointwiseSubtract(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_pointwiseSubtract({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pointwiseLogarithm( | ||||
|     d2: Distribution | ||||
|   ): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_pointwiseLogarithm({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   pointwisePower(d2: Distribution): result<Distribution, distributionError> { | ||||
|     return this.mapResultDist( | ||||
|       Constructors_pointwisePower({ env: this.env }, this.t, d2.t) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -1,205 +1,36 @@ | |||
| import * as _ from "lodash"; | ||||
| import type { | ||||
|   environment, | ||||
|   expressionValue, | ||||
|   externalBindings, | ||||
|   errorValue, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| import { | ||||
|   defaultEnvironment, | ||||
|   evaluatePartialUsingExternalBindings, | ||||
|   evaluateUsingOptions, | ||||
|   foreignFunctionInterface, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen"; | ||||
| import { SqProject } from "./SqProject"; | ||||
| import { SqValue, SqValueTag } from "./SqValue"; | ||||
| export { SqValueLocation } from "./SqValueLocation"; | ||||
| export { result } from "../rescript/ForTS/ForTS_Result_tag"; | ||||
| export { SqDistribution, SqDistributionTag } from "./SqDistribution"; | ||||
| export { SqDistributionError } from "./SqDistributionError"; | ||||
| export { SqRecord } from "./SqRecord"; | ||||
| export { SqLambda } from "./SqLambda"; | ||||
| export { SqProject }; | ||||
| export { SqValue, SqValueTag }; | ||||
| export { | ||||
|   makeSampleSetDist, | ||||
|   errorValueToString, | ||||
|   distributionErrorToString, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| export type { | ||||
|   distributionError, | ||||
|   declarationArg, | ||||
|   declaration, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| export type { errorValue, externalBindings as bindings, jsImports }; | ||||
| import { | ||||
|   jsValueToBinding, | ||||
|   jsValueToExpressionValue, | ||||
|   jsValue, | ||||
|   rescriptExport, | ||||
|   squiggleExpression, | ||||
|   convertRawToTypescript, | ||||
|   lambdaValue, | ||||
| } from "./rescript_interop"; | ||||
| import { result, resultMap, tag, tagged } from "./types"; | ||||
| import { Distribution, shape } from "./distribution"; | ||||
|   environment, | ||||
|   defaultEnvironment, | ||||
| } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen"; | ||||
| export { SqError } from "./SqError"; | ||||
| export { SqShape } from "./SqPointSetDist"; | ||||
| 
 | ||||
| export { Distribution, resultMap, defaultEnvironment }; | ||||
| export type { result, shape, environment, lambdaValue, squiggleExpression }; | ||||
| export { resultMap } from "./types"; | ||||
| 
 | ||||
| export { parse } from "./parse"; | ||||
| 
 | ||||
| export let defaultSamplingInputs: environment = { | ||||
|   sampleCount: 1000, | ||||
|   xyPointLength: 1000, | ||||
| export const run = ( | ||||
|   code: string, | ||||
|   options?: { | ||||
|     environment?: environment; | ||||
|   } | ||||
| ) => { | ||||
|   const project = SqProject.create(); | ||||
|   project.setSource("main", code); | ||||
|   if (options?.environment) { | ||||
|     project.setEnvironment(options.environment); | ||||
|   } | ||||
|   project.run("main"); | ||||
|   const result = project.getResult("main"); | ||||
|   const bindings = project.getBindings("main"); | ||||
|   return { result, bindings }; | ||||
| }; | ||||
| 
 | ||||
| export function run( | ||||
|   squiggleString: string, | ||||
|   bindings?: externalBindings, | ||||
|   environment?: environment, | ||||
|   imports?: jsImports | ||||
| ): result<squiggleExpression, errorValue> { | ||||
|   let b = bindings ? bindings : defaultBindings; | ||||
|   let i = imports ? imports : defaultImports; | ||||
|   let e = environment ? environment : defaultEnvironment; | ||||
|   let res: result<expressionValue, errorValue> = evaluateUsingOptions( | ||||
|     { externalBindings: mergeImportsWithBindings(b, i), environment: e }, | ||||
|     squiggleString | ||||
|   ); | ||||
|   return resultMap(res, (x) => createTsExport(x, e)); | ||||
| } | ||||
| 
 | ||||
| // Run Partial. A partial is a block of code that doesn't return a value
 | ||||
| export function runPartial( | ||||
|   squiggleString: string, | ||||
|   bindings?: externalBindings, | ||||
|   environment?: environment, | ||||
|   imports?: jsImports | ||||
| ): result<externalBindings, errorValue> { | ||||
|   let b = bindings ? bindings : defaultBindings; | ||||
|   let i = imports ? imports : defaultImports; | ||||
|   let e = environment ? environment : defaultEnvironment; | ||||
| 
 | ||||
|   return evaluatePartialUsingExternalBindings( | ||||
|     squiggleString, | ||||
|     mergeImportsWithBindings(b, i), | ||||
|     e | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function runForeign( | ||||
|   fn: lambdaValue, | ||||
|   args: jsValue[], | ||||
|   environment?: environment | ||||
| ): result<squiggleExpression, errorValue> { | ||||
|   let e = environment ? environment : defaultEnvironment; | ||||
|   let res: result<expressionValue, errorValue> = foreignFunctionInterface( | ||||
|     fn, | ||||
|     args.map(jsValueToExpressionValue), | ||||
|     e | ||||
|   ); | ||||
|   return resultMap(res, (x) => createTsExport(x, e)); | ||||
| } | ||||
| 
 | ||||
| function mergeImportsWithBindings( | ||||
|   bindings: externalBindings, | ||||
|   imports: jsImports | ||||
| ): externalBindings { | ||||
|   let transformedImports = Object.fromEntries( | ||||
|     Object.entries(imports).map(([key, value]) => [ | ||||
|       "$" + key, | ||||
|       jsValueToBinding(value), | ||||
|     ]) | ||||
|   ); | ||||
|   return _.merge(bindings, transformedImports); | ||||
| } | ||||
| 
 | ||||
| type jsImports = { [key: string]: jsValue }; | ||||
| 
 | ||||
| export let defaultImports: jsImports = {}; | ||||
| export let defaultBindings: externalBindings = {}; | ||||
| 
 | ||||
| export function mergeBindings( | ||||
|   allBindings: externalBindings[] | ||||
| ): externalBindings { | ||||
|   return allBindings.reduce((acc, x) => ({ ...acc, ...x })); | ||||
| } | ||||
| 
 | ||||
| function createTsExport( | ||||
|   x: expressionValue, | ||||
|   environment: environment | ||||
| ): squiggleExpression { | ||||
|   switch (x) { | ||||
|     case "EvVoid": | ||||
|       return tag("void", ""); | ||||
|     default: { | ||||
|       switch (x.tag) { | ||||
|         case "EvArray": | ||||
|           // genType doesn't convert anything more than 2 layers down into {tag: x, value: x}
 | ||||
|           // format, leaving it as the raw values. This converts the raw values
 | ||||
|           // directly into typescript values.
 | ||||
|           //
 | ||||
|           // The casting here is because genType is about the types of the returned
 | ||||
|           // values, claiming they are fully recursive when that's not actually the
 | ||||
|           // case
 | ||||
|           return tag( | ||||
|             "array", | ||||
|             x.value.map( | ||||
|               (arrayItem): squiggleExpression => | ||||
|                 convertRawToTypescript( | ||||
|                   arrayItem as unknown as rescriptExport, | ||||
|                   environment | ||||
|                 ) | ||||
|             ) | ||||
|           ); | ||||
|         case "EvArrayString": | ||||
|           return tag("arraystring", x.value); | ||||
|         case "EvBool": | ||||
|           return tag("boolean", x.value); | ||||
|         case "EvCall": | ||||
|           return tag("call", x.value); | ||||
|         case "EvLambda": | ||||
|           return tag("lambda", x.value); | ||||
|         case "EvDistribution": | ||||
|           return tag("distribution", new Distribution(x.value, environment)); | ||||
|         case "EvNumber": | ||||
|           return tag("number", x.value); | ||||
|         case "EvRecord": | ||||
|           // genType doesn't support records, so we have to do the raw conversion ourself
 | ||||
|           let result: tagged<"record", { [key: string]: squiggleExpression }> = | ||||
|             tag( | ||||
|               "record", | ||||
|               _.mapValues(x.value, (x: unknown) => | ||||
|                 convertRawToTypescript(x as rescriptExport, environment) | ||||
|               ) | ||||
|             ); | ||||
|           return result; | ||||
|         case "EvString": | ||||
|           return tag("string", x.value); | ||||
|         case "EvSymbol": | ||||
|           return tag("symbol", x.value); | ||||
|         case "EvDate": | ||||
|           return tag("date", x.value); | ||||
|         case "EvTimeDuration": | ||||
|           return tag("timeDuration", x.value); | ||||
|         case "EvDeclaration": | ||||
|           return tag("lambdaDeclaration", x.value); | ||||
|         case "EvTypeIdentifier": | ||||
|           return tag("typeIdentifier", x.value); | ||||
|         case "EvType": | ||||
|           let typeResult: tagged< | ||||
|             "type", | ||||
|             { [key: string]: squiggleExpression } | ||||
|           > = tag( | ||||
|             "type", | ||||
|             _.mapValues(x.value, (x: unknown) => | ||||
|               convertRawToTypescript(x as rescriptExport, environment) | ||||
|             ) | ||||
|           ); | ||||
|           return typeResult; | ||||
|         case "EvModule": | ||||
|           let moduleResult: tagged< | ||||
|             "module", | ||||
|             { [key: string]: squiggleExpression } | ||||
|           > = tag( | ||||
|             "module", | ||||
|             _.mapValues(x.value, (x: unknown) => | ||||
|               convertRawToTypescript(x as rescriptExport, environment) | ||||
|             ) | ||||
|           ); | ||||
|           return moduleResult; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,23 +1,23 @@ | |||
| import { | ||||
|   errorValue, | ||||
|   parse as parseRescript, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| import { result } from "./types"; | ||||
| import { AnyPeggyNode } from "../rescript/Reducer/Reducer_Peggy/helpers"; | ||||
| // import {
 | ||||
| //   errorValue,
 | ||||
| //   parse as parseRescript,
 | ||||
| // } from "../rescript/TypescriptInterface.gen";
 | ||||
| // import { result } from "./types";
 | ||||
| // import { AnyPeggyNode } from "../rescript/Reducer/Reducer_Peggy/helpers";
 | ||||
| 
 | ||||
| export function parse( | ||||
|   squiggleString: string | ||||
| ): result<AnyPeggyNode, Extract<errorValue, { tag: "RESyntaxError" }>> { | ||||
|   const maybeExpression = parseRescript(squiggleString); | ||||
|   if (maybeExpression.tag === "Ok") { | ||||
|     return { tag: "Ok", value: maybeExpression.value as AnyPeggyNode }; | ||||
|   } else { | ||||
|     if ( | ||||
|       typeof maybeExpression.value !== "object" || | ||||
|       maybeExpression.value.tag !== "RESyntaxError" | ||||
|     ) { | ||||
|       throw new Error("Expected syntax error"); | ||||
|     } | ||||
|     return { tag: "Error", value: maybeExpression.value }; | ||||
|   } | ||||
| } | ||||
| // export function parse(
 | ||||
| //   squiggleString: string
 | ||||
| // ): result<AnyPeggyNode, Extract<errorValue, { tag: "RESyntaxError" }>> {
 | ||||
| //   const maybeExpression = parseRescript(squiggleString);
 | ||||
| //   if (maybeExpression.tag === "Ok") {
 | ||||
| //     return { tag: "Ok", value: maybeExpression.value as AnyPeggyNode };
 | ||||
| //   } else {
 | ||||
| //     if (
 | ||||
| //       typeof maybeExpression.value !== "object" ||
 | ||||
| //       maybeExpression.value.tag !== "RESyntaxError"
 | ||||
| //     ) {
 | ||||
| //       throw new Error("Expected syntax error");
 | ||||
| //     }
 | ||||
| //     return { tag: "Error", value: maybeExpression.value };
 | ||||
| //   }
 | ||||
| // }
 | ||||
|  |  | |||
|  | @ -1,269 +0,0 @@ | |||
| import * as _ from "lodash"; | ||||
| import type { | ||||
|   expressionValue, | ||||
|   mixedShape, | ||||
|   sampleSetDist, | ||||
|   genericDist, | ||||
|   environment, | ||||
|   symbolicDist, | ||||
|   discreteShape, | ||||
|   continuousShape, | ||||
|   lambdaValue, | ||||
|   lambdaDeclaration, | ||||
|   declarationArg, | ||||
| } from "../rescript/TypescriptInterface.gen"; | ||||
| import { Distribution } from "./distribution"; | ||||
| import { tagged, tag } from "./types"; | ||||
| // This file is here to compensate for genType not fully recursively converting types
 | ||||
| 
 | ||||
| // Raw rescript types.
 | ||||
| export type rescriptExport = | ||||
|   | 0 // EvVoid
 | ||||
|   | { | ||||
|       TAG: 0; // EvArray
 | ||||
|       _0: rescriptExport[]; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 1; // EvString
 | ||||
|       _0: string[]; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 2; // EvBool
 | ||||
|       _0: boolean; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 3; // EvCall
 | ||||
|       _0: string; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 4; // EvDistribution
 | ||||
|       _0: rescriptDist; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 5; // EvLambda
 | ||||
|       _0: lambdaValue; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 6; // EvNumber
 | ||||
|       _0: number; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 7; // EvRecord
 | ||||
|       _0: { [key: string]: rescriptExport }; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 8; // EvString
 | ||||
|       _0: string; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 9; // EvSymbol
 | ||||
|       _0: string; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 10; // EvDate
 | ||||
|       _0: Date; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 11; // EvTimeDuration
 | ||||
|       _0: number; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 12; // EvDeclaration
 | ||||
|       _0: rescriptLambdaDeclaration; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 13; // EvTypeIdentifier
 | ||||
|       _0: string; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 14; // EvModule
 | ||||
|       _0: { [key: string]: rescriptExport }; | ||||
|     }; | ||||
| 
 | ||||
| type rescriptDist = | ||||
|   | { TAG: 0; _0: rescriptPointSetDist } | ||||
|   | { TAG: 1; _0: sampleSetDist } | ||||
|   | { TAG: 2; _0: symbolicDist }; | ||||
| 
 | ||||
| type rescriptPointSetDist = | ||||
|   | { | ||||
|       TAG: 0; // Mixed
 | ||||
|       _0: mixedShape; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 1; // Discrete
 | ||||
|       _0: discreteShape; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 2; // ContinuousShape
 | ||||
|       _0: continuousShape; | ||||
|     }; | ||||
| 
 | ||||
| type rescriptLambdaDeclaration = { | ||||
|   readonly fn: lambdaValue; | ||||
|   readonly args: rescriptDeclarationArg[]; | ||||
| }; | ||||
| 
 | ||||
| type rescriptDeclarationArg = | ||||
|   | { | ||||
|       TAG: 0; // Float
 | ||||
|       min: number; | ||||
|       max: number; | ||||
|     } | ||||
|   | { | ||||
|       TAG: 1; // Date
 | ||||
|       min: Date; | ||||
|       max: Date; | ||||
|     }; | ||||
| 
 | ||||
| export type squiggleExpression = | ||||
|   | tagged<"symbol", string> | ||||
|   | tagged<"string", string> | ||||
|   | tagged<"call", string> | ||||
|   | tagged<"lambda", lambdaValue> | ||||
|   | tagged<"array", squiggleExpression[]> | ||||
|   | tagged<"arraystring", string[]> | ||||
|   | tagged<"boolean", boolean> | ||||
|   | tagged<"distribution", Distribution> | ||||
|   | tagged<"number", number> | ||||
|   | tagged<"date", Date> | ||||
|   | tagged<"timeDuration", number> | ||||
|   | tagged<"lambdaDeclaration", lambdaDeclaration> | ||||
|   | tagged<"record", { [key: string]: squiggleExpression }> | ||||
|   | tagged<"type", { [key: string]: squiggleExpression }> | ||||
|   | tagged<"typeIdentifier", string> | ||||
|   | tagged<"module", { [key: string]: squiggleExpression }> | ||||
|   | tagged<"void", string>; | ||||
| 
 | ||||
| export { lambdaValue }; | ||||
| 
 | ||||
| export function convertRawToTypescript( | ||||
|   result: rescriptExport, | ||||
|   environment: environment | ||||
| ): squiggleExpression { | ||||
|   if (typeof result === "number") { | ||||
|     // EvVoid
 | ||||
|     return tag("void", ""); | ||||
|   } | ||||
|   switch (result.TAG) { | ||||
|     case 0: // EvArray
 | ||||
|       return tag( | ||||
|         "array", | ||||
|         result._0.map((x) => convertRawToTypescript(x, environment)) | ||||
|       ); | ||||
|     case 1: // EvArrayString
 | ||||
|       return tag("arraystring", result._0); | ||||
|     case 2: // EvBool
 | ||||
|       return tag("boolean", result._0); | ||||
|     case 3: // EvCall
 | ||||
|       return tag("call", result._0); | ||||
|     case 4: // EvDistribution
 | ||||
|       return tag( | ||||
|         "distribution", | ||||
|         new Distribution( | ||||
|           convertRawDistributionToGenericDist(result._0), | ||||
|           environment | ||||
|         ) | ||||
|       ); | ||||
|     case 5: // EvDistribution
 | ||||
|       return tag("lambda", result._0); | ||||
|     case 6: // EvNumber
 | ||||
|       return tag("number", result._0); | ||||
|     case 7: // EvRecord
 | ||||
|       return tag( | ||||
|         "record", | ||||
|         _.mapValues(result._0, (x) => convertRawToTypescript(x, environment)) | ||||
|       ); | ||||
|     case 8: // EvString
 | ||||
|       return tag("string", result._0); | ||||
|     case 9: // EvSymbol
 | ||||
|       return tag("symbol", result._0); | ||||
|     case 10: // EvDate
 | ||||
|       return tag("date", result._0); | ||||
|     case 11: // EvTimeDuration
 | ||||
|       return tag("number", result._0); | ||||
|     case 12: // EvDeclaration
 | ||||
|       return tag("lambdaDeclaration", { | ||||
|         fn: result._0.fn, | ||||
|         args: result._0.args.map(convertDeclaration), | ||||
|       }); | ||||
|     case 13: // EvSymbol
 | ||||
|       return tag("typeIdentifier", result._0); | ||||
|     case 14: // EvModule
 | ||||
|       return tag( | ||||
|         "module", | ||||
|         _.mapValues(result._0, (x) => convertRawToTypescript(x, environment)) | ||||
|       ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function convertDeclaration( | ||||
|   declarationArg: rescriptDeclarationArg | ||||
| ): declarationArg { | ||||
|   switch (declarationArg.TAG) { | ||||
|     case 0: // Float
 | ||||
|       return tag("Float", { min: declarationArg.min, max: declarationArg.max }); | ||||
|     case 1: // Date
 | ||||
|       return tag("Date", { min: declarationArg.min, max: declarationArg.max }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function convertRawDistributionToGenericDist( | ||||
|   result: rescriptDist | ||||
| ): genericDist { | ||||
|   switch (result.TAG) { | ||||
|     case 0: // Point Set Dist
 | ||||
|       switch (result._0.TAG) { | ||||
|         case 0: // Mixed
 | ||||
|           return tag("PointSet", tag("Mixed", result._0._0)); | ||||
|         case 1: // Discrete
 | ||||
|           return tag("PointSet", tag("Discrete", result._0._0)); | ||||
|         case 2: // Continuous
 | ||||
|           return tag("PointSet", tag("Continuous", result._0._0)); | ||||
|       } | ||||
|     case 1: // Sample Set Dist
 | ||||
|       return tag("SampleSet", result._0); | ||||
|     case 2: // Symbolic Dist
 | ||||
|       return tag("Symbolic", result._0); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type jsValue = | ||||
|   | string | ||||
|   | number | ||||
|   | jsValue[] | ||||
|   | { [key: string]: jsValue } | ||||
|   | boolean; | ||||
| 
 | ||||
| export function jsValueToBinding(value: jsValue): rescriptExport { | ||||
|   if (typeof value === "boolean") { | ||||
|     return { TAG: 2, _0: value as boolean }; | ||||
|   } else if (typeof value === "string") { | ||||
|     return { TAG: 8, _0: value as string }; | ||||
|   } else if (typeof value === "number") { | ||||
|     return { TAG: 6, _0: value as number }; | ||||
|   } else if (Array.isArray(value)) { | ||||
|     return { TAG: 0, _0: value.map(jsValueToBinding) }; | ||||
|   } else { | ||||
|     // Record
 | ||||
|     return { TAG: 7, _0: _.mapValues(value, jsValueToBinding) }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function jsValueToExpressionValue(value: jsValue): expressionValue { | ||||
|   if (typeof value === "boolean") { | ||||
|     return { tag: "EvBool", value: value as boolean }; | ||||
|   } else if (typeof value === "string") { | ||||
|     return { tag: "EvString", value: value as string }; | ||||
|   } else if (typeof value === "number") { | ||||
|     return { tag: "EvNumber", value: value as number }; | ||||
|   } else if (Array.isArray(value)) { | ||||
|     return { tag: "EvArray", value: value.map(jsValueToExpressionValue) }; | ||||
|   } else { | ||||
|     // Record
 | ||||
|     return { | ||||
|       tag: "EvRecord", | ||||
|       value: _.mapValues(value, jsValueToExpressionValue), | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | @ -1,30 +1,29 @@ | |||
| export type result<a, b> = | ||||
|   | { | ||||
|       tag: "Ok"; | ||||
|       value: a; | ||||
|     } | ||||
|   | { | ||||
|       tag: "Error"; | ||||
|       value: b; | ||||
|     }; | ||||
| import { result } from "../rescript/ForTS/ForTS_Result_tag"; | ||||
| export { result }; | ||||
| 
 | ||||
| export function resultMap<a, b, c>( | ||||
|   r: result<a, c>, | ||||
|   mapFn: (x: a) => b | ||||
| ): result<b, c> { | ||||
|   r: result<a, b>, | ||||
|   mapValue: (x: a) => c | ||||
| ): result<c, b> { | ||||
|   if (r.tag === "Ok") { | ||||
|     return { tag: "Ok", value: mapFn(r.value) }; | ||||
|     return { tag: "Ok", value: mapValue(r.value) }; | ||||
|   } else { | ||||
|     return r; | ||||
|     return { tag: "Error", value: r.value }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function resultMap2<a, b, c, d>( | ||||
|   r: result<a, b>, | ||||
|   mapValue: (x: a) => c, | ||||
|   mapError: (y: b) => d | ||||
| ): result<c, d> { | ||||
|   if (r.tag === "Ok") { | ||||
|     return { tag: "Ok", value: mapValue(r.value) }; | ||||
|   } else { | ||||
|     return { tag: "Error", value: mapError(r.value) }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function Ok<a, b>(x: a): result<a, b> { | ||||
|   return { tag: "Ok", value: x }; | ||||
| } | ||||
| 
 | ||||
| export type tagged<a, b> = { tag: a; value: b }; | ||||
| 
 | ||||
| export function tag<a, b>(x: a, y: b): tagged<a, b> { | ||||
|   return { tag: x, value: y }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| @genType | ||||
| //FIXME accessor methods or not opaque? | ||||
| @genType.opaque | ||||
| type genericDist = | ||||
|   | PointSet(PointSetTypes.pointSetDist) | ||||
|   | SampleSet(SampleSetDist.t) | ||||
|  | @ -6,7 +7,7 @@ type genericDist = | |||
| 
 | ||||
| type asAlgebraicCombinationStrategy = AsDefault | AsSymbolic | AsMonteCarlo | AsConvolution | ||||
| 
 | ||||
| @genType | ||||
| @genType.opaque | ||||
| type error = | ||||
|   | NotYetImplemented | ||||
|   | Unreachable | ||||
|  | @ -27,7 +28,6 @@ module Error = { | |||
| 
 | ||||
|   let fromString = (s: string): t => OtherError(s) | ||||
| 
 | ||||
|   @genType | ||||
|   let toString = (err: error): string => | ||||
|     switch err { | ||||
|     | NotYetImplemented => "Function not yet implemented" | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| //TODO: multimodal, add interface, test somehow, track performance, refactor sampleSet, refactor ASTEvaluator.res. | ||||
| 
 | ||||
| type t = DistributionTypes.genericDist | ||||
| type error = DistributionTypes.error | ||||
| type toPointSetFn = t => result<PointSetTypes.pointSetDist, error> | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ type pointSetDistMonad<'a, 'b, 'c> = | |||
|   | Discrete('b) | ||||
|   | Continuous('c) | ||||
| 
 | ||||
| @genType | ||||
| @genType.opaque | ||||
| type pointSetDist = pointSetDistMonad<mixedShape, discreteShape, continuousShape> | ||||
| 
 | ||||
| module ShapeMonad = { | ||||
|  |  | |||
|  | @ -0,0 +1,88 @@ | |||
| // Genetic Distribution happens to be abstract distribution | ||||
| @genType type distribution = DistributionTypes.genericDist | ||||
| @genType type distributionError = DistributionTypes.error | ||||
| @genType type pointSetDistribution = ForTS_Distribution_PointSetDistribution.pointSetDistribution | ||||
| @genType type sampleSetDistribution = ForTS_Distribution_SampleSetDistribution.sampleSetDistribution | ||||
| @genType type symbolicDistribution = ForTS_Distribution_SymbolicDistribution.symbolicDistribution | ||||
| 
 | ||||
| type environment = ForTS_Distribution_Environment.environment //use | ||||
| 
 | ||||
| @genType | ||||
| let defaultEnvironment: environment = DistributionOperation.defaultEnv | ||||
| 
 | ||||
| @module("./ForTS_Distribution_tag") @scope("distributionTag") | ||||
| external dtPointSet_: string = "PointSet" | ||||
| 
 | ||||
| @module("./ForTS_Distribution_tag") @scope("distributionTag") | ||||
| external dtSampleSet_: string = "SampleSet" | ||||
| 
 | ||||
| @module("./ForTS_Distribution_tag") @scope("distributionTag") | ||||
| external dtSymbolic_: string = "Symbolic" | ||||
| 
 | ||||
| @genType.import("./ForTS_Distribution_tag") | ||||
| type distributionTag | ||||
| 
 | ||||
| external castEnum: string => distributionTag = "%identity" | ||||
| 
 | ||||
| // type genericDist = | ||||
| //   | PointSet(PointSetTypes.pointSetDist) | ||||
| //   | SampleSet(SampleSetDist.t) | ||||
| //   | Symbolic(SymbolicDistTypes.symbolicDist) | ||||
| @genType | ||||
| let getTag = (variant: distribution): distributionTag => | ||||
|   switch variant { | ||||
|   | PointSet(_) => dtPointSet_->castEnum | ||||
|   | SampleSet(_) => dtSampleSet_->castEnum | ||||
|   | Symbolic(_) => dtSymbolic_->castEnum | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getPointSet = (variant: distribution): option<pointSetDistribution> => | ||||
|   switch variant { | ||||
|   | PointSet(dist) => dist->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getSampleSet = (variant: distribution): option<sampleSetDistribution> => | ||||
|   switch variant { | ||||
|   | SampleSet(dist) => dist->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getSymbolic = (variant: distribution): option<symbolicDistribution> => | ||||
|   switch variant { | ||||
|   | Symbolic(dist) => dist->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let mean = DistributionOperation.Constructors.mean | ||||
| 
 | ||||
| @genType | ||||
| let stdev = DistributionOperation.Constructors.stdev | ||||
| @genType | ||||
| let variance = DistributionOperation.Constructors.variance | ||||
| @genType | ||||
| let sample = DistributionOperation.Constructors.sample | ||||
| @genType | ||||
| let cdf = DistributionOperation.Constructors.cdf | ||||
| @genType | ||||
| let inv = DistributionOperation.Constructors.inv | ||||
| @genType | ||||
| let pdf = DistributionOperation.Constructors.pdf | ||||
| @genType | ||||
| let normalize = DistributionOperation.Constructors.normalize | ||||
| 
 | ||||
| @genType | ||||
| let toPointSet = (variant: distribution, env: environment) => | ||||
|   GenericDist.toPointSet( | ||||
|     variant, | ||||
|     ~sampleCount=env.sampleCount, | ||||
|     ~xyPointLength=env.xyPointLength, | ||||
|     (), | ||||
|   ) | ||||
| 
 | ||||
| @genType | ||||
| let toString = (variant: distribution) => GenericDist.toString(variant) | ||||
|  | @ -0,0 +1 @@ | |||
| @genType type environment = GenericDist.env //re-export | ||||
|  | @ -0,0 +1,4 @@ | |||
| @genType type distributionError = DistributionTypes.error | ||||
| 
 | ||||
| @genType | ||||
| let toString = (e: distributionError) => DistributionTypes.Error.toString(e) | ||||
|  | @ -0,0 +1,50 @@ | |||
| @genType type pointSetDistribution = PointSetTypes.pointSetDist | ||||
| @genType type continuousShape = PointSetTypes.continuousShape | ||||
| @genType type discreteShape = PointSetTypes.discreteShape | ||||
| @genType type mixedShape = PointSetTypes.mixedShape | ||||
| 
 | ||||
| @module("./ForTS_Distribution_PointSetDistribution_tag") @scope("pointSetDistributionTag") | ||||
| external pstMixed_: string = "Mixed" | ||||
| 
 | ||||
| @module("./ForTS_Distribution_PointSetDistribution_tag") @scope("pointSetDistributionTag") | ||||
| external pstDiscrete_: string = "Discrete" | ||||
| 
 | ||||
| @module("./ForTS_Distribution_PointSetDistribution_tag") @scope("pointSetDistributionTag") | ||||
| external pstContinuous_: string = "Continuous" | ||||
| 
 | ||||
| @genType.import("./ForTS_Distribution_PointSetDistribution_tag") | ||||
| type pointSetDistributionTag | ||||
| 
 | ||||
| external castEnum: string => pointSetDistributionTag = "%identity" | ||||
| 
 | ||||
| @genType | ||||
| let getTag = (variant: pointSetDistribution): pointSetDistributionTag => | ||||
|   switch variant { | ||||
|   | Mixed(_) => pstMixed_->castEnum | ||||
|   | Discrete(_) => pstDiscrete_->castEnum | ||||
|   | Continuous(_) => pstContinuous_->castEnum | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getMixed = (variant: pointSetDistribution): 'd => | ||||
|   switch variant { | ||||
|   | Mixed(mixed) => mixed->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getDiscrete = (variant: pointSetDistribution): 'd => | ||||
|   switch variant { | ||||
|   | Discrete(discrete) => discrete->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getContinues = (variant: pointSetDistribution): 'd => | ||||
|   switch variant { | ||||
|   | Continuous(continuous) => continuous->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let toDistribution = (v: pointSetDistribution): DistributionTypes.genericDist => PointSet(v) | ||||
|  | @ -0,0 +1,5 @@ | |||
| export enum pointSetDistributionTag { | ||||
|   Mixed = "Mixed", | ||||
|   Discrete = "Discrete", | ||||
|   Continuous = "Continuous", | ||||
| } | ||||
|  | @ -0,0 +1 @@ | |||
| @genType type sampleSetDistribution = SampleSetDist.t | ||||
|  | @ -0,0 +1 @@ | |||
| @genType type symbolicDistribution = SymbolicDistTypes.symbolicDist | ||||
|  | @ -0,0 +1,5 @@ | |||
| export enum distributionTag { | ||||
|   PointSet = "PointSet", | ||||
|   SampleSet = "SampleSet", | ||||
|   Symbolic = "Symbolic", | ||||
| } | ||||
|  | @ -0,0 +1,240 @@ | |||
| @genType type reducerProject = ReducerProject_T.t //re-export | ||||
| 
 | ||||
| type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use | ||||
| 
 | ||||
| type squiggleValue = ForTS_SquiggleValue.squiggleValue //use | ||||
| type squiggleValue_Module = ForTS_SquiggleValue_Module.squiggleValue_Module //use | ||||
| 
 | ||||
| type environment = ForTS_Distribution_Environment.environment //use | ||||
| 
 | ||||
| module T = ReducerProject_T | ||||
| module Private = ReducerProject.Private | ||||
| 
 | ||||
| /* | ||||
|   PUBLIC FUNCTIONS | ||||
| */ | ||||
| 
 | ||||
| /* | ||||
| A project links and runs sources that continue or include each other. | ||||
| 
 | ||||
| Creates a new project to hold the sources, executables, bindings, and other data.  | ||||
| The new project runs the sources according to their topological sorting because of the includes and continues. | ||||
| 
 | ||||
| Any source can include or continue other sources. "Therefore, the project is a graph data structure." | ||||
| The difference between including and continuing is that includes are stated inside the source code while continues are stated in the project. | ||||
| 
 | ||||
| To run a group of source codes and get results/bindings, the necessary methods are | ||||
| - setSource | ||||
| - setContinues | ||||
| - parseIncludes | ||||
| - run or runAll | ||||
| - getExternalBindings | ||||
| - getExternalResult | ||||
| 
 | ||||
| A project has a public field tag with a constant value "reducerProject" | ||||
| project = {tag: "reducerProject"} | ||||
| */ | ||||
| @genType | ||||
| let createProject = (): reducerProject => Private.createProject()->T.Private.castFromInternalProject | ||||
| 
 | ||||
| /* | ||||
| Answer all the source ids of all the sources in the project. | ||||
| */ | ||||
| @genType | ||||
| let getSourceIds = (project: reducerProject): array<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getSourceIds | ||||
| 
 | ||||
| /* | ||||
| Sets the source for a given source Id. | ||||
| */ | ||||
| @genType | ||||
| let setSource = (project: reducerProject, sourceId: string, value: string): unit => | ||||
|   project->T.Private.castToInternalProject->Private.setSource(sourceId, value) | ||||
| 
 | ||||
| /* | ||||
| Gets the source for a given source id. | ||||
| */ | ||||
| @genType | ||||
| let getSource = (project: reducerProject, sourceId: string): option<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getSource(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Touches the source for a given source id. This and dependent, sources are set to be re-evaluated. | ||||
| */ | ||||
| @genType | ||||
| let touchSource = (project: reducerProject, sourceId: string): unit => | ||||
|   project->T.Private.castToInternalProject->Private.touchSource(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Cleans the compilation artifacts for a given source ID. The results stay untouched, so compilation won't be run again. | ||||
| 
 | ||||
| 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 | ||||
| let clean = (project: reducerProject, sourceId: string): unit => | ||||
|   project->T.Private.castToInternalProject->Private.clean(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Cleans all the compilation artifacts in all of the project | ||||
| */ | ||||
| @genType | ||||
| let cleanAll = (project: reducerProject): unit => | ||||
|   project->T.Private.castToInternalProject->Private.cleanAll | ||||
| 
 | ||||
| /* | ||||
| Cleans results. Compilation stays untouched to be able to re-run the source. | ||||
| You would not do this if you were not trying to debug the source code. | ||||
| */ | ||||
| @genType | ||||
| let cleanResults = (project: reducerProject, sourceId: string): unit => | ||||
|   project->T.Private.castToInternalProject->Private.cleanResults(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Cleans all results. Compilations remains untouched to rerun the source. | ||||
| */ | ||||
| @genType | ||||
| let cleanAllResults = (project: reducerProject): unit => | ||||
|   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. | ||||
| */ | ||||
| @genType | ||||
| let getIncludes = (project: reducerProject, sourceId: string): result< | ||||
|   array<string>, | ||||
|   reducerErrorValue, | ||||
| > => project->T.Private.castToInternalProject->Private.getIncludes(sourceId) | ||||
| 
 | ||||
| /* Other sources contributing to the global namespace of this source. */ | ||||
| @genType | ||||
| let getPastChain = (project: reducerProject, sourceId: string): array<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getPastChain(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Answers the source codes after which this source code is continuing | ||||
| */ | ||||
| @genType | ||||
| let getContinues = (project: reducerProject, sourceId: string): array<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getContinues(sourceId) | ||||
| 
 | ||||
| /* | ||||
|  "continues" acts like hidden includes in the source.  | ||||
|  It is used to define a continuation that is not visible in the source code.  | ||||
|  You can chain source codes on the web interface for example | ||||
| */ | ||||
| @genType | ||||
| let setContinues = (project: reducerProject, sourceId: string, continues: array<string>): unit => | ||||
|   project->T.Private.castToInternalProject->Private.setContinues(sourceId, continues) | ||||
| 
 | ||||
| /* | ||||
| This source depends on the array of sources returned. | ||||
| */ | ||||
| @genType | ||||
| let getDependencies = (project: reducerProject, sourceId: string): array<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getDependencies(sourceId) | ||||
| 
 | ||||
| /* | ||||
| The sources returned are dependent on this | ||||
| */ | ||||
| @genType | ||||
| let getDependents = (project: reducerProject, sourceId: string): array<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getDependents(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Get the run order for the sources in the project. | ||||
| */ | ||||
| @genType | ||||
| let getRunOrder = (project: reducerProject): array<string> => | ||||
|   project->T.Private.castToInternalProject->Private.getRunOrder | ||||
| 
 | ||||
| /* | ||||
| Get the run order to get the results of this specific source | ||||
| */ | ||||
| @genType | ||||
| let getRunOrderFor = (project: reducerProject, sourceId: string) => | ||||
|   project->T.Private.castToInternalProject->Private.getRunOrderFor(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Parse includes so that you can load them before running.  | ||||
| Load includes by calling getIncludes which returns the includes that have been parsed.  | ||||
| It is your responsibility to load the includes before running. | ||||
| */ | ||||
| 
 | ||||
| @genType | ||||
| let parseIncludes = (project: reducerProject, sourceId: string): unit => | ||||
|   project->T.Private.castToInternalProject->Private.parseIncludes(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Parse the source code if it is not done already.  | ||||
| Use getRawParse to get the parse tree.  | ||||
| You would need this function if you want to see the parse tree without running the source code. | ||||
| */ | ||||
| @genType | ||||
| let rawParse = (project: reducerProject, sourceId: string): unit => | ||||
|   project->T.Private.castToInternalProject->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. | ||||
| */ | ||||
| @genType | ||||
| let run = (project: reducerProject, sourceId: string): unit => | ||||
|   project->T.Private.castToInternalProject->Private.run(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Runs all of the sources in a project. Their results and bindings will be available | ||||
| */ | ||||
| @genType | ||||
| let runAll = (project: reducerProject): unit => | ||||
|   project->T.Private.castToInternalProject->Private.runAll | ||||
| 
 | ||||
| /* | ||||
| Get the bindings after running this source fil. The bindings are local to the source | ||||
| */ | ||||
| @genType | ||||
| let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Module => | ||||
|   project->T.Private.castToInternalProject->Private.getBindings(sourceId) | ||||
| 
 | ||||
| /* | ||||
| Get the result after running this source file or the project | ||||
| */ | ||||
| @genType | ||||
| let getResult = (project: reducerProject, sourceId: string): result< | ||||
|   squiggleValue, | ||||
|   reducerErrorValue, | ||||
| > => project->T.Private.castToInternalProject->Private.getResult(sourceId) | ||||
| 
 | ||||
| /* | ||||
| This is a convenience function to get the result of a single source without creating a project.  | ||||
| However, without a project, you cannot handle include directives. | ||||
| The source has to be include free | ||||
| */ | ||||
| @genType | ||||
| let evaluate = (sourceCode: string): ( | ||||
|   result<squiggleValue, reducerErrorValue>, | ||||
|   squiggleValue_Module, | ||||
| ) => Private.evaluate(sourceCode) | ||||
| 
 | ||||
| @genType | ||||
| let setEnvironment = (project: reducerProject, environment: environment): unit => | ||||
|   project->T.Private.castToInternalProject->Private.setEnvironment(environment) | ||||
| 
 | ||||
| /* | ||||
| Foreign function interface is intentionally demolished. | ||||
| There is another way to do that: Umur. | ||||
| Also there is no more conversion from javascript to squiggle values currently. | ||||
| If the conversion to the new project is too difficult, I can add it later. | ||||
| */ | ||||
| 
 | ||||
| // let foreignFunctionInterface = ( | ||||
| //   lambdaValue: squiggleValue_Lambda, | ||||
| //   argArray: array<squiggleValue>, | ||||
| //   environment: environment, | ||||
| // ): result<squiggleValue, reducerErrorValue> => { | ||||
| //   let accessors = ReducerProject_ProjectAccessors_T.identityAccessorsWithEnvironment(environment) | ||||
| //   Reducer_Expression_Lambda.foreignFunctionInterface( | ||||
| //     lambdaValue, | ||||
| //     argArray, | ||||
| //     accessors, | ||||
| //     Reducer_Expression.reduceExpressionInProject, | ||||
| //   ) | ||||
| // } | ||||
|  | @ -0,0 +1,18 @@ | |||
| @genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias | ||||
| @genType type syntaxErrorLocation = Reducer_ErrorValue.syntaxErrorLocation //alias | ||||
| 
 | ||||
| @genType | ||||
| let toString = (e: reducerErrorValue): string => Reducer_ErrorValue.errorToString(e) | ||||
| 
 | ||||
| @genType | ||||
| let getLocation = (e: reducerErrorValue): option<syntaxErrorLocation> => | ||||
|   switch e { | ||||
|   | RESyntaxError(_, optionalLocation) => optionalLocation | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let createTodoError = (v: string) => Reducer_ErrorValue.RETodo(v) | ||||
| 
 | ||||
| @genType | ||||
| let createOtherError = (v: string) => Reducer_ErrorValue.REOther(v) | ||||
|  | @ -0,0 +1,9 @@ | |||
| export type result<a, b> = | ||||
|   | { | ||||
|       tag: "Ok"; | ||||
|       value: a; | ||||
|     } | ||||
|   | { | ||||
|       tag: "Error"; | ||||
|       value: b; | ||||
|     }; | ||||
|  | @ -0,0 +1,213 @@ | |||
| @genType type squiggleValue = ReducerInterface_InternalExpressionValue.t //re-export | ||||
| type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use | ||||
| 
 | ||||
| @genType type squiggleValue_Array = ReducerInterface_InternalExpressionValue.squiggleArray //re-export recursive type | ||||
| @genType type squiggleValue_Module = ReducerInterface_InternalExpressionValue.nameSpace //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_Distribution = ForTS_SquiggleValue_Distribution.squiggleValue_Distribution //use | ||||
| type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //use | ||||
| 
 | ||||
| // Return values are kept as they are if they are JavaScript types. | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtArray_: string = "Array" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtArrayString_: string = "ArrayString" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtBool_: string = "Bool" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtCall_: string = "Call" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtDate_: string = "Date" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtDeclaration_: string = "Declaration" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtDistribution_: string = "Distribution" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtLambda_: string = "Lambda" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtModule_: string = "Module" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtNumber_: string = "Number" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtRecord_: string = "Record" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtString_: string = "String" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| external svtSymbol_: string = "Symbol" | ||||
| 
 | ||||
| @module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag") | ||||
| 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") | ||||
| external svtVoid_: string = "Void" | ||||
| 
 | ||||
| @genType.import("./ForTS_SquiggleValue_tag") | ||||
| type squiggleValueTag | ||||
| 
 | ||||
| external castEnum: string => squiggleValueTag = "%identity" | ||||
| 
 | ||||
| @genType | ||||
| let getTag = (variant: squiggleValue): squiggleValueTag => | ||||
|   switch variant { | ||||
|   | IEvArray(_) => svtArray_->castEnum | ||||
|   | IEvArrayString(_) => svtArrayString_->castEnum | ||||
|   | IEvBool(_) => svtBool_->castEnum | ||||
|   | IEvCall(_) => svtCall_->castEnum //Impossible | ||||
|   | IEvDate(_) => svtDate_->castEnum | ||||
|   | IEvDeclaration(_) => svtDeclaration_->castEnum | ||||
|   | IEvDistribution(_) => svtDistribution_->castEnum | ||||
|   | IEvLambda(_) => svtLambda_->castEnum | ||||
|   | IEvBindings(_) => svtModule_->castEnum //Impossible | ||||
|   | IEvNumber(_) => svtNumber_->castEnum | ||||
|   | IEvRecord(_) => svtRecord_->castEnum | ||||
|   | IEvString(_) => svtString_->castEnum | ||||
|   | IEvSymbol(_) => svtSymbol_->castEnum | ||||
|   | IEvTimeDuration(_) => svtTimeDuration_->castEnum | ||||
|   | IEvType(_) => svtType_->castEnum | ||||
|   | IEvTypeIdentifier(_) => svtTypeIdentifier_->castEnum | ||||
|   | IEvVoid => svtVoid_->castEnum | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let toString = (variant: squiggleValue) => | ||||
|   ReducerInterface_InternalExpressionValue.toString(variant) | ||||
| 
 | ||||
| // This is a useful method for unit tests. | ||||
| // Convert the result along with the error message to a string. | ||||
| @genType | ||||
| let toStringResult = (variantResult: result<squiggleValue, reducerErrorValue>) => | ||||
|   ReducerInterface_InternalExpressionValue.toStringResult(variantResult) | ||||
| 
 | ||||
| @genType | ||||
| let getArray = (variant: squiggleValue): option<squiggleValue_Array> => | ||||
|   //FIXME: Convert | ||||
|   switch variant { | ||||
|   | IEvArray(arrayLike) => arrayLike->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getArrayString = (variant: squiggleValue): option<array<string>> => | ||||
|   switch variant { | ||||
|   | IEvArrayString(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getBool = (variant: squiggleValue): option<bool> => | ||||
|   switch variant { | ||||
|   | IEvBool(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getCall = (variant: squiggleValue): option<string> => | ||||
|   switch variant { | ||||
|   | IEvCall(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getDate = (variant: squiggleValue): option<Js.Date.t> => | ||||
|   switch variant { | ||||
|   | IEvDate(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getDeclaration = (variant: squiggleValue): option<squiggleValue_Declaration> => | ||||
|   switch variant { | ||||
|   | IEvDeclaration(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getDistribution = (variant: squiggleValue): option<squiggleValue_Distribution> => | ||||
|   switch variant { | ||||
|   | IEvDistribution(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getLambda = (variant: squiggleValue): option<squiggleValue_Lambda> => | ||||
|   switch variant { | ||||
|   | IEvLambda(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getModule = (variant: squiggleValue): option<squiggleValue_Module> => | ||||
|   switch variant { | ||||
|   | IEvBindings(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getNumber = (variant: squiggleValue): option<float> => | ||||
|   switch variant { | ||||
|   | IEvNumber(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getRecord = (variant: squiggleValue): option<squiggleValue_Record> => | ||||
|   switch variant { | ||||
|   | IEvRecord(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getString = (variant: squiggleValue): option<string> => | ||||
|   switch variant { | ||||
|   | IEvString(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getSymbol = (variant: squiggleValue): option<string> => | ||||
|   switch variant { | ||||
|   | IEvSymbol(value) => value->Some | ||||
|   | _ => None | ||||
|   } | ||||
| 
 | ||||
| @genType | ||||
| let getTimeDuration = (variant: squiggleValue): option<float> => | ||||
|   switch variant { | ||||
|   | IEvTimeDuration(value) => value->Some | ||||
|   | _ => 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 | ||||
|   } | ||||
|  | @ -0,0 +1,10 @@ | |||
| type squiggleValue = ForTS_SquiggleValue.squiggleValue | ||||
| @genType type squiggleValue_Array = ForTS_SquiggleValue.squiggleValue_Array //re-export recursive type | ||||
| 
 | ||||
| @genType | ||||
| let getValues = (v: squiggleValue_Array): array<squiggleValue> => | ||||
|   ReducerInterface_InternalExpressionValue.arrayToValueArray(v) | ||||
| 
 | ||||
| @genType | ||||
| let toString = (v: squiggleValue_Array): string => | ||||
|   ReducerInterface_InternalExpressionValue.toStringArray(v) | ||||
|  | @ -0,0 +1,5 @@ | |||
| @genType type squiggleValue_Declaration = ReducerInterface_InternalExpressionValue.lambdaDeclaration //re-export | ||||
| 
 | ||||
| @genType | ||||
| let toString = (v: squiggleValue_Declaration): string => | ||||
|   ReducerInterface_InternalExpressionValue.toStringDeclaration(v) | ||||
|  | @ -0,0 +1,5 @@ | |||
| @genType type squiggleValue_Distribution = ForTS_Distribution.distribution | ||||
| 
 | ||||
| @genType | ||||
| let toString = (v: squiggleValue_Distribution): string => | ||||
|   ReducerInterface_InternalExpressionValue.toStringDistribution(v) | ||||
|  | @ -0,0 +1,10 @@ | |||
| @genType type squiggleValue_Lambda = ReducerInterface_InternalExpressionValue.lambdaValue //re-export | ||||
| 
 | ||||
| @genType | ||||
| let toString = (v: squiggleValue_Lambda): string => | ||||
|   ReducerInterface_InternalExpressionValue.toStringFunction(v) | ||||
| 
 | ||||
| @genType | ||||
| let parameters = (v: squiggleValue_Lambda): array<string> => { | ||||
|   v.parameters | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user