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", |   "name": "@quri/squiggle-components", | ||||||
|   "version": "0.3.2", |   "version": "0.4.0-alpha.1", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@floating-ui/react-dom": "^1.0.0", |     "@floating-ui/react-dom": "^1.0.0", | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
|     "@headlessui/react": "^1.6.6", |     "@headlessui/react": "^1.6.6", | ||||||
|     "@heroicons/react": "^1.0.6", |     "@heroicons/react": "^1.0.6", | ||||||
|     "@hookform/resolvers": "^2.9.7", |     "@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", |     "@react-hook/size": "^2.1.2", | ||||||
|     "clsx": "^1.2.1", |     "clsx": "^1.2.1", | ||||||
|     "framer-motion": "^7.2.1", |     "framer-motion": "^7.2.1", | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| import * as React from "react"; | import * as React from "react"; | ||||||
| import { | import { | ||||||
|   Distribution, |   SqDistribution, | ||||||
|   result, |   result, | ||||||
|   distributionError, |   SqDistributionError, | ||||||
|   distributionErrorToString, |  | ||||||
|   squiggleExpression, |  | ||||||
|   resultMap, |   resultMap, | ||||||
|  |   SqRecord, | ||||||
|  |   environment, | ||||||
| } from "@quri/squiggle-lang"; | } from "@quri/squiggle-lang"; | ||||||
| import { Vega } from "react-vega"; | import { Vega } from "react-vega"; | ||||||
| import { ErrorAlert } from "./Alert"; | import { ErrorAlert } from "./Alert"; | ||||||
|  | @ -28,17 +28,16 @@ export type DistributionPlottingSettings = { | ||||||
| 
 | 
 | ||||||
| export type DistributionChartProps = { | export type DistributionChartProps = { | ||||||
|   plot: Plot; |   plot: Plot; | ||||||
|  |   environment: environment; | ||||||
|   width?: number; |   width?: number; | ||||||
|   height: number; |   height: number; | ||||||
| } & DistributionPlottingSettings; | } & DistributionPlottingSettings; | ||||||
| 
 | 
 | ||||||
| export function defaultPlot(distribution: Distribution): Plot { | export function defaultPlot(distribution: SqDistribution): Plot { | ||||||
|   return { distributions: [{ name: "default", distribution }] }; |   return { distributions: [{ name: "default", distribution }] }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function makePlot(record: { | export function makePlot(record: SqRecord): Plot | void { | ||||||
|   [key: string]: squiggleExpression; |  | ||||||
| }): Plot | void { |  | ||||||
|   const plotResult = parsePlot(record); |   const plotResult = parsePlot(record); | ||||||
|   if (plotResult.tag === "Ok") { |   if (plotResult.tag === "Ok") { | ||||||
|     return plotResult.value; |     return plotResult.value; | ||||||
|  | @ -46,22 +45,29 @@ export function makePlot(record: { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | 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) => { |   const [sized] = useSize((size) => { | ||||||
|     let shapes = flattenResult( |     const shapes = flattenResult( | ||||||
|       plot.distributions.map((x) => |       plot.distributions.map((x) => { | ||||||
|         resultMap(x.distribution.pointSet(), (shape) => ({ |         return resultMap(x.distribution.pointSet(environment), (pointSet) => ({ | ||||||
|  |           ...pointSet.asShape(), | ||||||
|           name: x.name, |           name: x.name, | ||||||
|           // color: x.color, // not supported yet
 |           // color: x.color, // not supported yet
 | ||||||
|           continuous: shape.continuous, |         })); | ||||||
|           discrete: shape.discrete, |       }) | ||||||
|         })) |  | ||||||
|       ) |  | ||||||
|     ); |     ); | ||||||
|     if (shapes.tag === "Error") { |     if (shapes.tag === "Error") { | ||||||
|       return ( |       return ( | ||||||
|         <ErrorAlert heading="Distribution Error"> |         <ErrorAlert heading="Distribution Error"> | ||||||
|           {distributionErrorToString(shapes.value)} |           {shapes.value.toString()} | ||||||
|         </ErrorAlert> |         </ErrorAlert> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  | @ -96,7 +102,10 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | ||||||
|         )} |         )} | ||||||
|         <div className="flex justify-center"> |         <div className="flex justify-center"> | ||||||
|           {showSummary && plot.distributions.length === 1 && ( |           {showSummary && plot.distributions.length === 1 && ( | ||||||
|             <SummaryTable distribution={plot.distributions[0].distribution} /> |             <SummaryTable | ||||||
|  |               distribution={plot.distributions[0].distribution} | ||||||
|  |               environment={environment} | ||||||
|  |             /> | ||||||
|           )} |           )} | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  | @ -120,32 +129,36 @@ const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => ( | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| type SummaryTableProps = { | type SummaryTableProps = { | ||||||
|   distribution: Distribution; |   distribution: SqDistribution; | ||||||
|  |   environment: environment; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => { | const SummaryTable: React.FC<SummaryTableProps> = ({ | ||||||
|   const mean = distribution.mean(); |   distribution, | ||||||
|   const stdev = distribution.stdev(); |   environment, | ||||||
|   const p5 = distribution.inv(0.05); | }) => { | ||||||
|   const p10 = distribution.inv(0.1); |   const mean = distribution.mean(environment); | ||||||
|   const p25 = distribution.inv(0.25); |   const stdev = distribution.stdev(environment); | ||||||
|   const p50 = distribution.inv(0.5); |   const p5 = distribution.inv(environment, 0.05); | ||||||
|   const p75 = distribution.inv(0.75); |   const p10 = distribution.inv(environment, 0.1); | ||||||
|   const p90 = distribution.inv(0.9); |   const p25 = distribution.inv(environment, 0.25); | ||||||
|   const p95 = distribution.inv(0.95); |   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"; |     x.tag === "Ok"; | ||||||
| 
 | 
 | ||||||
|   const unwrapResult = ( |   const unwrapResult = ( | ||||||
|     x: result<number, distributionError> |     x: result<number, SqDistributionError> | ||||||
|   ): React.ReactNode => { |   ): React.ReactNode => { | ||||||
|     if (x.tag === "Ok") { |     if (x.tag === "Ok") { | ||||||
|       return <NumberShower number={x.value} />; |       return <NumberShower number={x.value} />; | ||||||
|     } else { |     } else { | ||||||
|       return ( |       return ( | ||||||
|         <ErrorAlert heading="Distribution Error"> |         <ErrorAlert heading="Distribution Error"> | ||||||
|           {distributionErrorToString(x.value)} |           {x.value.toString()} | ||||||
|         </ErrorAlert> |         </ErrorAlert> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,5 @@ | ||||||
| import * as React from "react"; | import * as React from "react"; | ||||||
| import { | import { SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; | ||||||
|   lambdaValue, |  | ||||||
|   environment, |  | ||||||
|   runForeign, |  | ||||||
|   errorValueToString, |  | ||||||
| } from "@quri/squiggle-lang"; |  | ||||||
| import { FunctionChart1Dist } from "./FunctionChart1Dist"; | import { FunctionChart1Dist } from "./FunctionChart1Dist"; | ||||||
| import { FunctionChart1Number } from "./FunctionChart1Number"; | import { FunctionChart1Number } from "./FunctionChart1Number"; | ||||||
| import { DistributionPlottingSettings } from "./DistributionChart"; | import { DistributionPlottingSettings } from "./DistributionChart"; | ||||||
|  | @ -17,7 +12,7 @@ export type FunctionChartSettings = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface FunctionChartProps { | interface FunctionChartProps { | ||||||
|   fn: lambdaValue; |   fn: SqLambda; | ||||||
|   chartSettings: FunctionChartSettings; |   chartSettings: FunctionChartSettings; | ||||||
|   distributionPlotSettings: DistributionPlottingSettings; |   distributionPlotSettings: DistributionPlottingSettings; | ||||||
|   environment: environment; |   environment: environment; | ||||||
|  | @ -38,8 +33,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | ||||||
|       </MessageAlert> |       </MessageAlert> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   const result1 = runForeign(fn, [chartSettings.start], environment); |   const result1 = fn.call([chartSettings.start]); | ||||||
|   const result2 = runForeign(fn, [chartSettings.stop], environment); |   const result2 = fn.call([chartSettings.stop]); | ||||||
|   const getValidResult = () => { |   const getValidResult = () => { | ||||||
|     if (result1.tag === "Ok") { |     if (result1.tag === "Ok") { | ||||||
|       return result1; |       return result1; | ||||||
|  | @ -53,14 +48,12 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | ||||||
| 
 | 
 | ||||||
|   if (validResult.tag === "Error") { |   if (validResult.tag === "Error") { | ||||||
|     return ( |     return ( | ||||||
|       <ErrorAlert heading="Error"> |       <ErrorAlert heading="Error">{validResult.value.toString()}</ErrorAlert> | ||||||
|         {errorValueToString(validResult.value)} |  | ||||||
|       </ErrorAlert> |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   switch (validResult.value.tag) { |   switch (validResult.value.tag) { | ||||||
|     case "distribution": |     case SqValueTag.Distribution: | ||||||
|       return ( |       return ( | ||||||
|         <FunctionChart1Dist |         <FunctionChart1Dist | ||||||
|           fn={fn} |           fn={fn} | ||||||
|  | @ -70,7 +63,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | ||||||
|           distributionPlotSettings={distributionPlotSettings} |           distributionPlotSettings={distributionPlotSettings} | ||||||
|         /> |         /> | ||||||
|       ); |       ); | ||||||
|     case "number": |     case SqValueTag.Number: | ||||||
|       return ( |       return ( | ||||||
|         <FunctionChart1Number |         <FunctionChart1Number | ||||||
|           fn={fn} |           fn={fn} | ||||||
|  |  | ||||||
|  | @ -2,14 +2,13 @@ import * as React from "react"; | ||||||
| import _ from "lodash"; | import _ from "lodash"; | ||||||
| import type { Spec } from "vega"; | import type { Spec } from "vega"; | ||||||
| import { | import { | ||||||
|   Distribution, |   SqDistribution, | ||||||
|   result, |   result, | ||||||
|   lambdaValue, |   SqLambda, | ||||||
|   environment, |   environment, | ||||||
|   runForeign, |   SqError, | ||||||
|   squiggleExpression, |   SqValue, | ||||||
|   errorValue, |   SqValueTag, | ||||||
|   errorValueToString, |  | ||||||
| } from "@quri/squiggle-lang"; | } from "@quri/squiggle-lang"; | ||||||
| import { createClassFromSpec } from "react-vega"; | import { createClassFromSpec } from "react-vega"; | ||||||
| import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; | import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; | ||||||
|  | @ -46,7 +45,7 @@ export type FunctionChartSettings = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface FunctionChart1DistProps { | interface FunctionChart1DistProps { | ||||||
|   fn: lambdaValue; |   fn: SqLambda; | ||||||
|   chartSettings: FunctionChartSettings; |   chartSettings: FunctionChartSettings; | ||||||
|   distributionPlotSettings: DistributionPlottingSettings; |   distributionPlotSettings: DistributionPlottingSettings; | ||||||
|   environment: environment; |   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( |   let chartPointsToRender = _rangeByCount( | ||||||
|     chartSettings.start, |     chartSettings.start, | ||||||
|     chartSettings.stop, |     chartSettings.stop, | ||||||
|  | @ -87,9 +94,9 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   let chartPointsData: point[] = chartPointsToRender.map((x) => { |   let chartPointsData: point[] = chartPointsToRender.map((x) => { | ||||||
|     let result = runForeign(fn, [x], environment); |     let result = fn.call([x]); | ||||||
|     if (result.tag === "Ok") { |     if (result.tag === "Ok") { | ||||||
|       if (result.value.tag === "distribution") { |       if (result.value.tag === SqValueTag.Distribution) { | ||||||
|         return { x, value: { tag: "Ok", value: result.value.value } }; |         return { x, value: { tag: "Ok", value: result.value.value } }; | ||||||
|       } else { |       } else { | ||||||
|         return { |         return { | ||||||
|  | @ -104,13 +111,13 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { | ||||||
|     } else { |     } else { | ||||||
|       return { |       return { | ||||||
|         x, |         x, | ||||||
|         value: { tag: "Error", value: errorValueToString(result.value) }, |         value: { tag: "Error", value: result.value.toString() }, | ||||||
|       }; |       }; | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   let initialPartition: [ |   let initialPartition: [ | ||||||
|     { x: number; value: Distribution }[], |     { x: number; value: SqDistribution }[], | ||||||
|     { x: number; value: string }[] |     { x: number; value: string }[] | ||||||
|   ] = [[], []]; |   ] = [[], []]; | ||||||
| 
 | 
 | ||||||
|  | @ -126,26 +133,23 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { | ||||||
|   let groupedErrors: errors = _.groupBy(errors, (x) => x.value); |   let groupedErrors: errors = _.groupBy(errors, (x) => x.value); | ||||||
| 
 | 
 | ||||||
|   let percentiles: percentiles = functionImage.map(({ 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
 |     const res = { | ||||||
|     // distribution, it doesn't internally convert it to a pointSet distribution for every
 |  | ||||||
|     // single inv() call.
 |  | ||||||
|     let toPointSet: Distribution = unwrap(value.toPointSet()); |  | ||||||
|     return { |  | ||||||
|       x: x, |       x: x, | ||||||
|       p1: unwrap(toPointSet.inv(0.01)), |       p1: unwrap(value.inv(environment, 0.01)), | ||||||
|       p5: unwrap(toPointSet.inv(0.05)), |       p5: unwrap(value.inv(environment, 0.05)), | ||||||
|       p10: unwrap(toPointSet.inv(0.1)), |       p10: unwrap(value.inv(environment, 0.1)), | ||||||
|       p20: unwrap(toPointSet.inv(0.2)), |       p20: unwrap(value.inv(environment, 0.2)), | ||||||
|       p30: unwrap(toPointSet.inv(0.3)), |       p30: unwrap(value.inv(environment, 0.3)), | ||||||
|       p40: unwrap(toPointSet.inv(0.4)), |       p40: unwrap(value.inv(environment, 0.4)), | ||||||
|       p50: unwrap(toPointSet.inv(0.5)), |       p50: unwrap(value.inv(environment, 0.5)), | ||||||
|       p60: unwrap(toPointSet.inv(0.6)), |       p60: unwrap(value.inv(environment, 0.6)), | ||||||
|       p70: unwrap(toPointSet.inv(0.7)), |       p70: unwrap(value.inv(environment, 0.7)), | ||||||
|       p80: unwrap(toPointSet.inv(0.8)), |       p80: unwrap(value.inv(environment, 0.8)), | ||||||
|       p90: unwrap(toPointSet.inv(0.9)), |       p90: unwrap(value.inv(environment, 0.9)), | ||||||
|       p95: unwrap(toPointSet.inv(0.95)), |       p95: unwrap(value.inv(environment, 0.95)), | ||||||
|       p99: unwrap(toPointSet.inv(0.99)), |       p99: unwrap(value.inv(environment, 0.99)), | ||||||
|     }; |     }; | ||||||
|  |     return res; | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   return { percentiles, errors: groupedErrors }; |   return { percentiles, errors: groupedErrors }; | ||||||
|  | @ -168,19 +172,20 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({ | ||||||
|   const signalListeners = { mousemove: handleHover, mouseout: handleOut }; |   const signalListeners = { mousemove: handleHover, mouseout: handleOut }; | ||||||
| 
 | 
 | ||||||
|   //TODO: This custom error handling is a bit hacky and should be improved.
 |   //TODO: This custom error handling is a bit hacky and should be improved.
 | ||||||
|   let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay |   let mouseItem: result<SqValue, SqError> = !!mouseOverlay | ||||||
|     ? runForeign(fn, [mouseOverlay], environment) |     ? fn.call([mouseOverlay]) | ||||||
|     : { |     : { | ||||||
|         tag: "Error", |         tag: "Error", | ||||||
|         value: { |         value: SqError.createOtherError( | ||||||
|           tag: "RETodo", |           "Hover x-coordinate returned NaN. Expected a number." | ||||||
|           value: "Hover x-coordinate returned NaN. Expected a number.", |         ), | ||||||
|         }, |  | ||||||
|       }; |       }; | ||||||
|   let showChart = |   let showChart = | ||||||
|     mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? ( |     mouseItem.tag === "Ok" && | ||||||
|  |     mouseItem.value.tag === SqValueTag.Distribution ? ( | ||||||
|       <DistributionChart |       <DistributionChart | ||||||
|         plot={defaultPlot(mouseItem.value.value)} |         plot={defaultPlot(mouseItem.value.value)} | ||||||
|  |         environment={environment} | ||||||
|         width={400} |         width={400} | ||||||
|         height={50} |         height={50} | ||||||
|         {...distributionPlotSettings} |         {...distributionPlotSettings} | ||||||
|  |  | ||||||
|  | @ -1,16 +1,11 @@ | ||||||
| import * as React from "react"; | import * as React from "react"; | ||||||
| import _ from "lodash"; | import _ from "lodash"; | ||||||
| import type { Spec } from "vega"; | import type { Spec } from "vega"; | ||||||
| import { | import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; | ||||||
|   result, |  | ||||||
|   lambdaValue, |  | ||||||
|   environment, |  | ||||||
|   runForeign, |  | ||||||
|   errorValueToString, |  | ||||||
| } from "@quri/squiggle-lang"; |  | ||||||
| import { createClassFromSpec } from "react-vega"; | import { createClassFromSpec } from "react-vega"; | ||||||
| import * as lineChartSpec from "../vega-specs/spec-line-chart.json"; | import * as lineChartSpec from "../vega-specs/spec-line-chart.json"; | ||||||
| import { ErrorAlert } from "./Alert"; | import { ErrorAlert } from "./Alert"; | ||||||
|  | import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag"; | ||||||
| 
 | 
 | ||||||
| let SquiggleLineChart = createClassFromSpec({ | let SquiggleLineChart = createClassFromSpec({ | ||||||
|   spec: lineChartSpec as Spec, |   spec: lineChartSpec as Spec, | ||||||
|  | @ -30,7 +25,7 @@ export type FunctionChartSettings = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface FunctionChart1NumberProps { | interface FunctionChart1NumberProps { | ||||||
|   fn: lambdaValue; |   fn: SqLambda; | ||||||
|   chartSettings: FunctionChartSettings; |   chartSettings: FunctionChartSettings; | ||||||
|   environment: environment; |   environment: environment; | ||||||
|   height: number; |   height: number; | ||||||
|  | @ -38,7 +33,15 @@ interface FunctionChart1NumberProps { | ||||||
| 
 | 
 | ||||||
| type point = { x: number; value: result<number, string> }; | 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( |   let chartPointsToRender = _rangeByCount( | ||||||
|     chartSettings.start, |     chartSettings.start, | ||||||
|     chartSettings.stop, |     chartSettings.stop, | ||||||
|  | @ -46,9 +49,9 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => { | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   let chartPointsData: point[] = chartPointsToRender.map((x) => { |   let chartPointsData: point[] = chartPointsToRender.map((x) => { | ||||||
|     let result = runForeign(fn, [x], environment); |     let result = fn.call([x]); | ||||||
|     if (result.tag === "Ok") { |     if (result.tag === "Ok") { | ||||||
|       if (result.value.tag == "number") { |       if (result.value.tag === SqValueTag.Number) { | ||||||
|         return { x, value: { tag: "Ok", value: result.value.value } }; |         return { x, value: { tag: "Ok", value: result.value.value } }; | ||||||
|       } else { |       } else { | ||||||
|         return { |         return { | ||||||
|  | @ -62,7 +65,7 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => { | ||||||
|     } else { |     } else { | ||||||
|       return { |       return { | ||||||
|         x, |         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 * as React from "react"; | ||||||
| import { | import { | ||||||
|   squiggleExpression, |   SqValue, | ||||||
|   bindings, |  | ||||||
|   environment, |   environment, | ||||||
|   jsImports, |  | ||||||
|   defaultImports, |  | ||||||
|   defaultBindings, |  | ||||||
|   defaultEnvironment, |   defaultEnvironment, | ||||||
|  |   resultMap, | ||||||
|  |   SqValueTag, | ||||||
| } from "@quri/squiggle-lang"; | } from "@quri/squiggle-lang"; | ||||||
| import { useSquiggle } from "../lib/hooks"; | import { useSquiggle } from "../lib/hooks"; | ||||||
| import { SquiggleViewer } from "./SquiggleViewer"; | import { SquiggleViewer } from "./SquiggleViewer"; | ||||||
|  | import { JsImports } from "../lib/jsImports"; | ||||||
| 
 | 
 | ||||||
| export interface SquiggleChartProps { | export interface SquiggleChartProps { | ||||||
|   /** The input string for squiggle */ |   /** The input string for squiggle */ | ||||||
|  | @ -27,14 +26,12 @@ export interface SquiggleChartProps { | ||||||
|   /** If the result is a function, the amount of stops sampled */ |   /** If the result is a function, the amount of stops sampled */ | ||||||
|   diagramCount?: number; |   diagramCount?: number; | ||||||
|   /** When the squiggle code gets reevaluated */ |   /** When the squiggle code gets reevaluated */ | ||||||
|   onChange?(expr: squiggleExpression | undefined): void; |   onChange?(expr: SqValue | undefined): void; | ||||||
|   /** CSS width of the element */ |   /** CSS width of the element */ | ||||||
|   width?: number; |   width?: number; | ||||||
|   height?: number; |   height?: number; | ||||||
|   /** Bindings of previous variables declared */ |  | ||||||
|   bindings?: bindings; |  | ||||||
|   /** JS imported parameters */ |   /** JS imported parameters */ | ||||||
|   jsImports?: jsImports; |   jsImports?: JsImports; | ||||||
|   /** Whether to show a summary of the distribution */ |   /** Whether to show a summary of the distribution */ | ||||||
|   showSummary?: boolean; |   showSummary?: boolean; | ||||||
|   /** Set the x scale to be logarithmic by deault */ |   /** Set the x scale to be logarithmic by deault */ | ||||||
|  | @ -57,6 +54,7 @@ export interface SquiggleChartProps { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const defaultOnChange = () => {}; | const defaultOnChange = () => {}; | ||||||
|  | const defaultImports: JsImports = {}; | ||||||
| 
 | 
 | ||||||
| export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||||
|   ({ |   ({ | ||||||
|  | @ -65,7 +63,6 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||||
|     environment, |     environment, | ||||||
|     onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 |     onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 | ||||||
|     height = 200, |     height = 200, | ||||||
|     bindings = defaultBindings, |  | ||||||
|     jsImports = defaultImports, |     jsImports = defaultImports, | ||||||
|     showSummary = false, |     showSummary = false, | ||||||
|     width, |     width, | ||||||
|  | @ -82,9 +79,8 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||||
|     distributionChartActions, |     distributionChartActions, | ||||||
|     enableLocalSettings = false, |     enableLocalSettings = false, | ||||||
|   }) => { |   }) => { | ||||||
|     const result = useSquiggle({ |     const { result, bindings } = useSquiggle({ | ||||||
|       code, |       code, | ||||||
|       bindings, |  | ||||||
|       environment, |       environment, | ||||||
|       jsImports, |       jsImports, | ||||||
|       onChange, |       onChange, | ||||||
|  | @ -109,9 +105,13 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||||
|       count: diagramCount, |       count: diagramCount, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     const resultToRender = resultMap(result, (value) => | ||||||
|  |       value.tag === SqValueTag.Void ? bindings.asValue() : value | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|       <SquiggleViewer |       <SquiggleViewer | ||||||
|         result={result} |         result={resultToRender} | ||||||
|         width={width} |         width={width} | ||||||
|         height={height} |         height={height} | ||||||
|         distributionPlotSettings={distributionPlotSettings} |         distributionPlotSettings={distributionPlotSettings} | ||||||
|  |  | ||||||
|  | @ -1,11 +1,8 @@ | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import { CodeEditor } from "./CodeEditor"; | import { CodeEditor } from "./CodeEditor"; | ||||||
| import { environment, bindings, jsImports } from "@quri/squiggle-lang"; |  | ||||||
| import { defaultImports, defaultBindings } from "@quri/squiggle-lang"; |  | ||||||
| import { SquiggleContainer } from "./SquiggleContainer"; | import { SquiggleContainer } from "./SquiggleContainer"; | ||||||
| import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | ||||||
| import { useSquigglePartial, useMaybeControlledValue } from "../lib/hooks"; | import { useMaybeControlledValue } from "../lib/hooks"; | ||||||
| import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; |  | ||||||
| 
 | 
 | ||||||
| const WrappedCodeEditor: React.FC<{ | const WrappedCodeEditor: React.FC<{ | ||||||
|   code: string; |   code: string; | ||||||
|  | @ -42,51 +39,3 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => { | ||||||
|     </SquiggleContainer> |     </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 React from "react"; | ||||||
| import { ErrorAlert } from "./Alert"; | import { ErrorAlert } from "./Alert"; | ||||||
| 
 | 
 | ||||||
| type Props = { | type Props = { | ||||||
|   error: errorValue; |   error: SqError; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => { | 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"; | } from "@heroicons/react/solid"; | ||||||
| import clsx from "clsx"; | import clsx from "clsx"; | ||||||
| 
 | 
 | ||||||
| import { defaultBindings, environment } from "@quri/squiggle-lang"; | import { environment } from "@quri/squiggle-lang"; | ||||||
| 
 | 
 | ||||||
| import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | ||||||
| import { CodeEditor } from "./CodeEditor"; | import { CodeEditor } from "./CodeEditor"; | ||||||
|  | @ -39,6 +39,7 @@ import { ViewSettings, viewSettingsSchema } from "./ViewSettings"; | ||||||
| import { HeadedSection } from "./ui/HeadedSection"; | import { HeadedSection } from "./ui/HeadedSection"; | ||||||
| import { defaultTickFormat } from "../lib/distributionSpecBuilder"; | import { defaultTickFormat } from "../lib/distributionSpecBuilder"; | ||||||
| import { Button } from "./ui/Button"; | import { Button } from "./ui/Button"; | ||||||
|  | import { JsImports } from "../lib/jsImports"; | ||||||
| 
 | 
 | ||||||
| type PlaygroundProps = SquiggleChartProps & { | type PlaygroundProps = SquiggleChartProps & { | ||||||
|   /** The initial squiggle string to put in the playground */ |   /** The initial squiggle string to put in the playground */ | ||||||
|  | @ -112,8 +113,8 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({ | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| const InputVariablesSettings: React.FC<{ | const InputVariablesSettings: React.FC<{ | ||||||
|   initialImports: any; // TODO - any json type
 |   initialImports: JsImports; | ||||||
|   setImports: (imports: any) => void; |   setImports: (imports: JsImports) => void; | ||||||
| }> = ({ initialImports, setImports }) => { | }> = ({ initialImports, setImports }) => { | ||||||
|   const [importString, setImportString] = useState(() => |   const [importString, setImportString] = useState(() => | ||||||
|     JSON.stringify(initialImports) |     JSON.stringify(initialImports) | ||||||
|  | @ -122,7 +123,7 @@ const InputVariablesSettings: React.FC<{ | ||||||
| 
 | 
 | ||||||
|   const onChange = (value: string) => { |   const onChange = (value: string) => { | ||||||
|     setImportString(value); |     setImportString(value); | ||||||
|     let imports = {} as any; |     let imports = {}; | ||||||
|     try { |     try { | ||||||
|       imports = JSON.parse(value); |       imports = JSON.parse(value); | ||||||
|       setImportsAreValid(true); |       setImportsAreValid(true); | ||||||
|  | @ -251,7 +252,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | ||||||
|     onChange: onCodeChange, |     onChange: onCodeChange, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   const [imports, setImports] = useState({}); |   const [imports, setImports] = useState<JsImports>({}); | ||||||
| 
 | 
 | ||||||
|   const { register, control } = useForm({ |   const { register, control } = useForm({ | ||||||
|     resolver: yupResolver(schema), |     resolver: yupResolver(schema), | ||||||
|  | @ -309,7 +310,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | ||||||
|           executionId={executionId} |           executionId={executionId} | ||||||
|           environment={env} |           environment={env} | ||||||
|           {...vars} |           {...vars} | ||||||
|           bindings={defaultBindings} |  | ||||||
|           jsImports={imports} |           jsImports={imports} | ||||||
|           enableLocalSettings={true} |           enableLocalSettings={true} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import React from "react"; | import React, { useContext } from "react"; | ||||||
| import { squiggleExpression, declaration } from "@quri/squiggle-lang"; | import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang"; | ||||||
| import { NumberShower } from "../NumberShower"; | import { NumberShower } from "../NumberShower"; | ||||||
| import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; | import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; | ||||||
| import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; | import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; | ||||||
|  | @ -8,7 +8,10 @@ import { VariableBox } from "./VariableBox"; | ||||||
| import { ItemSettingsMenu } from "./ItemSettingsMenu"; | import { ItemSettingsMenu } from "./ItemSettingsMenu"; | ||||||
| import { hasMassBelowZero } from "../../lib/distributionUtils"; | import { hasMassBelowZero } from "../../lib/distributionUtils"; | ||||||
| import { MergedItemSettings } from "./utils"; | import { MergedItemSettings } from "./utils"; | ||||||
|  | import { ViewerContext } from "./ViewerContext"; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | // DISABLED FOR 0.4 branch, for now
 | ||||||
| function getRange<a>(x: declaration<a>) { | function getRange<a>(x: declaration<a>) { | ||||||
|   const first = x.args[0]; |   const first = x.args[0]; | ||||||
|   switch (first.tag) { |   switch (first.tag) { | ||||||
|  | @ -31,15 +34,21 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings { | ||||||
|     count: 20, |     count: 20, | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  | */ | ||||||
| 
 | 
 | ||||||
| const VariableList: React.FC<{ | const VariableList: React.FC<{ | ||||||
|   path: string[]; |   value: SqValue; | ||||||
|   heading: string; |   heading: string; | ||||||
|   children: (settings: MergedItemSettings) => React.ReactNode; |   children: (settings: MergedItemSettings) => React.ReactNode; | ||||||
| }> = ({ path, heading, children }) => ( | }> = ({ value, heading, children }) => ( | ||||||
|   <VariableBox path={path} heading={heading}> |   <VariableBox value={value} heading={heading}> | ||||||
|     {(settings) => ( |     {(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)} |         {children(settings)} | ||||||
|       </div> |       </div> | ||||||
|     )} |     )} | ||||||
|  | @ -48,51 +57,44 @@ const VariableList: React.FC<{ | ||||||
| 
 | 
 | ||||||
| export interface Props { | export interface Props { | ||||||
|   /** The output of squiggle's run */ |   /** The output of squiggle's run */ | ||||||
|   expression: squiggleExpression; |   value: SqValue; | ||||||
|   /** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */ |  | ||||||
|   path: string[]; |  | ||||||
|   width?: number; |   width?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const ExpressionViewer: React.FC<Props> = ({ | export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | ||||||
|   path, |   const { getMergedSettings } = useContext(ViewerContext); | ||||||
|   expression, | 
 | ||||||
|   width, |   switch (value.tag) { | ||||||
| }) => { |     case SqValueTag.Number: | ||||||
|   if (typeof expression !== "object") { |  | ||||||
|     return ( |  | ||||||
|       <VariableList path={path} heading="Error"> |  | ||||||
|         {() => `Unknown expression: ${expression}`} |  | ||||||
|       </VariableList> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   switch (expression.tag) { |  | ||||||
|     case "number": |  | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Number"> |         <VariableBox value={value} heading="Number"> | ||||||
|           {() => ( |           {() => ( | ||||||
|             <div className="font-semibold text-slate-600"> |             <div className="font-semibold text-slate-600"> | ||||||
|               <NumberShower precision={3} number={expression.value} /> |               <NumberShower precision={3} number={value.value} /> | ||||||
|             </div> |             </div> | ||||||
|           )} |           )} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "distribution": { |     case SqValueTag.Distribution: { | ||||||
|       const distType = expression.value.type(); |       const distType = value.value.tag; | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox |         <VariableBox | ||||||
|           path={path} |           value={value} | ||||||
|           heading={`Distribution (${distType})\n${ |           heading={`Distribution (${distType})\n${ | ||||||
|             distType === "Symbolic" ? expression.value.toString() : "" |             distType === SqDistributionTag.Symbolic | ||||||
|  |               ? value.value.toString() | ||||||
|  |               : "" | ||||||
|           }`}
 |           }`}
 | ||||||
|           renderSettingsMenu={({ onChange }) => { |           renderSettingsMenu={({ onChange }) => { | ||||||
|             const shape = expression.value.pointSet(); |             const shape = value.value.pointSet( | ||||||
|  |               getMergedSettings(value.location).environment | ||||||
|  |             ); | ||||||
|             return ( |             return ( | ||||||
|               <ItemSettingsMenu |               <ItemSettingsMenu | ||||||
|                 path={path} |                 value={value} | ||||||
|                 onChange={onChange} |                 onChange={onChange} | ||||||
|                 disableLogX={ |                 disableLogX={ | ||||||
|                   shape.tag === "Ok" && hasMassBelowZero(shape.value) |                   shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape()) | ||||||
|                 } |                 } | ||||||
|                 withFunctionSettings={false} |                 withFunctionSettings={false} | ||||||
|               /> |               /> | ||||||
|  | @ -102,7 +104,8 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|           {(settings) => { |           {(settings) => { | ||||||
|             return ( |             return ( | ||||||
|               <DistributionChart |               <DistributionChart | ||||||
|                 plot={defaultPlot(expression.value)} |                 plot={defaultPlot(value.value)} | ||||||
|  |                 environment={settings.environment} | ||||||
|                 {...settings.distributionPlotSettings} |                 {...settings.distributionPlotSettings} | ||||||
|                 height={settings.height} |                 height={settings.height} | ||||||
|                 width={width} |                 width={width} | ||||||
|  | @ -112,77 +115,77 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     case "string": |     case SqValueTag.String: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="String"> |         <VariableBox value={value} heading="String"> | ||||||
|           {() => ( |           {() => ( | ||||||
|             <> |             <> | ||||||
|               <span className="text-slate-400">"</span> |               <span className="text-slate-400">"</span> | ||||||
|               <span className="text-slate-600 font-semibold font-mono"> |               <span className="text-slate-600 font-semibold font-mono"> | ||||||
|                 {expression.value} |                 {value.value} | ||||||
|               </span> |               </span> | ||||||
|               <span className="text-slate-400">"</span> |               <span className="text-slate-400">"</span> | ||||||
|             </> |             </> | ||||||
|           )} |           )} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "boolean": |     case SqValueTag.Bool: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Boolean"> |         <VariableBox value={value} heading="Boolean"> | ||||||
|           {() => expression.value.toString()} |           {() => value.value.toString()} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "symbol": |     case SqValueTag.Symbol: | ||||||
|       return ( |       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-500 mr-2">Undefined Symbol:</span> | ||||||
|               <span className="text-slate-600">{expression.value}</span> |               <span className="text-slate-600">{value.value}</span> | ||||||
|             </> |             </> | ||||||
|           )} |           )} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "call": |     case SqValueTag.Call: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Call"> |         <VariableBox value={value} heading="Call"> | ||||||
|           {() => expression.value} |           {() => value.value} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "arraystring": |     case SqValueTag.ArrayString: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Array String"> |         <VariableBox value={value} heading="Array String"> | ||||||
|           {() => expression.value.map((r) => `"${r}"`).join(", ")} |           {() => value.value.map((r) => `"${r}"`).join(", ")} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "date": |     case SqValueTag.Date: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Date"> |         <VariableBox value={value} heading="Date"> | ||||||
|           {() => expression.value.toDateString()} |           {() => value.value.toDateString()} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "void": |     case SqValueTag.Void: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Void"> |         <VariableBox value={value} heading="Void"> | ||||||
|           {() => "Void"} |           {() => "Void"} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "timeDuration": { |     case SqValueTag.TimeDuration: { | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox path={path} heading="Time Duration"> |         <VariableBox value={value} heading="Time Duration"> | ||||||
|           {() => <NumberShower precision={3} number={expression.value} />} |           {() => <NumberShower precision={3} number={value.value} />} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     case "lambda": |     case SqValueTag.Lambda: | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox |         <VariableBox | ||||||
|           path={path} |           value={value} | ||||||
|           heading="Function" |           heading="Function" | ||||||
|           renderSettingsMenu={({ onChange }) => { |           renderSettingsMenu={({ onChange }) => { | ||||||
|             return ( |             return ( | ||||||
|               <ItemSettingsMenu |               <ItemSettingsMenu | ||||||
|                 path={path} |                 value={value} | ||||||
|                 onChange={onChange} |                 onChange={onChange} | ||||||
|                 withFunctionSettings={true} |                 withFunctionSettings={true} | ||||||
|               /> |               /> | ||||||
|  | @ -191,11 +194,11 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|         > |         > | ||||||
|           {(settings) => ( |           {(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 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() | ||||||
|               )})`}</div>
 |                 .join(",")})`}</div>
 | ||||||
|               <FunctionChart |               <FunctionChart | ||||||
|                 fn={expression.value} |                 fn={value.value} | ||||||
|                 chartSettings={settings.chartSettings} |                 chartSettings={settings.chartSettings} | ||||||
|                 distributionPlotSettings={settings.distributionPlotSettings} |                 distributionPlotSettings={settings.distributionPlotSettings} | ||||||
|                 height={settings.height} |                 height={settings.height} | ||||||
|  | @ -208,47 +211,48 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|           )} |           )} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     case "lambdaDeclaration": { |     case SqValueTag.Declaration: { | ||||||
|       return ( |       return ( | ||||||
|         <VariableBox |         <VariableBox | ||||||
|           path={path} |           value={value} | ||||||
|           heading="Function Declaration" |           heading="Function Declaration" | ||||||
|           renderSettingsMenu={({ onChange }) => { |           renderSettingsMenu={({ onChange }) => { | ||||||
|             return ( |             return ( | ||||||
|               <ItemSettingsMenu |               <ItemSettingsMenu | ||||||
|                 onChange={onChange} |                 onChange={onChange} | ||||||
|                 path={path} |                 value={value} | ||||||
|                 withFunctionSettings={true} |                 withFunctionSettings={true} | ||||||
|               /> |               /> | ||||||
|             ); |             ); | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           {(settings) => ( |           {(settings) => ( | ||||||
|             <FunctionChart |             <div>NOT IMPLEMENTED IN 0.4 YET</div> | ||||||
|               fn={expression.value.fn} |             // <FunctionChart
 | ||||||
|               chartSettings={getChartSettings(expression.value)} |             //   fn={expression.value.fn}
 | ||||||
|               distributionPlotSettings={settings.distributionPlotSettings} |             //   chartSettings={getChartSettings(expression.value)}
 | ||||||
|               height={settings.height} |             //   distributionPlotSettings={settings.distributionPlotSettings}
 | ||||||
|               environment={{ |             //   height={settings.height}
 | ||||||
|                 sampleCount: settings.environment.sampleCount / 10, |             //   environment={{
 | ||||||
|                 xyPointLength: settings.environment.xyPointLength / 10, |             //     sampleCount: settings.environment.sampleCount / 10,
 | ||||||
|               }} |             //     xyPointLength: settings.environment.xyPointLength / 10,
 | ||||||
|             /> |             //   }}
 | ||||||
|  |             // />
 | ||||||
|           )} |           )} | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     case "module": { |     case SqValueTag.Module: { | ||||||
|       return ( |       return ( | ||||||
|         <VariableList path={path} heading="Module"> |         <VariableList value={value} heading="Module"> | ||||||
|           {(_) => |           {(_) => | ||||||
|             Object.entries(expression.value) |             value.value | ||||||
|               .filter(([key, _]) => !key.match(/^(Math|System)\./)) |               .entries() | ||||||
|  |               .filter(([key, _]) => !key.match(/^(__result__)$/)) | ||||||
|               .map(([key, r]) => ( |               .map(([key, r]) => ( | ||||||
|                 <ExpressionViewer |                 <ExpressionViewer | ||||||
|                   key={key} |                   key={key} | ||||||
|                   path={[...path, key]} |                   value={r} | ||||||
|                   expression={r} |  | ||||||
|                   width={width !== undefined ? width - 20 : width} |                   width={width !== undefined ? width - 20 : width} | ||||||
|                 /> |                 /> | ||||||
|               )) |               )) | ||||||
|  | @ -256,23 +260,26 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|         </VariableList> |         </VariableList> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     case "record": |     case SqValueTag.Record: | ||||||
|       const plot = makePlot(expression.value); |       const plot = makePlot(value.value); | ||||||
|       if (plot) { |       if (plot) { | ||||||
|         return ( |         return ( | ||||||
|           <VariableBox |           <VariableBox | ||||||
|             path={path} |             value={value} | ||||||
|             heading={"Plot"} |             heading="Plot" | ||||||
|             renderSettingsMenu={({ onChange }) => { |             renderSettingsMenu={({ onChange }) => { | ||||||
|               let disableLogX = plot.distributions.some((x) => { |               let disableLogX = plot.distributions.some((x) => { | ||||||
|                 let pointSet = x.distribution.pointSet(); |                 let pointSet = x.distribution.pointSet( | ||||||
|  |                   getMergedSettings(value.location).environment | ||||||
|  |                 ); | ||||||
|                 return ( |                 return ( | ||||||
|                   pointSet.tag === "Ok" && hasMassBelowZero(pointSet.value) |                   pointSet.tag === "Ok" && | ||||||
|  |                   hasMassBelowZero(pointSet.value.asShape()) | ||||||
|                 ); |                 ); | ||||||
|               }); |               }); | ||||||
|               return ( |               return ( | ||||||
|                 <ItemSettingsMenu |                 <ItemSettingsMenu | ||||||
|                   path={path} |                   value={value} | ||||||
|                   onChange={onChange} |                   onChange={onChange} | ||||||
|                   disableLogX={disableLogX} |                   disableLogX={disableLogX} | ||||||
|                   withFunctionSettings={false} |                   withFunctionSettings={false} | ||||||
|  | @ -284,6 +291,7 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|               return ( |               return ( | ||||||
|                 <DistributionChart |                 <DistributionChart | ||||||
|                   plot={plot} |                   plot={plot} | ||||||
|  |                   environment={settings.environment} | ||||||
|                   {...settings.distributionPlotSettings} |                   {...settings.distributionPlotSettings} | ||||||
|                   height={settings.height} |                   height={settings.height} | ||||||
|                   width={width} |                   width={width} | ||||||
|  | @ -294,44 +302,44 @@ export const ExpressionViewer: React.FC<Props> = ({ | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         return ( |         return ( | ||||||
|           <VariableList path={path} heading="Record"> |           <VariableList value={value} heading="Record"> | ||||||
|             {(_) => |             {(_) => | ||||||
|               Object.entries(expression.value).map(([key, r]) => ( |               value.value | ||||||
|                 <ExpressionViewer |                 .entries() | ||||||
|                   key={key} |                 .map(([key, r]) => ( | ||||||
|                   path={[...path, key]} |                   <ExpressionViewer | ||||||
|                   expression={r} |                     key={key} | ||||||
|                   width={width !== undefined ? width - 20 : width} |                     value={r} | ||||||
|                 /> |                     width={width !== undefined ? width - 20 : width} | ||||||
|               )) |                   /> | ||||||
|  |                 )) | ||||||
|             } |             } | ||||||
|           </VariableList> |           </VariableList> | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     case "array": |     case SqValueTag.Array: | ||||||
|       return ( |       return ( | ||||||
|         <VariableList path={path} heading="Array"> |         <VariableList value={value} heading="Array"> | ||||||
|           {(_) => |           {(_) => | ||||||
|             expression.value.map((r, i) => ( |             value.value | ||||||
|               <ExpressionViewer |               .getValues() | ||||||
|                 key={i} |               .map((r, i) => ( | ||||||
|                 path={[...path, String(i)]} |                 <ExpressionViewer | ||||||
|                 expression={r} |                   key={i} | ||||||
|                 width={width !== undefined ? width - 20 : width} |                   value={r} | ||||||
|               /> |                   width={width !== undefined ? width - 20 : width} | ||||||
|             )) |                 /> | ||||||
|  |               )) | ||||||
|           } |           } | ||||||
|         </VariableList> |         </VariableList> | ||||||
|       ); |       ); | ||||||
|     default: { |     default: { | ||||||
|       return ( |       return ( | ||||||
|         <VariableList path={path} heading="Error"> |         <VariableList value={value} heading="Error"> | ||||||
|           {() => ( |           {() => ( | ||||||
|             <div> |             <div> | ||||||
|               <span>No display for type: </span>{" "} |               <span>No display for type: </span>{" "} | ||||||
|               <span className="font-semibold text-slate-600"> |               <span className="font-semibold text-slate-600">{value.tag}</span> | ||||||
|                 {expression.tag} |  | ||||||
|               </span> |  | ||||||
|             </div> |             </div> | ||||||
|           )} |           )} | ||||||
|         </VariableList> |         </VariableList> | ||||||
|  |  | ||||||
|  | @ -4,13 +4,14 @@ import { useForm } from "react-hook-form"; | ||||||
| import { yupResolver } from "@hookform/resolvers/yup"; | import { yupResolver } from "@hookform/resolvers/yup"; | ||||||
| import { Modal } from "../ui/Modal"; | import { Modal } from "../ui/Modal"; | ||||||
| import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; | import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; | ||||||
| import { Path, pathAsString } from "./utils"; |  | ||||||
| import { ViewerContext } from "./ViewerContext"; | import { ViewerContext } from "./ViewerContext"; | ||||||
| import { defaultTickFormat } from "../../lib/distributionSpecBuilder"; | import { defaultTickFormat } from "../../lib/distributionSpecBuilder"; | ||||||
| import { PlaygroundContext } from "../SquigglePlayground"; | import { PlaygroundContext } from "../SquigglePlayground"; | ||||||
|  | import { SqValue } from "@quri/squiggle-lang"; | ||||||
|  | import { locationAsString } from "./utils"; | ||||||
| 
 | 
 | ||||||
| type Props = { | type Props = { | ||||||
|   path: Path; |   value: SqValue; | ||||||
|   onChange: () => void; |   onChange: () => void; | ||||||
|   disableLogX?: boolean; |   disableLogX?: boolean; | ||||||
|   withFunctionSettings: boolean; |   withFunctionSettings: boolean; | ||||||
|  | @ -19,7 +20,7 @@ type Props = { | ||||||
| const ItemSettingsModal: React.FC< | const ItemSettingsModal: React.FC< | ||||||
|   Props & { close: () => void; resetScroll: () => void } |   Props & { close: () => void; resetScroll: () => void } | ||||||
| > = ({ | > = ({ | ||||||
|   path, |   value, | ||||||
|   onChange, |   onChange, | ||||||
|   disableLogX, |   disableLogX, | ||||||
|   withFunctionSettings, |   withFunctionSettings, | ||||||
|  | @ -29,7 +30,7 @@ const ItemSettingsModal: React.FC< | ||||||
|   const { setSettings, getSettings, getMergedSettings } = |   const { setSettings, getSettings, getMergedSettings } = | ||||||
|     useContext(ViewerContext); |     useContext(ViewerContext); | ||||||
| 
 | 
 | ||||||
|   const mergedSettings = getMergedSettings(path); |   const mergedSettings = getMergedSettings(value.location); | ||||||
| 
 | 
 | ||||||
|   const { register, watch } = useForm({ |   const { register, watch } = useForm({ | ||||||
|     resolver: yupResolver(viewSettingsSchema), |     resolver: yupResolver(viewSettingsSchema), | ||||||
|  | @ -53,8 +54,8 @@ const ItemSettingsModal: React.FC< | ||||||
|   }); |   }); | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const subscription = watch((vars) => { |     const subscription = watch((vars) => { | ||||||
|       const settings = getSettings(path); // get the latest version
 |       const settings = getSettings(value.location); // get the latest version
 | ||||||
|       setSettings(path, { |       setSettings(value.location, { | ||||||
|         ...settings, |         ...settings, | ||||||
|         distributionPlotSettings: { |         distributionPlotSettings: { | ||||||
|           showSummary: vars.showSummary, |           showSummary: vars.showSummary, | ||||||
|  | @ -75,7 +76,7 @@ const ItemSettingsModal: React.FC< | ||||||
|       onChange(); |       onChange(); | ||||||
|     }); |     }); | ||||||
|     return () => subscription.unsubscribe(); |     return () => subscription.unsubscribe(); | ||||||
|   }, [getSettings, setSettings, onChange, path, watch]); |   }, [getSettings, setSettings, onChange, value.location, watch]); | ||||||
| 
 | 
 | ||||||
|   const { getLeftPanelElement } = useContext(PlaygroundContext); |   const { getLeftPanelElement } = useContext(PlaygroundContext); | ||||||
| 
 | 
 | ||||||
|  | @ -83,7 +84,7 @@ const ItemSettingsModal: React.FC< | ||||||
|     <Modal container={getLeftPanelElement()} close={close}> |     <Modal container={getLeftPanelElement()} close={close}> | ||||||
|       <Modal.Header> |       <Modal.Header> | ||||||
|         Chart settings |         Chart settings | ||||||
|         {path.length ? ( |         {value.location.path.items.length ? ( | ||||||
|           <> |           <> | ||||||
|             {" for "} |             {" for "} | ||||||
|             <span |             <span | ||||||
|  | @ -91,7 +92,7 @@ const ItemSettingsModal: React.FC< | ||||||
|               className="cursor-pointer" |               className="cursor-pointer" | ||||||
|               onClick={resetScroll} |               onClick={resetScroll} | ||||||
|             > |             > | ||||||
|               {pathAsString(path)} |               {locationAsString(value.location)} | ||||||
|             </span>{" "} |             </span>{" "} | ||||||
|           </> |           </> | ||||||
|         ) : ( |         ) : ( | ||||||
|  | @ -120,7 +121,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => { | ||||||
|   if (!enableLocalSettings) { |   if (!enableLocalSettings) { | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   const settings = getSettings(props.path); |   const settings = getSettings(props.value.location); | ||||||
| 
 | 
 | ||||||
|   const resetScroll = () => { |   const resetScroll = () => { | ||||||
|     if (!ref.current) return; |     if (!ref.current) return; | ||||||
|  | @ -139,7 +140,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => { | ||||||
|       {settings.distributionPlotSettings || settings.chartSettings ? ( |       {settings.distributionPlotSettings || settings.chartSettings ? ( | ||||||
|         <button |         <button | ||||||
|           onClick={() => { |           onClick={() => { | ||||||
|             setSettings(props.path, { |             setSettings(props.value.location, { | ||||||
|               ...settings, |               ...settings, | ||||||
|               distributionPlotSettings: undefined, |               distributionPlotSettings: undefined, | ||||||
|               chartSettings: undefined, |               chartSettings: undefined, | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | import { SqValue, SqValueLocation } from "@quri/squiggle-lang"; | ||||||
| import React, { useContext, useReducer } from "react"; | import React, { useContext, useReducer } from "react"; | ||||||
| import { Tooltip } from "../ui/Tooltip"; | import { Tooltip } from "../ui/Tooltip"; | ||||||
| import { LocalItemSettings, MergedItemSettings } from "./utils"; | import { LocalItemSettings, MergedItemSettings } from "./utils"; | ||||||
|  | @ -8,14 +9,14 @@ type SettingsMenuParams = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type VariableBoxProps = { | type VariableBoxProps = { | ||||||
|   path: string[]; |   value: SqValue; | ||||||
|   heading: string; |   heading: string; | ||||||
|   renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; |   renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; | ||||||
|   children: (settings: MergedItemSettings) => React.ReactNode; |   children: (settings: MergedItemSettings) => React.ReactNode; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const VariableBox: React.FC<VariableBoxProps> = ({ | export const VariableBox: React.FC<VariableBoxProps> = ({ | ||||||
|   path, |   value: { location }, | ||||||
|   heading = "Error", |   heading = "Error", | ||||||
|   renderSettingsMenu, |   renderSettingsMenu, | ||||||
|   children, |   children, | ||||||
|  | @ -27,10 +28,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | ||||||
|   // So we use `forceUpdate` to force rerendering.
 |   // So we use `forceUpdate` to force rerendering.
 | ||||||
|   const [_, forceUpdate] = useReducer((x) => x + 1, 0); |   const [_, forceUpdate] = useReducer((x) => x + 1, 0); | ||||||
| 
 | 
 | ||||||
|   const settings = getSettings(path); |   const settings = getSettings(location); | ||||||
| 
 | 
 | ||||||
|   const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { |   const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { | ||||||
|     setSettings(path, newSettings); |     setSettings(location, newSettings); | ||||||
|     forceUpdate(); |     forceUpdate(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | @ -38,8 +39,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | ||||||
|     setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); |     setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const isTopLevel = path.length === 0; |   const isTopLevel = location.path.items.length === 0; | ||||||
|   const name = isTopLevel ? "Result" : path[path.length - 1]; |   const name = isTopLevel | ||||||
|  |     ? { result: "Result", bindings: "Bindings" }[location.path.root] | ||||||
|  |     : location.path.items[location.path.items.length - 1]; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div> |     <div> | ||||||
|  | @ -65,13 +68,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | ||||||
|       </header> |       </header> | ||||||
|       {settings.collapsed ? null : ( |       {settings.collapsed ? null : ( | ||||||
|         <div className="flex w-full"> |         <div className="flex w-full"> | ||||||
|           {path.length ? ( |           {location.path.items.length ? ( | ||||||
|             <div |             <div | ||||||
|               className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" |               className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" | ||||||
|               onClick={toggleCollapsed} |               onClick={toggleCollapsed} | ||||||
|             ></div> |             ></div> | ||||||
|           ) : null} |           ) : null} | ||||||
|           <div className="grow">{children(getMergedSettings(path))}</div> |           <div className="grow">{children(getMergedSettings(location))}</div> | ||||||
|         </div> |         </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 React from "react"; | ||||||
| import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; | import { LocalItemSettings, MergedItemSettings } from "./utils"; | ||||||
| 
 | 
 | ||||||
| type ViewerContextShape = { | 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).
 |   // 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.
 |   // 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.
 |   // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
 | ||||||
|   getSettings(path: Path): LocalItemSettings; |   getSettings(location: SqValueLocation): LocalItemSettings; | ||||||
|   getMergedSettings(path: Path): MergedItemSettings; |   getMergedSettings(location: SqValueLocation): MergedItemSettings; | ||||||
|   setSettings(path: Path, value: LocalItemSettings): void; |   setSettings(location: SqValueLocation, value: LocalItemSettings): void; | ||||||
|   enableLocalSettings: boolean; // show local settings icon in the UI
 |   enableLocalSettings: boolean; // show local settings icon in the UI
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,21 +1,20 @@ | ||||||
| import React, { useCallback, useRef } from "react"; | import React, { useCallback, useRef } from "react"; | ||||||
| import { environment } from "@quri/squiggle-lang"; | import { environment, SqValueLocation } from "@quri/squiggle-lang"; | ||||||
| import { DistributionPlottingSettings } from "../DistributionChart"; | import { DistributionPlottingSettings } from "../DistributionChart"; | ||||||
| import { FunctionChartSettings } from "../FunctionChart"; | import { FunctionChartSettings } from "../FunctionChart"; | ||||||
| import { ExpressionViewer } from "./ExpressionViewer"; | import { ExpressionViewer } from "./ExpressionViewer"; | ||||||
| import { ViewerContext } from "./ViewerContext"; | import { ViewerContext } from "./ViewerContext"; | ||||||
| import { | import { | ||||||
|   LocalItemSettings, |   LocalItemSettings, | ||||||
|  |   locationAsString, | ||||||
|   MergedItemSettings, |   MergedItemSettings, | ||||||
|   Path, |  | ||||||
|   pathAsString, |  | ||||||
| } from "./utils"; | } from "./utils"; | ||||||
| import { useSquiggle } from "../../lib/hooks"; | import { useSquiggle } from "../../lib/hooks"; | ||||||
| import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; | import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; | ||||||
| 
 | 
 | ||||||
| type Props = { | type Props = { | ||||||
|   /** The output of squiggle's run */ |   /** The output of squiggle's run */ | ||||||
|   result: ReturnType<typeof useSquiggle>; |   result: ReturnType<typeof useSquiggle>["result"]; | ||||||
|   width?: number; |   width?: number; | ||||||
|   height: number; |   height: number; | ||||||
|   distributionPlotSettings: DistributionPlottingSettings; |   distributionPlotSettings: DistributionPlottingSettings; | ||||||
|  | @ -45,22 +44,22 @@ export const SquiggleViewer: React.FC<Props> = ({ | ||||||
|   const settingsRef = useRef<Settings>({}); |   const settingsRef = useRef<Settings>({}); | ||||||
| 
 | 
 | ||||||
|   const getSettings = useCallback( |   const getSettings = useCallback( | ||||||
|     (path: Path) => { |     (location: SqValueLocation) => { | ||||||
|       return settingsRef.current[pathAsString(path)] || defaultSettings; |       return settingsRef.current[locationAsString(location)] || defaultSettings; | ||||||
|     }, |     }, | ||||||
|     [settingsRef] |     [settingsRef] | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const setSettings = useCallback( |   const setSettings = useCallback( | ||||||
|     (path: Path, value: LocalItemSettings) => { |     (location: SqValueLocation, value: LocalItemSettings) => { | ||||||
|       settingsRef.current[pathAsString(path)] = value; |       settingsRef.current[locationAsString(location)] = value; | ||||||
|     }, |     }, | ||||||
|     [settingsRef] |     [settingsRef] | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const getMergedSettings = useCallback( |   const getMergedSettings = useCallback( | ||||||
|     (path: Path) => { |     (location: SqValueLocation) => { | ||||||
|       const localSettings = getSettings(path); |       const localSettings = getSettings(location); | ||||||
|       const result: MergedItemSettings = { |       const result: MergedItemSettings = { | ||||||
|         distributionPlotSettings: { |         distributionPlotSettings: { | ||||||
|           ...distributionPlotSettings, |           ...distributionPlotSettings, | ||||||
|  | @ -91,7 +90,7 @@ export const SquiggleViewer: React.FC<Props> = ({ | ||||||
|       }} |       }} | ||||||
|     > |     > | ||||||
|       {result.tag === "Ok" ? ( |       {result.tag === "Ok" ? ( | ||||||
|         <ExpressionViewer path={[]} expression={result.value} width={width} /> |         <ExpressionViewer value={result.value} width={width} /> | ||||||
|       ) : ( |       ) : ( | ||||||
|         <SquiggleErrorAlert error={result.value} /> |         <SquiggleErrorAlert error={result.value} /> | ||||||
|       )} |       )} | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { DistributionPlottingSettings } from "../DistributionChart"; | import { DistributionPlottingSettings } from "../DistributionChart"; | ||||||
| import { FunctionChartSettings } from "../FunctionChart"; | import { FunctionChartSettings } from "../FunctionChart"; | ||||||
| import { environment } from "@quri/squiggle-lang"; | import { environment, SqValueLocation } from "@quri/squiggle-lang"; | ||||||
| 
 | 
 | ||||||
| export type LocalItemSettings = { | export type LocalItemSettings = { | ||||||
|   collapsed: boolean; |   collapsed: boolean; | ||||||
|  | @ -17,6 +17,5 @@ export type MergedItemSettings = { | ||||||
|   environment: environment; |   environment: environment; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type Path = string[]; | export const locationAsString = (location: SqValueLocation) => | ||||||
| 
 |   location.path.items.join("."); | ||||||
| export const pathAsString = (path: Path) => path.join("."); |  | ||||||
|  |  | ||||||
|  | @ -1,7 +1,4 @@ | ||||||
| export { SquiggleChart } from "./components/SquiggleChart"; | export { SquiggleChart } from "./components/SquiggleChart"; | ||||||
| export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor"; | export { SquiggleEditor } from "./components/SquiggleEditor"; | ||||||
| export { SquigglePlayground } from "./components/SquigglePlayground"; | export { SquigglePlayground } from "./components/SquigglePlayground"; | ||||||
| export { SquiggleContainer } from "./components/SquiggleContainer"; | 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.continuous.some((x) => x.x <= 0) || | ||||||
|   shape.discrete.some((x) => x.x <= 0); |   shape.discrete.some((x) => x.x <= 0); | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| export { useMaybeControlledValue } from "./useMaybeControlledValue"; | export { useMaybeControlledValue } from "./useMaybeControlledValue"; | ||||||
| export { useSquiggle, useSquigglePartial } from "./useSquiggle"; | export { useSquiggle } from "./useSquiggle"; | ||||||
| export { useRunnerState } from "./useRunnerState"; | export { useRunnerState } from "./useRunnerState"; | ||||||
|  |  | ||||||
|  | @ -1,53 +1,42 @@ | ||||||
| import { | import { environment, SqProject, SqValue } from "@quri/squiggle-lang"; | ||||||
|   bindings, |  | ||||||
|   environment, |  | ||||||
|   jsImports, |  | ||||||
|   run, |  | ||||||
|   runPartial, |  | ||||||
| } from "@quri/squiggle-lang"; |  | ||||||
| import { useEffect, useMemo } from "react"; | import { useEffect, useMemo } from "react"; | ||||||
|  | import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; | ||||||
| 
 | 
 | ||||||
| type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = { | type SquiggleArgs = { | ||||||
|   code: string; |   code: string; | ||||||
|   executionId?: number; |   executionId?: number; | ||||||
|   bindings?: bindings; |   jsImports?: JsImports; | ||||||
|   jsImports?: jsImports; |  | ||||||
|   environment?: environment; |   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>>( | export const useSquiggle = (args: SquiggleArgs) => { | ||||||
|   args: SquiggleArgs<T>, |   const result = useMemo( | ||||||
|   f: (...args: Parameters<typeof run>) => T |     () => { | ||||||
| ) => { |       const project = SqProject.create(); | ||||||
|   const result: T = useMemo<T>( |       project.setSource("main", args.code); | ||||||
|     () => f(args.code, args.bindings, args.environment, args.jsImports), |       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
 |     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||||
|     [ |     [args.code, args.environment, args.jsImports, args.executionId] | ||||||
|       f, |  | ||||||
|       args.code, |  | ||||||
|       args.bindings, |  | ||||||
|       args.environment, |  | ||||||
|       args.jsImports, |  | ||||||
|       args.executionId, |  | ||||||
|     ] |  | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const { onChange } = args; |   const { onChange } = args; | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     onChange?.(result.tag === "Ok" ? result.value : undefined); |     onChange?.(result.result.tag === "Ok" ? result.result.value : undefined); | ||||||
|   }, [result, onChange]); |   }, [result, onChange]); | ||||||
| 
 | 
 | ||||||
|   return result; |   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 * as yup from "yup"; | ||||||
| import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang"; | import { SqDistribution, result, SqRecord } from "@quri/squiggle-lang"; | ||||||
| 
 | 
 | ||||||
| export type LabeledDistribution = { | export type LabeledDistribution = { | ||||||
|   name: string; |   name: string; | ||||||
|   distribution: Distribution; |   distribution: SqDistribution; | ||||||
|   color?: string; |   color?: string; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -53,9 +53,7 @@ const schema = yup | ||||||
|     }), |     }), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| export function parsePlot(record: { | export function parsePlot(record: SqRecord): result<Plot, string> { | ||||||
|   [key: string]: squiggleExpression; |  | ||||||
| }): result<Plot, string> { |  | ||||||
|   try { |   try { | ||||||
|     const plotRecord = schema.validateSync(record); |     const plotRecord = schema.validateSync(record); | ||||||
|     return ok({ |     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 Jest | ||||||
| open Expect | open Expect | ||||||
| 
 | 
 | ||||||
| let expectEvalToBe = (expr: string, answer: string) => | let expectEvalToBe = (sourceCode: string, answer: string) => | ||||||
|   Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) |   Expression.BackCompatible.evaluateString(sourceCode) | ||||||
|  |   ->ExpressionValue.toStringResult | ||||||
|  |   ->expect | ||||||
|  |   ->toBe(answer) | ||||||
| 
 | 
 | ||||||
| let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) | let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| // Reducer_Helpers | // Reducer_Helpers | ||||||
| module ErrorValue = Reducer_ErrorValue | module ErrorValue = Reducer_ErrorValue | ||||||
| module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue |  | ||||||
| module InternalExpressionValue = ReducerInterface.InternalExpressionValue | module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||||
| module Bindings = Reducer_Bindings | 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 rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal) | ||||||
| let rRemoveDefaultsExternal = r => Belt.Result.map(r, removeDefaultsExternal) |  | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module MathJs = Reducer_MathJs | module MathJs = Reducer_MathJs | ||||||
| module ErrorValue = Reducer.ErrorValue | module ErrorValue = Reducer_ErrorValue | ||||||
| 
 | 
 | ||||||
| open Jest | open Jest | ||||||
| open ExpectJs | open ExpectJs | ||||||
|  |  | ||||||
|  | @ -3,359 +3,464 @@ open Reducer_Peggy_TestHelpers | ||||||
| 
 | 
 | ||||||
| describe("Peggy parse", () => { | describe("Peggy parse", () => { | ||||||
|   describe("float", () => { |   describe("float", () => { | ||||||
|     testParse("1.", "{1}") |     testParse("1.", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("1.1", "{1.1}") |     testParse("1.1", "{(::$_endOfOuterBlock_$ () 1.1)}") | ||||||
|     testParse(".1", "{0.1}") |     testParse(".1", "{(::$_endOfOuterBlock_$ () 0.1)}") | ||||||
|     testParse("0.1", "{0.1}") |     testParse("0.1", "{(::$_endOfOuterBlock_$ () 0.1)}") | ||||||
|     testParse("1e1", "{10}") |     testParse("1e1", "{(::$_endOfOuterBlock_$ () 10)}") | ||||||
|     testParse("1e-1", "{0.1}") |     testParse("1e-1", "{(::$_endOfOuterBlock_$ () 0.1)}") | ||||||
|     testParse(".1e1", "{1}") |     testParse(".1e1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("0.1e1", "{1}") |     testParse("0.1e1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("literals operators parenthesis", () => { |   describe("literals operators parenthesis", () => { | ||||||
|     // Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement |     // Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement | ||||||
|     testParse("1", "{1}") |     testParse("1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("'hello'", "{'hello'}") |     testParse("'hello'", "{(::$_endOfOuterBlock_$ () 'hello')}") | ||||||
|     testParse("true", "{true}") |     testParse("true", "{(::$_endOfOuterBlock_$ () true)}") | ||||||
|     testParse("1+2", "{(::add 1 2)}") |     testParse("1+2", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||||
|     testParse("add(1,2)", "{(::add 1 2)}") |     testParse("add(1,2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||||
|     testParse("(1)", "{1}") |     testParse("(1)", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("(1+2)", "{(::add 1 2)}") |     testParse("(1+2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("unary", () => { |   describe("unary", () => { | ||||||
|     testParse("-1", "{(::unaryMinus 1)}") |     testParse("-1", "{(::$_endOfOuterBlock_$ () (::unaryMinus 1))}") | ||||||
|     testParse("!true", "{(::not true)}") |     testParse("!true", "{(::$_endOfOuterBlock_$ () (::not true))}") | ||||||
|     testParse("1 + -1", "{(::add 1 (::unaryMinus 1))}") |     testParse("1 + -1", "{(::$_endOfOuterBlock_$ () (::add 1 (::unaryMinus 1)))}") | ||||||
|     testParse("-a[0]", "{(::unaryMinus (::$_atIndex_$ :a 0))}") |     testParse("-a[0]", "{(::$_endOfOuterBlock_$ () (::unaryMinus (::$_atIndex_$ :a 0)))}") | ||||||
|     testParse("!a[0]", "{(::not (::$_atIndex_$ :a 0))}") |     testParse("!a[0]", "{(::$_endOfOuterBlock_$ () (::not (::$_atIndex_$ :a 0)))}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("multiplicative", () => { |   describe("multiplicative", () => { | ||||||
|     testParse("1 * 2", "{(::multiply 1 2)}") |     testParse("1 * 2", "{(::$_endOfOuterBlock_$ () (::multiply 1 2))}") | ||||||
|     testParse("1 / 2", "{(::divide 1 2)}") |     testParse("1 / 2", "{(::$_endOfOuterBlock_$ () (::divide 1 2))}") | ||||||
|     testParse("1 * 2 * 3", "{(::multiply (::multiply 1 2) 3)}") |     testParse("1 * 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::multiply 1 2) 3))}") | ||||||
|     testParse("1 * 2 / 3", "{(::divide (::multiply 1 2) 3)}") |     testParse("1 * 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::multiply 1 2) 3))}") | ||||||
|     testParse("1 / 2 * 3", "{(::multiply (::divide 1 2) 3)}") |     testParse("1 / 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::divide 1 2) 3))}") | ||||||
|     testParse("1 / 2 / 3", "{(::divide (::divide 1 2) 3)}") |     testParse("1 / 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::divide 1 2) 3))}") | ||||||
|     testParse("1 * 2 + 3 * 4", "{(::add (::multiply 1 2) (::multiply 3 4))}") |     testParse( | ||||||
|     testParse("1 * 2 - 3 * 4", "{(::subtract (::multiply 1 2) (::multiply 3 4))}") |       "1 * 2 + 3 * 4", | ||||||
|     testParse("1 * 2 .+ 3 * 4", "{(::dotAdd (::multiply 1 2) (::multiply 3 4))}") |       "{(::$_endOfOuterBlock_$ () (::add (::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( | ||||||
|     testParse("1 * 2 + 3 / 4", "{(::add (::multiply 1 2) (::divide 3 4))}") |       "1 * 2 - 3 * 4", | ||||||
|     testParse("1 * 2 + 3 ./ 4", "{(::add (::multiply 1 2) (::dotDivide 3 4))}") |       "{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 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( | ||||||
|     testParse("1 * 2 - 3 ./ 4", "{(::subtract (::multiply 1 2) (::dotDivide 3 4))}") |       "1 * 2 .+ 3 * 4", | ||||||
|     testParse("1 * 2 - 3 * 4^5", "{(::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5)))}") |       "{(::$_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( |     testParse( | ||||||
|       "1 * 2 - 3 * 4^5^6", |       "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", () => { |   describe("multi-line", () => { | ||||||
|     testParse("x=1; 2", "{:x = {1}; 2}") |     testParse("x=1; 2", "{:x = {1}; (::$_endOfOuterBlock_$ () 2)}") | ||||||
|     testParse("x=1; y=2", "{:x = {1}; :y = {2}}") |     testParse("x=1; y=2", "{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("variables", () => { |   describe("variables", () => { | ||||||
|     testParse("x = 1", "{:x = {1}}") |     testParse("x = 1", "{:x = {1}; (::$_endOfOuterBlock_$ () ())}") | ||||||
|     testParse("x", "{:x}") |     testParse("x", "{(::$_endOfOuterBlock_$ () :x)}") | ||||||
|     testParse("x = 1; x", "{:x = {1}; :x}") |     testParse("x = 1; x", "{:x = {1}; (::$_endOfOuterBlock_$ () :x)}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("functions", () => { |   describe("functions", () => { | ||||||
|     testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments |     testParse("identity(x) = x", "{:identity = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions become lambda assignments | ||||||
|     testParse("identity(x)", "{(::identity :x)}") |     testParse("identity(x)", "{(::$_endOfOuterBlock_$ () (::identity :x))}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("arrays", () => { |   describe("arrays", () => { | ||||||
|     testParse("[]", "{(::$_constructArray_$ ())}") |     testParse("[]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ ()))}") | ||||||
|     testParse("[0, 1, 2]", "{(::$_constructArray_$ (0 1 2))}") |     testParse("[0, 1, 2]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ (0 1 2)))}") | ||||||
|     testParse("['hello', 'world']", "{(::$_constructArray_$ ('hello' 'world'))}") |     testParse( | ||||||
|     testParse("([0,1,2])[1]", "{(::$_atIndex_$ (::$_constructArray_$ (0 1 2)) 1)}") |       "['hello', 'world']", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ ('hello' 'world')))}", | ||||||
|  |     ) | ||||||
|  |     testParse( | ||||||
|  |       "([0,1,2])[1]", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::$_atIndex_$ (::$_constructArray_$ (0 1 2)) 1))}", | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("records", () => { |   describe("records", () => { | ||||||
|     testParse("{a: 1, b: 2}", "{(::$_constructRecord_$ ('a': 1 'b': 2))}") |     testParse( | ||||||
|     testParse("{1+0: 1, 2+0: 2}", "{(::$_constructRecord_$ ((::add 1 0): 1 (::add 2 0): 2))}") // key can be any expression |       "{a: 1, b: 2}", | ||||||
|     testParse("record.property", "{(::$_atIndex_$ :record 'property')}") |       "{(::$_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", () => { |   describe("post operators", () => { | ||||||
|     //function call, array and record access are post operators with higher priority than unary operators |     //function call, array and record access are post operators with higher priority than unary operators | ||||||
|     testParse("a==!b(1)", "{(::equal :a (::not (::b 1)))}") |     testParse("a==!b(1)", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::b 1))))}") | ||||||
|     testParse("a==!b[1]", "{(::equal :a (::not (::$_atIndex_$ :b 1)))}") |     testParse("a==!b[1]", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 1))))}") | ||||||
|     testParse("a==!b.one", "{(::equal :a (::not (::$_atIndex_$ :b 'one')))}") |     testParse( | ||||||
|  |       "a==!b.one", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 'one'))))}", | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("comments", () => { |   describe("comments", () => { | ||||||
|     testParse("1 # This is a line comment", "{1}") |     testParse("1 # This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("1 // This is a line comment", "{1}") |     testParse("1 // This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("1 /* This is a multi line comment */", "{1}") |     testParse("1 /* This is a multi line comment */", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse("/* This is a multi line comment */ 1", "{1}") |     testParse("/* This is a multi line comment */ 1", "{(::$_endOfOuterBlock_$ () 1)}") | ||||||
|     testParse( |     testParse( | ||||||
|       ` |       ` | ||||||
|     /* This is  |   /* This is  | ||||||
|     a multi line  |   a multi line  | ||||||
|     comment */ |   comment */ | ||||||
|     1`, |   1`, | ||||||
|       "{1}", |       "{(::$_endOfOuterBlock_$ () 1)}", | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("ternary operator", () => { |   describe("ternary operator", () => { | ||||||
|     testParse("true ? 2 : 3", "{(::$$_ternary_$$ true 2 3)}") |     testParse("true ? 2 : 3", "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true 2 3))}") | ||||||
|     testParse( |     testParse( | ||||||
|       "false ? 2 : false ? 4 : 5", |       "false ? 2 : false ? 4 : 5", | ||||||
|       "{(::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5))}", |       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5)))}", | ||||||
|     ) // nested ternary |     ) // nested ternary | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("if then else", () => { |   describe("if then else", () => { | ||||||
|     testParse("if true then 2 else 3", "{(::$$_ternary_$$ true {2} {3})}") |     testParse( | ||||||
|     testParse("if false then {2} else {3}", "{(::$$_ternary_$$ false {2} {3})}") |       "if true then 2 else 3", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true {2} {3}))}", | ||||||
|  |     ) | ||||||
|  |     testParse( | ||||||
|  |       "if false then {2} else {3}", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} {3}))}", | ||||||
|  |     ) | ||||||
|     testParse( |     testParse( | ||||||
|       "if false then {2} else if false then {4} else {5}", |       "if false then {2} else if false then {4} else {5}", | ||||||
|       "{(::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5}))}", |       "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5})))}", | ||||||
|     ) //nested if |     ) //nested if | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("logical", () => { |   describe("logical", () => { | ||||||
|     testParse("true || false", "{(::or true false)}") |     testParse("true || false", "{(::$_endOfOuterBlock_$ () (::or true false))}") | ||||||
|     testParse("true && false", "{(::and true false)}") |     testParse("true && false", "{(::$_endOfOuterBlock_$ () (::and true false))}") | ||||||
|     testParse("a * b + c", "{(::add (::multiply :a :b) :c)}") // for comparison |     testParse("a * b + c", "{(::$_endOfOuterBlock_$ () (::add (::multiply :a :b) :c))}") // for comparison | ||||||
|     testParse("a && b || c", "{(::or (::and :a :b) :c)}") |     testParse("a && b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) :c))}") | ||||||
|     testParse("a && b || c && d", "{(::or (::and :a :b) (::and :c :d))}") |     testParse("a && b || c && d", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) (::and :c :d)))}") | ||||||
|     testParse("a && !b || c", "{(::or (::and :a (::not :b)) :c)}") |     testParse("a && !b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::not :b)) :c))}") | ||||||
|     testParse("a && b==c || d", "{(::or (::and :a (::equal :b :c)) :d)}") |     testParse("a && b==c || d", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::equal :b :c)) :d))}") | ||||||
|     testParse("a && b!=c || d", "{(::or (::and :a (::unequal :b :c)) :d)}") |     testParse( | ||||||
|     testParse("a && !(b==c) || d", "{(::or (::and :a (::not (::equal :b :c))) :d)}") |       "a && b!=c || d", | ||||||
|     testParse("a && b>=c || d", "{(::or (::and :a (::largerEq :b :c)) :d)}") |       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::unequal :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( | ||||||
|     testParse("a && b>c || d", "{(::or (::and :a (::larger :b :c)) :d)}") |       "a && !(b==c) || d", | ||||||
|     testParse("a && b<c || d", "{(::or (::and :a (::smaller :b :c)) :d)}") |       "{(::$_endOfOuterBlock_$ () (::or (::and :a (::not (::equal :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( | ||||||
|     testParse("a && b<c(i) || d", "{(::or (::and :a (::smaller :b (::c :i))) :d)}") |       "a && b>=c || d", | ||||||
|     testParse("a && b<1+2 || d", "{(::or (::and :a (::smaller :b (::add 1 2))) :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( |     testParse( | ||||||
|       "a && b<1+2*3 || d", |       "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( |     testParse( | ||||||
|       "a && b<1+2*-3+4 || d", |       "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( |     testParse( | ||||||
|       "a && b<1+2*3 || d ? true : false", |       "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", () => { |   describe("pipe", () => { | ||||||
|     testParse("1 -> add(2)", "{(::add 1 2)}") |     testParse("1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||||
|     testParse("-1 -> add(2)", "{(::add (::unaryMinus 1) 2)}") |     testParse("-1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus 1) 2))}") | ||||||
|     testParse("-a[1] -> add(2)", "{(::add (::unaryMinus (::$_atIndex_$ :a 1)) 2)}") |     testParse( | ||||||
|     testParse("-f(1) -> add(2)", "{(::add (::unaryMinus (::f 1)) 2)}") |       "-a[1] -> add(2)", | ||||||
|     testParse("1 + 2 -> add(3)", "{(::add 1 (::add 2 3))}") |       "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::$_atIndex_$ :a 1)) 2))}", | ||||||
|     testParse("1 -> add(2) * 3", "{(::multiply (::add 1 2) 3)}") |     ) | ||||||
|     testParse("1 -> subtract(2)", "{(::subtract 1 2)}") |     testParse("-f(1) -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::f 1)) 2))}") | ||||||
|     testParse("-1 -> subtract(2)", "{(::subtract (::unaryMinus 1) 2)}") |     testParse("1 + 2 -> add(3)", "{(::$_endOfOuterBlock_$ () (::add 1 (::add 2 3)))}") | ||||||
|     testParse("1 -> subtract(2) * 3", "{(::multiply (::subtract 1 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", () => { |   describe("elixir pipe", () => { | ||||||
|     //handled together with -> so there is no need for seperate tests |     //handled together with -> so there is no need for seperate tests | ||||||
|     testParse("1 |> add(2)", "{(::add 1 2)}") |     testParse("1 |> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("to", () => { |   describe("to", () => { | ||||||
|     testParse("1 to 2", "{(::credibleIntervalToDistribution 1 2)}") |     testParse("1 to 2", "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution 1 2))}") | ||||||
|     testParse("-1 to -2", "{(::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2))}") // lower than unary |     testParse( | ||||||
|  |       "-1 to -2", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2)))}", | ||||||
|  |     ) // lower than unary | ||||||
|     testParse( |     testParse( | ||||||
|       "a[1] to a[2]", |       "a[1] to a[2]", | ||||||
|       "{(::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2))}", |       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2)))}", | ||||||
|     ) // lower than post |     ) // lower than post | ||||||
|     testParse( |     testParse( | ||||||
|       "a.p1 to a.p2", |       "a.p1 to a.p2", | ||||||
|       "{(::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2'))}", |       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2')))}", | ||||||
|     ) // lower than post |     ) // lower than post | ||||||
|     testParse("1 to 2 + 3", "{(::add (::credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators |     testParse( | ||||||
|  |       "1 to 2 + 3", | ||||||
|  |       "{(::$_endOfOuterBlock_$ () (::add (::credibleIntervalToDistribution 1 2) 3))}", | ||||||
|  |     ) // higher than binary operators | ||||||
|     testParse( |     testParse( | ||||||
|       "1->add(2) to 3->add(4) -> add(4)", |       "1->add(2) to 3->add(4) -> add(4)", | ||||||
|       "{(::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4))}", |       "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4)))}", | ||||||
|     ) // lower than chain |     ) // lower than chain | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("inner block", () => { |   describe("inner block", () => { | ||||||
|     // inner blocks are 0 argument lambdas. They can be used whenever a value is required. |     // inner blocks are 0 argument lambdas. They can be used whenever a value is required. | ||||||
|     // Like lambdas they have a local scope. |     // Like lambdas they have a local scope. | ||||||
|     testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; :x}") |     testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; (::$_endOfOuterBlock_$ () :x)}") | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("lambda", () => { |   describe("lambda", () => { | ||||||
|     testParse("{|x| x}", "{{|:x| {:x}}}") |     testParse("{|x| x}", "{(::$_endOfOuterBlock_$ () {|:x| {:x}})}") | ||||||
|     testParse("f={|x| x}", "{:f = {{|:x| {:x}}}}") |     testParse("f={|x| x}", "{:f = {{|:x| {:x}}}; (::$_endOfOuterBlock_$ () ())}") | ||||||
|     testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments |     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)}}}") // 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", () => { |   describe("Using lambda as value", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "myadd(x,y)=x+y; z=myadd; z", |       "myadd(x,y)=x+y; z=myadd; z", | ||||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; :z}", |       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; (::$_endOfOuterBlock_$ () :z)}", | ||||||
|     ) |     ) | ||||||
|     testParse( |     testParse( | ||||||
|       "myadd(x,y)=x+y; z=[myadd]; z", |       "myadd(x,y)=x+y; z=[myadd]; z", | ||||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ (:myadd))}; :z}", |       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ (:myadd))}; (::$_endOfOuterBlock_$ () :z)}", | ||||||
|     ) |     ) | ||||||
|     testParse( |     testParse( | ||||||
|       "myaddd(x,y)=x+y; z={x: myaddd}; z", |       "myaddd(x,y)=x+y; z={x: myaddd}; z", | ||||||
|       "{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; :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( |     testParse( | ||||||
|       "map([1,2,3], {|x| x+1})", |       "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( |     testParse( | ||||||
|       "[1,2,3]->map({|x| x+1})", |       "[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", () => { |   describe("unit", () => { | ||||||
|     testParse("1m", "{(::fromUnit_m 1)}") |     testParse("1m", "{(::$_endOfOuterBlock_$ () (::fromUnit_m 1))}") | ||||||
|     testParse("1M", "{(::fromUnit_M 1)}") |     testParse("1M", "{(::$_endOfOuterBlock_$ () (::fromUnit_M 1))}") | ||||||
|     testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}") |     testParse("1m+2cm", "{(::$_endOfOuterBlock_$ () (::add (::fromUnit_m 1) (::fromUnit_cm 2)))}") | ||||||
|   }) |   }) | ||||||
|   describe("Module", () => { |   describe("Module", () => { | ||||||
|     testParse("x", "{:x}") |     testParse("x", "{(::$_endOfOuterBlock_$ () :x)}") | ||||||
|     testParse("Math.pi", "{:Math.pi}") |     testParse("Math.pi", "{(::$_endOfOuterBlock_$ () :Math.pi)}") | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| describe("parsing new line", () => { | describe("parsing new line", () => { | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   a +  |  a +  | ||||||
|   b`, |  b`, | ||||||
|     "{(::add :a :b)}", |     "{(::$_endOfOuterBlock_$ () (::add :a :b))}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   x= |  x= | ||||||
|   1`, |  1`, | ||||||
|     "{:x = {1}}", |     "{:x = {1}; (::$_endOfOuterBlock_$ () ())}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   x=1 |  x=1 | ||||||
|   y=2`, |  y=2`, | ||||||
|     "{:x = {1}; :y = {2}}", |     "{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   x={ |  x={ | ||||||
|    y=2; |   y=2; | ||||||
|    y } |   y } | ||||||
|   x`, |  x`, | ||||||
|     "{:x = {:y = {2}; :y}; :x}", |     "{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   x={ |  x={ | ||||||
|    y=2 |   y=2 | ||||||
|    y } |   y } | ||||||
|   x`, |  x`, | ||||||
|     "{:x = {:y = {2}; :y}; :x}", |     "{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   x={ |  x={ | ||||||
|    y=2 |   y=2 | ||||||
|    y  |   y  | ||||||
|    } |   } | ||||||
|   x`, |  x`, | ||||||
|     "{:x = {:y = {2}; :y}; :x}", |     "{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|  |  x=1 | ||||||
|  |  y=2 | ||||||
|  |  z=3 | ||||||
|  |  `, | ||||||
|  |     "{:x = {1}; :y = {2}; :z = {3}; (::$_endOfOuterBlock_$ () ())}", | ||||||
|  |   ) | ||||||
|  |   testParse( | ||||||
|  |     ` | ||||||
|  |  f={ | ||||||
|   x=1 |   x=1 | ||||||
|   y=2 |   y=2 | ||||||
|   z=3 |   z=3 | ||||||
|   `, |   x+y+z | ||||||
|     "{:x = {1}; :y = {2}; :z = {3}}", |  } | ||||||
|  |  `, | ||||||
|  |     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; (::$_endOfOuterBlock_$ () ())}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   f={ |  f={ | ||||||
|     x=1 |   x=1 | ||||||
|     y=2 |   y=2 | ||||||
|     z=3 |   z=3 | ||||||
|     x+y+z |   x+y+z | ||||||
|  |  } | ||||||
|  |  g=f+4 | ||||||
|  |  g | ||||||
|  |  `, | ||||||
|  |     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () :g)}", | ||||||
|  |   ) | ||||||
|  |   testParse( | ||||||
|  |     ` | ||||||
|  |  f = | ||||||
|  |   { | ||||||
|  |    x=1; //x | ||||||
|  |    y=2 //y | ||||||
|  |    z= | ||||||
|  |     3 | ||||||
|  |    x+ | ||||||
|  |     y+ | ||||||
|  |     z | ||||||
|   } |   } | ||||||
|   `, |  g = | ||||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}", |   f + | ||||||
|  |    4 | ||||||
|  |  g -> | ||||||
|  |   h -> | ||||||
|  |   p -> | ||||||
|  |   q  | ||||||
|  |  `, | ||||||
|  |     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () (::q (::p (::h :g))))}", | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   f={ |   a |> | ||||||
|     x=1 |   b |> | ||||||
|     y=2 |   c |> | ||||||
|     z=3 |   d  | ||||||
|     x+y+z |  `, | ||||||
|   } |     "{(::$_endOfOuterBlock_$ () (::d (::c (::b :a))))}", | ||||||
|   g=f+4 |  | ||||||
|   g |  | ||||||
|   `, |  | ||||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; :g}", |  | ||||||
|   ) |   ) | ||||||
|   testParse( |   testParse( | ||||||
|     ` |     ` | ||||||
|   f = |   a |> | ||||||
|     { |   b |> | ||||||
|       x=1; //x |   c |> | ||||||
|       y=2  //y |   d + | ||||||
|       z= |   e | ||||||
|         3 |  `, | ||||||
|       x+ |     "{(::$_endOfOuterBlock_$ () (::add (::d (::c (::b :a))) :e))}", | ||||||
|         y+ |  | ||||||
|         z |  | ||||||
|     } |  | ||||||
|   g = |  | ||||||
|     f + |  | ||||||
|       4 |  | ||||||
|   g -> |  | ||||||
|     h -> |  | ||||||
|     p -> |  | ||||||
|     q  |  | ||||||
|   `, |  | ||||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::q (::p (::h :g)))}", |  | ||||||
|   ) |  | ||||||
|   testParse( |  | ||||||
|     ` |  | ||||||
|     a |> |  | ||||||
|     b |> |  | ||||||
|     c |> |  | ||||||
|     d  |  | ||||||
|   `, |  | ||||||
|     "{(::d (::c (::b :a)))}", |  | ||||||
|   ) |  | ||||||
|   testParse( |  | ||||||
|     ` |  | ||||||
|     a |> |  | ||||||
|     b |> |  | ||||||
|     c |> |  | ||||||
|     d + |  | ||||||
|     e |  | ||||||
|   `, |  | ||||||
|     "{(::add (::d (::c (::b :a))) :e)}", |  | ||||||
|   ) |   ) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -3,77 +3,83 @@ open Reducer_Peggy_TestHelpers | ||||||
| 
 | 
 | ||||||
| describe("Peggy parse type", () => { | describe("Peggy parse type", () => { | ||||||
|   describe("type of", () => { |   describe("type of", () => { | ||||||
|     testParse("p: number", "{(::$_typeOf_$ :p #number)}") |     testParse("p: number", "{(::$_typeOf_$ :p #number); (::$_endOfOuterBlock_$ () ())}") | ||||||
|   }) |   }) | ||||||
|   describe("type alias", () => { |   describe("type alias", () => { | ||||||
|     testParse("type index=number", "{(::$_typeAlias_$ #index #number)}") |     testParse( | ||||||
|  |       "type index=number", | ||||||
|  |       "{(::$_typeAlias_$ #index #number); (::$_endOfOuterBlock_$ () ())}", | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type or", () => { |   describe("type or", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: number|string", |       "answer: number|string", | ||||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string))))}", |       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type function", () => { |   describe("type function", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "f: number=>number=>number", |       "f: number=>number=>number", | ||||||
|       "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", |       "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number)))); (::$_endOfOuterBlock_$ () ())}", | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("high priority contract", () => { |   describe("high priority contract", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: number<-min<-max(100)|string", |       "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( |     testParse( | ||||||
|       "answer: number<-memberOf([1,3,5])", |       "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", () => { |   describe("low priority contract", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: number | string $ opaque", |       "answer: number | string $ opaque", | ||||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", |       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string))))); (::$_endOfOuterBlock_$ () ())}", | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type array", () => { |   describe("type array", () => { | ||||||
|     testParse("answer: [number]", "{(::$_typeOf_$ :answer (::$_typeArray_$ #number))}") |     testParse( | ||||||
|  |       "answer: [number]", | ||||||
|  |       "{(::$_typeOf_$ :answer (::$_typeArray_$ #number)); (::$_endOfOuterBlock_$ () ())}", | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type record", () => { |   describe("type record", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: {a: number, b: string}", |       "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", () => { |   describe("type constructor", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: Age(number)", |       "answer: Age(number)", | ||||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number))))}", |       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number)))); (::$_endOfOuterBlock_$ () ())}", | ||||||
|     ) |     ) | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: Complex(number, number)", |       "answer: Complex(number, number)", | ||||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number))))}", |       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number)))); (::$_endOfOuterBlock_$ () ())}", | ||||||
|     ) |     ) | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: Person({age: number, name: string})", |       "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( |     testParse( | ||||||
|       "weekend: Saturday | Sunday", |       "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", () => { |   describe("type parenthesis", () => { | ||||||
|     //$ is introduced to avoid parenthesis |     //$ is introduced to avoid parenthesis | ||||||
|     testParse( |     testParse( | ||||||
|       "answer: (number|string)<-opaque", |       "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", () => { |   describe("squiggle expressions in type contracts", () => { | ||||||
|     testParse( |     testParse( | ||||||
|       "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", |       "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 { |   } else { | ||||||
|     let a2 = |     let a2 = | ||||||
|       rExpr |       rExpr | ||||||
|       ->Result.flatMap(expr => |       ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) | ||||||
|         Expression.reduceExpression( |  | ||||||
|           expr, |  | ||||||
|           ReducerInterface_StdLib.internalStdLib, |  | ||||||
|           ExpressionValue.defaultEnvironment, |  | ||||||
|         ) |  | ||||||
|       ) |  | ||||||
|       ->Reducer_Helpers.rRemoveDefaultsInternal |       ->Reducer_Helpers.rRemoveDefaultsInternal | ||||||
|       ->ExpressionValue.toStringResultOkless |       ->ExpressionValue.toStringResultOkless | ||||||
|     (a1, a2)->expect->toEqual((answer, v)) |     (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("Peggy to Expression", () => { | ||||||
|   describe("literals operators parenthesis", () => { |   describe("literals operators parenthesis", () => { | ||||||
|     // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement |     // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement | ||||||
|     testToExpression("1", "{1}", ~v="1", ()) |     testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||||
|     testToExpression("'hello'", "{'hello'}", ~v="'hello'", ()) |     testToExpression("'hello'", "{(:$_endOfOuterBlock_$ () 'hello')}", ~v="'hello'", ()) | ||||||
|     testToExpression("true", "{true}", ~v="true", ()) |     testToExpression("true", "{(:$_endOfOuterBlock_$ () true)}", ~v="true", ()) | ||||||
|     testToExpression("1+2", "{(:add 1 2)}", ~v="3", ()) |     testToExpression("1+2", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||||
|     testToExpression("add(1,2)", "{(:add 1 2)}", ~v="3", ()) |     testToExpression("add(1,2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||||
|     testToExpression("(1)", "{1}", ()) |     testToExpression("(1)", "{(:$_endOfOuterBlock_$ () 1)}", ()) | ||||||
|     testToExpression("(1+2)", "{(:add 1 2)}", ()) |     testToExpression("(1+2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ()) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("unary", () => { |   describe("unary", () => { | ||||||
|     testToExpression("-1", "{(:unaryMinus 1)}", ~v="-1", ()) |     testToExpression("-1", "{(:$_endOfOuterBlock_$ () (:unaryMinus 1))}", ~v="-1", ()) | ||||||
|     testToExpression("!true", "{(:not true)}", ~v="false", ()) |     testToExpression("!true", "{(:$_endOfOuterBlock_$ () (:not true))}", ~v="false", ()) | ||||||
|     testToExpression("1 + -1", "{(:add 1 (:unaryMinus 1))}", ~v="0", ()) |     testToExpression("1 + -1", "{(:$_endOfOuterBlock_$ () (:add 1 (:unaryMinus 1)))}", ~v="0", ()) | ||||||
|     testToExpression("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}", ()) |     testToExpression("-a[0]", "{(:$_endOfOuterBlock_$ () (:unaryMinus (:$_atIndex_$ :a 0)))}", ()) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("multi-line", () => { |   describe("multi-line", () => { | ||||||
|     testToExpression("x=1; 2", "{(:$_let_$ :x {1}); 2}", ~v="2", ()) |     testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ()) | ||||||
|     testToExpression("x=1; y=2", "{(:$_let_$ :x {1}); (:$_let_$ :y {2})}", ~v="@{x: 1,y: 2}", ()) |     testToExpression( | ||||||
|  |       "x=1; y=2", | ||||||
|  |       "{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("variables", () => { |   describe("variables", () => { | ||||||
|     testToExpression("x = 1", "{(:$_let_$ :x {1})}", ~v="@{x: 1}", ()) |     testToExpression("x = 1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ()) | ||||||
|     testToExpression("x", "{:x}", ~v=":x", ()) //TODO: value should return error |     testToExpression("x", "{(:$_endOfOuterBlock_$ () :x)}", ~v="Error(x is not defined)", ()) //TODO: value should return error | ||||||
|     testToExpression("x = 1; x", "{(:$_let_$ :x {1}); :x}", ~v="1", ()) |     testToExpression("x = 1; x", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () :x)}", ~v="1", ()) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("functions", () => { |   describe("functions", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "identity(x) = x", |       "identity(x) = x", | ||||||
|       "{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x}))}", |       "{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{identity: lambda(x=>internal code)}", |  | ||||||
|       (), |       (), | ||||||
|     ) // Function definitions become lambda assignments |     ) // 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( |     testToExpression( | ||||||
|       "f(x) = x> 2 ? 0 : 1; f(3)", |       "f(x) = x> 2 ? 0 : 1; f(3)", | ||||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:f 3)}", |       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:$_endOfOuterBlock_$ () (:f 3))}", | ||||||
|       ~v="0", |       ~v="0", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("arrays", () => { |   describe("arrays", () => { | ||||||
|     testToExpression("[]", "{(:$_constructArray_$ ())}", ~v="[]", ()) |     testToExpression("[]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ ()))}", ~v="[]", ()) | ||||||
|     testToExpression("[0, 1, 2]", "{(:$_constructArray_$ (0 1 2))}", ~v="[0,1,2]", ()) |     testToExpression( | ||||||
|  |       "[0, 1, 2]", | ||||||
|  |       "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ (0 1 2)))}", | ||||||
|  |       ~v="[0,1,2]", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "['hello', 'world']", |       "['hello', 'world']", | ||||||
|       "{(:$_constructArray_$ ('hello' 'world'))}", |       "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ ('hello' 'world')))}", | ||||||
|       ~v="['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", () => { |   describe("records", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "{a: 1, b: 2}", |       "{a: 1, b: 2}", | ||||||
|       "{(:$_constructRecord_$ (('a' 1) ('b' 2)))}", |       "{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (('a' 1) ('b' 2))))}", | ||||||
|       ~v="{a: 1,b: 2}", |       ~v="{a: 1,b: 2}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "{1+0: 1, 2+0: 2}", |       "{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 |     ) // key can be any expression | ||||||
|     testToExpression("record.property", "{(:$_atIndex_$ :record 'property')}", ()) |     testToExpression( | ||||||
|  |       "record.property", | ||||||
|  |       "{(:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "record={property: 1}; record.property", |       "record={property: 1}; record.property", | ||||||
|       "{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_atIndex_$ :record 'property')}", |       "{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}", | ||||||
|       ~v="1", |       ~v="1", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("comments", () => { |   describe("comments", () => { | ||||||
|     testToExpression("1 # This is a line comment", "{1}", ~v="1", ()) |     testToExpression("1 # This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||||
|     testToExpression("1 // This is a line comment", "{1}", ~v="1", ()) |     testToExpression("1 // This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ()) | ||||||
|     testToExpression("1 /* This is a multi line comment */", "{1}", ~v="1", ()) |     testToExpression( | ||||||
|     testToExpression("/* This is a multi line comment */ 1", "{1}", ~v="1", ()) |       "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", () => { |   describe("ternary operator", () => { | ||||||
|     testToExpression("true ? 1 : 0", "{(:$$_ternary_$$ true 1 0)}", ~v="1", ()) |     testToExpression( | ||||||
|     testToExpression("false ? 1 : 0", "{(:$$_ternary_$$ false 1 0)}", ~v="0", ()) |       "true ? 1 : 0", | ||||||
|  |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 0))}", | ||||||
|  |       ~v="1", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|  |     testToExpression( | ||||||
|  |       "false ? 1 : 0", | ||||||
|  |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 0))}", | ||||||
|  |       ~v="0", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "true ? 1 : false ? 2 : 0", |       "true ? 1 : false ? 2 : 0", | ||||||
|       "{(:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0))}", |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0)))}", | ||||||
|       ~v="1", |       ~v="1", | ||||||
|       (), |       (), | ||||||
|     ) // nested ternary |     ) // nested ternary | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "false ? 1 : false ? 2 : 0", |       "false ? 1 : false ? 2 : 0", | ||||||
|       "{(:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0))}", |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0)))}", | ||||||
|       ~v="0", |       ~v="0", | ||||||
|       (), |       (), | ||||||
|     ) // nested ternary |     ) // nested ternary | ||||||
|  | @ -109,21 +146,21 @@ describe("Peggy to Expression", () => { | ||||||
|       testToExpression( |       testToExpression( | ||||||
|         // expression binding |         // expression binding | ||||||
|         "f(a) = a > 5 ? 1 : 0; f(6)", |         "f(a) = a > 5 ? 1 : 0; f(6)", | ||||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:f 6)}", |         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:$_endOfOuterBlock_$ () (:f 6))}", | ||||||
|         ~v="1", |         ~v="1", | ||||||
|         (), |         (), | ||||||
|       ) |       ) | ||||||
|       testToExpression( |       testToExpression( | ||||||
|         // when true binding |         // when true binding | ||||||
|         "f(a) = a > 5 ? a : 0; f(6)", |         "f(a) = a > 5 ? a : 0; f(6)", | ||||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:f 6)}", |         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:$_endOfOuterBlock_$ () (:f 6))}", | ||||||
|         ~v="6", |         ~v="6", | ||||||
|         (), |         (), | ||||||
|       ) |       ) | ||||||
|       testToExpression( |       testToExpression( | ||||||
|         // when false binding |         // when false binding | ||||||
|         "f(a) = a < 5 ? 1 : a; f(6)", |         "f(a) = a < 5 ? 1 : a; f(6)", | ||||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:f 6)}", |         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:$_endOfOuterBlock_$ () (:f 6))}", | ||||||
|         ~v="6", |         ~v="6", | ||||||
|         (), |         (), | ||||||
|       ) |       ) | ||||||
|  | @ -131,23 +168,41 @@ describe("Peggy to Expression", () => { | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("if then else", () => { |   describe("if then else", () => { | ||||||
|     testToExpression("if true then 2 else 3", "{(:$$_ternary_$$ true {2} {3})}", ()) |     testToExpression( | ||||||
|     testToExpression("if true then {2} else {3}", "{(:$$_ternary_$$ true {2} {3})}", ()) |       "if true then 2 else 3", | ||||||
|  |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|  |     testToExpression( | ||||||
|  |       "if true then {2} else {3}", | ||||||
|  |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}", | ||||||
|  |       (), | ||||||
|  |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "if false then {2} else if false then {4} else {5}", |       "if false then {2} else if false then {4} else {5}", | ||||||
|       "{(:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5}))}", |       "{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5})))}", | ||||||
|       (), |       (), | ||||||
|     ) //nested if |     ) //nested if | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("pipe", () => { |   describe("pipe", () => { | ||||||
|     testToExpression("1 -> add(2)", "{(:add 1 2)}", ~v="3", ()) |     testToExpression("1 -> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ()) | ||||||
|     testToExpression("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}", ~v="1", ()) // note that unary has higher priority naturally |     testToExpression( | ||||||
|     testToExpression("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}", ~v="9", ()) |       "-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", () => { |   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 |   // see testParse for priorities of to and credibleIntervalToDistribution | ||||||
|  | @ -157,30 +212,31 @@ describe("Peggy to Expression", () => { | ||||||
|     // Like lambdas they have a local scope. |     // Like lambdas they have a local scope. | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "y=99; x={y=1; y}", |       "y=99; x={y=1; y}", | ||||||
|       "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y})}", |       "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{x: 1,y: 99}", |  | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("lambda", () => { |   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( |     testToExpression( | ||||||
|       "f={|x| x}", |       "f={|x| x}", | ||||||
|       "{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})})}", |       "{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})}); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{f: lambda(x=>internal code)}", |  | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "f(x)=x", |       "f(x)=x", | ||||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))}", |       "{(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{f: lambda(x=>internal code)}", |  | ||||||
|       (), |       (), | ||||||
|     ) // Function definitions are lambda assignments |     ) // Function definitions are lambda assignments | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "f(x)=x ? 1 : 0", |       "f(x)=x ? 1 : 0", | ||||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)}))}", |       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)})); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{f: lambda(x=>internal code)}", |  | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  | @ -188,12 +244,12 @@ describe("Peggy to Expression", () => { | ||||||
|   describe("module", () => { |   describe("module", () => { | ||||||
|     // testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ()) |     // testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ()) | ||||||
|     // Only.test("stdlibrary", () => { |     // Only.test("stdlibrary", () => { | ||||||
|     //   ReducerInterface_StdLib.internalStdLib |     //  ReducerInterface_StdLib.internalStdLib | ||||||
|     //   ->IEvBindings |     //  ->IEvBindings | ||||||
|     //   ->InternalExpressionValue.toString |     //  ->InternalExpressionValue.toString | ||||||
|     //   ->expect |     //  ->expect | ||||||
|     //   ->toBe("") |     //  ->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", () => { |   describe("type of", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "p: number", |       "p: number", | ||||||
|       "{(:$_typeOf_$ :p #number)}", |       "{(:$_typeOf_$ :p #number); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {p: #number}}", |       // ~v="@{_typeReferences_: {p: #number}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type alias", () => { |   describe("type alias", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "type index=number", |       "type index=number", | ||||||
|       "{(:$_typeAlias_$ #index #number)}", |       "{(:$_typeAlias_$ #index #number); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeAliases_: {index: #number}}", |       // ~v="@{_typeAliases_: {index: #number}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type or", () => { |   describe("type or", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number|string|distribution", |       "answer: number|string|distribution", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution))))}", |       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution)))); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", |       // ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("type function", () => { |   describe("type function", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "f: number=>number=>number", |       "f: number=>number=>number", | ||||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number))))}", |       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number)))); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", |       // ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "f: number=>number", |       "f: number=>number", | ||||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number))))}", |       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number)))); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", |       // ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("high priority contract", () => { |   describe("high priority contract", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number<-min(1)<-max(100)|string", |       "answer: number<-min(1)<-max(100)|string", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", |       "{(:$_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'}}}", |       // ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number<-memberOf([1,3,5])", |       "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_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", |       // ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number<-min(1)", |       "answer: number<-min(1)", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1))}", |       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1)); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", |       // ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number<-max(10)", |       "answer: number<-max(10)", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10))}", |       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10)); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", |       // ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number<-min(1)<-max(10)", |       "answer: number<-min(1)<-max(10)", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10))}", |       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10)); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", |       // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number<-max(10)<-min(1)", |       "answer: number<-max(10)<-min(1)", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1))}", |       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1)); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", |       // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("low priority contract", () => { |   describe("low priority contract", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "answer: number | string $ opaque", |       "answer: number | string $ opaque", | ||||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", |       "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string))))); (:$_endOfOuterBlock_$ () ())}", | ||||||
|       ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", |       // ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", | ||||||
|       (), |       (), | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   describe("squiggle expressions in type contracts", () => { |   describe("squiggle expressions in type contracts", () => { | ||||||
|     testToExpression( |     testToExpression( | ||||||
|       "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", |       "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)))}", |       "{(:$_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]}", |       // ~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", () => { | describe("Peggy void", () => { | ||||||
|   //literal |   //literal | ||||||
|   testToExpression("()", "{()}", ~v="()", ()) |   testToExpression("()", "{(:$_endOfOuterBlock_$ () ())}", ~v="()", ()) | ||||||
|   testToExpression( |   testToExpression( | ||||||
|     "fn()=1", |     "fn()=1", | ||||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1}))}", |     "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () ())}", | ||||||
|     ~v="@{fn: lambda(_=>internal code)}", |     // ~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( |   testToExpression( | ||||||
|     "fn(a)=(); call fn(1)", |     "fn(a)=(); call fn(1)", | ||||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)})}", |     "{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)}); (:$_endOfOuterBlock_$ () ())}", | ||||||
|     ~v="@{_: (),fn: lambda(a=>internal code)}", |     // ~v="@{_: (),fn: lambda(a=>internal code)}", | ||||||
|     (), |     (), | ||||||
|   ) |   ) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| module ExpressionT = Reducer_Expression_T |  | ||||||
| module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue |  | ||||||
| module ErrorValue = Reducer_ErrorValue | module ErrorValue = Reducer_ErrorValue | ||||||
|  | module Expression = Reducer_Expression | ||||||
|  | module ExpressionT = Reducer_Expression_T | ||||||
|  | module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||||
| 
 | 
 | ||||||
| open Jest | open Jest | ||||||
| open Expect | open Expect | ||||||
|  | @ -8,30 +9,26 @@ open Expect | ||||||
| let unwrapRecord = rValue => | let unwrapRecord = rValue => | ||||||
|   rValue->Belt.Result.flatMap(value => |   rValue->Belt.Result.flatMap(value => | ||||||
|     switch value { |     switch value { | ||||||
|     | ExternalExpressionValue.EvRecord(aRecord) => Ok(aRecord) |     | InternalExpressionValue.IEvRecord(aRecord) => Ok(aRecord) | ||||||
|     | _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error |     | _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
| let expectParseToBe = (expr: string, answer: string) => | let expectParseToBe = (code: string, answer: string) => | ||||||
|   Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer) |   Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer) | ||||||
| 
 | 
 | ||||||
| let expectEvalToBe = (expr: string, answer: string) => | let expectEvalToBe = (code: string, answer: string) => | ||||||
|   Reducer.evaluate(expr) |   Expression.BackCompatible.evaluateString(code) | ||||||
|   ->Reducer_Helpers.rRemoveDefaultsExternal |   ->Reducer_Helpers.rRemoveDefaultsInternal | ||||||
|   ->ExternalExpressionValue.toStringResult |   ->InternalExpressionValue.toStringResult | ||||||
|   ->expect |   ->expect | ||||||
|   ->toBe(answer) |   ->toBe(answer) | ||||||
| 
 | 
 | ||||||
| let expectEvalError = (expr: string) => | let expectEvalError = (code: string) => | ||||||
|   Reducer.evaluate(expr)->ExternalExpressionValue.toStringResult->expect->toMatch("Error\(") |   Expression.BackCompatible.evaluateString(code) | ||||||
| 
 |   ->InternalExpressionValue.toStringResult | ||||||
| let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) => |  | ||||||
|   Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None) |  | ||||||
|   ->Reducer_Helpers.rRemoveDefaultsExternal |  | ||||||
|   ->ExternalExpressionValue.toStringResult |  | ||||||
|   ->expect |   ->expect | ||||||
|   ->toBe(answer) |   ->toMatch("Error\(") | ||||||
| 
 | 
 | ||||||
| let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) | let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) | ||||||
| let testDescriptionParseToBe = (desc, expr, answer) => | let testDescriptionParseToBe = (desc, expr, answer) => | ||||||
|  | @ -40,18 +37,12 @@ let testDescriptionParseToBe = (desc, expr, answer) => | ||||||
| let testEvalError = expr => test(expr, () => expectEvalError(expr)) | let testEvalError = expr => test(expr, () => expectEvalError(expr)) | ||||||
| let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) | let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) | ||||||
| let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => 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 = { | module MySkip = { | ||||||
|   let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) |   let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) | ||||||
|   let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(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 = { | module MyOnly = { | ||||||
|   let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) |   let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) | ||||||
|   let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(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 Jest | ||||||
| open Expect | open Expect | ||||||
| 
 | 
 | ||||||
|  | module Bindings = Reducer_Bindings | ||||||
| module BindingsReplacer = Reducer_Expression_BindingsReplacer | module BindingsReplacer = Reducer_Expression_BindingsReplacer | ||||||
| module Expression = Reducer_Expression | module Expression = Reducer_Expression | ||||||
| // module ExpressionValue = ReducerInterface.ExpressionValue |  | ||||||
| module InternalExpressionValue = ReducerInterface.InternalExpressionValue |  | ||||||
| module ExpressionWithContext = Reducer_ExpressionWithContext | module ExpressionWithContext = Reducer_ExpressionWithContext | ||||||
|  | module InternalExpressionValue = ReducerInterface.InternalExpressionValue | ||||||
| module Macro = Reducer_Expression_Macro | module Macro = Reducer_Expression_Macro | ||||||
|  | module ProjectAccessorsT = ReducerProject_ProjectAccessors_T | ||||||
| module T = Reducer_Expression_T | module T = Reducer_Expression_T | ||||||
| module Bindings = Reducer_Bindings |  | ||||||
| 
 | 
 | ||||||
| let testMacro_ = ( | let testMacro_ = ( | ||||||
|   tester, |   tester, | ||||||
|  | @ -21,8 +21,8 @@ let testMacro_ = ( | ||||||
|     expr |     expr | ||||||
|     ->Macro.expandMacroCall( |     ->Macro.expandMacroCall( | ||||||
|       bindings, |       bindings, | ||||||
|       InternalExpressionValue.defaultEnvironment, |       ProjectAccessorsT.identityAccessors, | ||||||
|       Expression.reduceExpression, |       Expression.reduceExpressionInProject, | ||||||
|     ) |     ) | ||||||
|     ->ExpressionWithContext.toStringResult |     ->ExpressionWithContext.toStringResult | ||||||
|     ->expect |     ->expect | ||||||
|  | @ -41,8 +41,8 @@ let testMacroEval_ = ( | ||||||
|     expr |     expr | ||||||
|     ->Macro.doMacroCall( |     ->Macro.doMacroCall( | ||||||
|       bindings, |       bindings, | ||||||
|       InternalExpressionValue.defaultEnvironment, |       ProjectAccessorsT.identityAccessors, | ||||||
|       Expression.reduceExpression, |       Expression.reduceExpressionInProject, | ||||||
|     ) |     ) | ||||||
|     ->InternalExpressionValue.toStringResult |     ->InternalExpressionValue.toStringResult | ||||||
|     ->expect |     ->expect | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ open Jest | ||||||
| open Expect | open Expect | ||||||
| 
 | 
 | ||||||
| let myIevEval = (aTypeSourceCode: string) => | let myIevEval = (aTypeSourceCode: string) => | ||||||
|   TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression) |   TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject) | ||||||
| let myIevEvalToString = (aTypeSourceCode: string) => | let myIevEvalToString = (aTypeSourceCode: string) => | ||||||
|   myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult |   myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult | ||||||
| 
 | 
 | ||||||
|  | @ -19,7 +19,7 @@ let myIevTest = (test, aTypeSourceCode, answer) => | ||||||
|   test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer)) |   test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer)) | ||||||
| 
 | 
 | ||||||
| let myTypeEval = (aTypeSourceCode: string) => | let myTypeEval = (aTypeSourceCode: string) => | ||||||
|   TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression) |   TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject) | ||||||
| let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult | let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult | ||||||
| 
 | 
 | ||||||
| let myTypeExpectEqual = (aTypeSourceCode, answer) => | let myTypeExpectEqual = (aTypeSourceCode, answer) => | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
|  | module Bindings = Reducer_Bindings | ||||||
|  | module ErrorValue = Reducer_ErrorValue | ||||||
| module Expression = Reducer_Expression | module Expression = Reducer_Expression | ||||||
| module ExpressionT = Reducer_Expression_T | module ExpressionT = Reducer_Expression_T | ||||||
| module ErrorValue = Reducer_ErrorValue |  | ||||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||||
| module Bindings = Reducer_Bindings | module ProjectAccessorsT = ReducerProject_ProjectAccessors_T | ||||||
| module T = Reducer_Type_T | module T = Reducer_Type_T | ||||||
| module TypeChecker = Reducer_Type_TypeChecker | module TypeChecker = Reducer_Type_TypeChecker | ||||||
| 
 | 
 | ||||||
|  | @ -13,10 +14,10 @@ let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): re | ||||||
|   'v, |   'v, | ||||||
|   ErrorValue.t, |   ErrorValue.t, | ||||||
| > => { | > => { | ||||||
|   let reducerFn = Expression.reduceExpression |   let reducerFn = Expression.reduceExpressionInProject | ||||||
|   let rResult = |   let rResult = | ||||||
|     Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => |     Expression.BackCompatible.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||||
|       reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) |       reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors) | ||||||
|     ) |     ) | ||||||
|   rResult->Belt.Result.flatMap(result => |   rResult->Belt.Result.flatMap(result => | ||||||
|     switch result { |     switch result { | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||||
| module Bindings = Reducer_Bindings | module Bindings = Reducer_Bindings | ||||||
| module T = Reducer_Type_T | module T = Reducer_Type_T | ||||||
| module TypeChecker = Reducer_Type_TypeChecker | module TypeChecker = Reducer_Type_TypeChecker | ||||||
|  | module ProjectAccessorsT = ReducerProject_ProjectAccessors_T | ||||||
| 
 | 
 | ||||||
| open Jest | open Jest | ||||||
| open Expect | open Expect | ||||||
|  | @ -16,10 +17,10 @@ let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result< | ||||||
|   'v, |   'v, | ||||||
|   ErrorValue.t, |   ErrorValue.t, | ||||||
| > => { | > => { | ||||||
|   let reducerFn = Expression.reduceExpression |   let reducerFn = Expression.reduceExpressionInProject | ||||||
|   let rResult = |   let rResult = | ||||||
|     Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => |     Expression.BackCompatible.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||||
|       reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) |       reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors) | ||||||
|     ) |     ) | ||||||
|   rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn)) |   rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,8 +4,11 @@ open Expect | ||||||
| module DispatchT = Reducer_Dispatch_T | module DispatchT = Reducer_Dispatch_T | ||||||
| module Expression = Reducer_Expression | module Expression = Reducer_Expression | ||||||
| module ExpressionT = Reducer_Expression_T | 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 TypeChecker = Reducer_Type_TypeChecker | ||||||
|  | module TypeCompile = Reducer_Type_Compile | ||||||
|  | 
 | ||||||
| open ReducerInterface_InternalExpressionValue | open ReducerInterface_InternalExpressionValue | ||||||
| 
 | 
 | ||||||
| type errorValue = Reducer_ErrorValue.errorValue | 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. | // 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. | // Otherwise we return None so that the call chain can continue. | ||||||
| // So we want to build a function like | // 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. | // 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. | // 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. | // 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 |   // Let's have a pure implementations | ||||||
|   module Implementation = { |   module Implementation = { | ||||||
|     let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b) |     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 |   // Let's bridge the pure implementation to expression values | ||||||
|   module Bridge = { |   module Bridge = { | ||||||
|     let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => { |     let stringConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => { | ||||||
|       let (a, b) = extractStringString(args) |       let (a, b) = extractStringString(args) | ||||||
|       Implementation.stringConcat(a, b)->IEvString->Ok |       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) |       let (a, b) = extractArrayArray(args) | ||||||
|       Implementation.arrayConcat(a, b)->IEvArray->Ok |       Implementation.arrayConcat(a, b)->IEvArray->Ok | ||||||
|     } |     } | ||||||
|     let plot: DispatchT.genericIEvFunction = (args, _environment) => { |     let plot: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => { | ||||||
|       switch args { |       switch args { | ||||||
|       // Just assume that we are doing the business of extracting and converting the deep record |       // 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 |       | [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 | // Exactly the same as the one used in real life | ||||||
| let _dispatch = ( | let _dispatch = ( | ||||||
|   call: functionCall, |   call: functionCall, | ||||||
|   environment, |   accessors: ProjectAccessorsT.t, | ||||||
|   reducer: Reducer_Expression_T.reducerFn, |   reducer: ProjectReducerFnT.t, | ||||||
|   chain, |   chain, | ||||||
| ): result<internalExpressionValue, 'e> => { | ): result<internalExpressionValue, 'e> => { | ||||||
|   let dispatchChainPiece = makeMyDispatchChainPiece(reducer) |   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? | // What is important about this implementation? | ||||||
|  | @ -112,12 +116,12 @@ let _dispatch = ( | ||||||
| // B) Complicated recursive record types are not a problem. | // B) Complicated recursive record types are not a problem. | ||||||
| 
 | 
 | ||||||
| describe("Type Dispatch", () => { | describe("Type Dispatch", () => { | ||||||
|   let reducerFn = Expression.reduceExpression |   let reducerFn = Expression.reduceExpressionInProject | ||||||
|   let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn) |   let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn) | ||||||
|   test("stringConcat", () => { |   test("stringConcat", () => { | ||||||
|     let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")]) |     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")))) |     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 | open Reducer_TestHelpers | ||||||
| 
 | 
 | ||||||
| describe("Parse function assignment", () => { | describe("Parse function assignment", () => { | ||||||
|   testParseToBe("f(x)=x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))})") |   testParseToBe( | ||||||
|   testParseToBe("f(x)=2*x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)}))})") |     "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 |   //MathJs does not allow blocks in function definitions | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -39,33 +39,27 @@ describe("symbol not defined", () => { | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| describe("call and bindings", () => { | 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(x)=x+1; f(1)", "Ok(2)") | ||||||
|   testEvalToBe("f=1;y=2", "Ok(@{f: 1,y: 2})") |   testEvalToBe("f=1;y=2", "Ok(())") | ||||||
|   testEvalToBe("f(x)=x+1; y=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2})") |   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); 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; y=f(1); z=f(1); z", "Ok(2)") | ||||||
|   testEvalToBe( |   testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)") | ||||||
|     "f(x)=x+1; g(x)=f(x)+1", |  | ||||||
|     "Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code)})", |  | ||||||
|   ) |  | ||||||
|   testParseToBe( |   testParseToBe( | ||||||
|     "f=99; g(x)=f; g(2)", |     "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=99; g(x)=f; g(2)", "Ok(99)") | ||||||
|   testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)") |   testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)") | ||||||
|   testEvalToBe( |   testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)") | ||||||
|     "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; g(2)", "Ok(4)") |   testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(2)", "Ok(4)") | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| describe("function tricks", () => { | describe("function tricks", () => { | ||||||
|   testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed |   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)=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); 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 |   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))") |   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", () => { | describe("lambda in structures", () => { | ||||||
|   testEvalToBe( |   testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok(())") | ||||||
|     "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]; z[0]", "Ok(lambda(x,y=>internal code))") |   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("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)})") |   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 | open Reducer_TestHelpers | ||||||
| 
 | 
 | ||||||
| describe("Parse ternary operator", () => { | 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", () => { | describe("Evaluate ternary operator", () => { | ||||||
|  |  | ||||||
|  | @ -48,7 +48,7 @@ describe("eval", () => { | ||||||
|     testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") |     testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") | ||||||
|     testEvalError("1; x=1") |     testEvalError("1; x=1") | ||||||
|     testEvalError("1; 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("parse on distribution functions", () => { | ||||||
|   describe("power", () => { |   describe("power", () => { | ||||||
|     testParse("normal(5,2) ^ normal(5,1)", "Ok({(:pow (:normal 5 2) (:normal 5 1))})") |     testParse( | ||||||
|     testParse("3 ^ normal(5,1)", "Ok({(:pow 3 (:normal 5 1))})") |       "normal(5,2) ^ normal(5,1)", | ||||||
|     testParse("normal(5,2) ^ 3", "Ok({(:pow (:normal 5 2) 3)})") |       "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", () => { |   describe("subtraction", () => { | ||||||
|     testParse("10 - normal(5,1)", "Ok({(:subtract 10 (:normal 5 1))})") |     testParse("10 - normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:subtract 10 (:normal 5 1)))})") | ||||||
|     testParse("normal(5,1) - 10", "Ok({(:subtract (:normal 5 1) 10)})") |     testParse("normal(5,1) - 10", "Ok({(:$_endOfOuterBlock_$ () (:subtract (:normal 5 1) 10))})") | ||||||
|   }) |   }) | ||||||
|   describe("pointwise arithmetic expressions", () => { |   describe("pointwise arithmetic expressions", () => { | ||||||
|     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") |     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") | ||||||
|     testParse( |     testParse( | ||||||
|       ~skip=true, |       ~skip=true, | ||||||
|       "normal(5,2) .- normal(5,1)", |       "normal(5,2) .- normal(5,1)", | ||||||
|       "Ok((:$$_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))})" |       // 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( | ||||||
|     testParse("normal(5,2) ./ normal(5,1)", "Ok({(:dotDivide (:normal 5 2) (:normal 5 1))})") |       "normal(5,2) .* normal(5,1)", | ||||||
|     testParse("normal(5,2) .^ normal(5,1)", "Ok({(:dotPow (: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", () => { |   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", () => { |   describe("pointwise adding two normals", () => { | ||||||
|     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") |     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| open ReducerInterface.ExternalExpressionValue | open ReducerInterface.InternalExpressionValue | ||||||
| open Jest | open Jest | ||||||
| open Expect | open Expect | ||||||
| 
 | 
 | ||||||
| describe("ExpressionValue", () => { | 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", () => |   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 Expect | ||||||
| open Reducer_TestHelpers | open Reducer_TestHelpers | ||||||
| 
 | 
 | ||||||
| let expectEvalToBeOk = (expr: string) => | let expectEvalToBeOk = (code: string) => | ||||||
|   Reducer.evaluate(expr)->Reducer_Helpers.rRemoveDefaultsExternal->E.R.isOk->expect->toBe(true) |   Reducer_Expression.BackCompatible.evaluateString(code) | ||||||
|  |   ->Reducer_Helpers.rRemoveDefaultsInternal | ||||||
|  |   ->E.R.isOk | ||||||
|  |   ->expect | ||||||
|  |   ->toBe(true) | ||||||
| 
 | 
 | ||||||
| let registry = FunctionRegistry_Library.registry | let registry = FunctionRegistry_Library.registry | ||||||
| let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry)) | let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry)) | ||||||
|  | @ -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.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.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("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( |     testEvalToBe( | ||||||
|       "addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))", |       "addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))", | ||||||
|       "Ok([2,3,4,5,6,7])", |       "Ok([2,3,4,5,6,7])", | ||||||
|  | @ -91,8 +101,8 @@ describe("FunctionRegistry Library", () => { | ||||||
|       ((fn, example)) => { |       ((fn, example)) => { | ||||||
|         let responseType = |         let responseType = | ||||||
|           example |           example | ||||||
|           ->Reducer.evaluate |           ->Reducer_Expression.BackCompatible.evaluateString | ||||||
|           ->E.R2.fmap(ReducerInterface_InternalExpressionValue.externalValueToValueType) |           ->E.R2.fmap(ReducerInterface_InternalExpressionValue.valueToValueType) | ||||||
|         let expectedOutputType = fn.output |> E.O.toExn("") |         let expectedOutputType = fn.output |> E.O.toExn("") | ||||||
|         expect(responseType)->toEqual(Ok(expectedOutputType)) |         expect(responseType)->toEqual(Ok(expectedOutputType)) | ||||||
|       }, |       }, | ||||||
|  |  | ||||||
|  | @ -1,10 +1,5 @@ | ||||||
| import { | import { run, SqProject, SqValue, SqValueTag } from "../../src/js"; | ||||||
|   Distribution, | import { testRun } from "./TestHelpers"; | ||||||
|   resultMap, |  | ||||||
|   defaultBindings, |  | ||||||
|   mergeBindings, |  | ||||||
| } from "../../src/js/index"; |  | ||||||
| import { testRun, testRunPartial } from "./TestHelpers"; |  | ||||||
| 
 | 
 | ||||||
| function Ok<b>(x: b) { | function Ok<b>(x: b) { | ||||||
|   return { tag: "Ok", value: x }; |   return { tag: "Ok", value: x }; | ||||||
|  | @ -12,97 +7,57 @@ function Ok<b>(x: b) { | ||||||
| 
 | 
 | ||||||
| describe("Simple calculations and results", () => { | describe("Simple calculations and results", () => { | ||||||
|   test("mean(normal(5,2))", () => { |   test("mean(normal(5,2))", () => { | ||||||
|     expect(testRun("mean(normal(5,2))")).toEqual({ |     const result = testRun("mean(normal(5,2))"); // FIXME
 | ||||||
|       tag: "number", |     expect(result.toString()).toEqual("5"); | ||||||
|       value: 5, |  | ||||||
|     }); |  | ||||||
|   }); |   }); | ||||||
|   test("10+10", () => { |   test("10+10", () => { | ||||||
|     let foo = testRun("10 + 10"); |     let result = testRun("10 + 10"); | ||||||
|     expect(foo).toEqual({ tag: "number", value: 20 }); |     expect(result.toString()).toEqual("20"); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| describe("Log function", () => { | describe("Log function", () => { | ||||||
|   test("log(1) = 0", () => { |   test("log(1) = 0", () => { | ||||||
|     let foo = testRun("log(1)"); |     let foo = testRun("log(1)"); | ||||||
|     expect(foo).toEqual({ tag: "number", value: 0 }); |     expect(foo.toString()).toEqual("0"); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe("Array", () => { | describe("Array", () => { | ||||||
|   test("nested Array", () => { |   test("nested Array", () => { | ||||||
|     expect(testRun("[[1]]")).toEqual({ |     expect(testRun("[[ 1 ]]").toString()).toEqual("[[1]]"); | ||||||
|       tag: "array", |  | ||||||
|       value: [ |  | ||||||
|         { |  | ||||||
|           tag: "array", |  | ||||||
|           value: [ |  | ||||||
|             { |  | ||||||
|               tag: "number", |  | ||||||
|               value: 1, |  | ||||||
|             }, |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|       ], |  | ||||||
|     }); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe("Record", () => { | describe("Record", () => { | ||||||
|   test("Return record", () => { |   test("Return record", () => { | ||||||
|     expect(testRun("{a: 1}")).toEqual({ |     expect(testRun("{a:1}").toString()).toEqual("{a: 1}"); | ||||||
|       tag: "record", |  | ||||||
|       value: { |  | ||||||
|         a: { |  | ||||||
|           tag: "number", |  | ||||||
|           value: 1, |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|     }); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe("Partials", () => { | describe("Continues", () => { | ||||||
|   test("Can pass variables between partials and cells", () => { |   test("Bindings from continues are accessible", () => { | ||||||
|     let bindings = testRunPartial(`x = 5`); |     const project = SqProject.create(); | ||||||
|     let bindings2 = testRunPartial(`y = x + 2`, bindings); |     project.setSource("p1", "x = 5"); | ||||||
|     expect(testRun(`y + 3`, bindings2)).toEqual({ |     project.setSource("p2", "y = x + 2"); | ||||||
|       tag: "number", |     project.setSource("main", "y + 3"); | ||||||
|       value: 10, |     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", () => { |   test("Can merge bindings from three partials", () => { | ||||||
|     let bindings1 = testRunPartial(`x = 1`); |     const project = SqProject.create(); | ||||||
|     let bindings2 = testRunPartial(`y = 2`); |     project.setSource("p1", "x = 1"); | ||||||
|     let bindings3 = testRunPartial(`z = 3`); |     project.setSource("p2", "y = 2"); | ||||||
|     expect( |     project.setSource("p3", "z = 3"); | ||||||
|       testRun(`x + y + z`, mergeBindings([bindings1, bindings2, bindings3])) |     project.setSource("main", "x + y + z"); | ||||||
|     ).toEqual({ |     project.setContinues("main", ["p1", "p2", "p3"]); | ||||||
|       tag: "number", |     project.run("main"); | ||||||
|       value: 6, |     const result = project.getResult("main"); | ||||||
|     }); |     expect(result.tag).toEqual("Ok"); | ||||||
|   }); |     expect(result.value.toString()).toEqual("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, |  | ||||||
|     }); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | @ -112,50 +67,62 @@ describe("Distribution", () => { | ||||||
|   let env = { sampleCount: 8, xyPointLength: 100 }; |   let env = { sampleCount: 8, xyPointLength: 100 }; | ||||||
|   let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30]; |   let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30]; | ||||||
|   let dist1SampleCount = dist1Samples.length; |   let dist1SampleCount = dist1Samples.length; | ||||||
|   let dist = new Distribution( | 
 | ||||||
|     { tag: "SampleSet", value: [3, 4, 5, 6, 6, 7, 10, 15, 30] }, |   const buildDist = (samples: number[]) => { | ||||||
|     env |     const src = `SampleSet.fromList([${samples.join(",")}])`; | ||||||
|   ); |     const { result } = run(src, { | ||||||
|   let dist2 = new Distribution( |       environment: env, | ||||||
|     { tag: "SampleSet", value: [20, 22, 24, 29, 30, 35, 38, 44, 52] }, |     }); | ||||||
|     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", () => { |   test("mean", () => { | ||||||
|     expect(dist.mean().value).toBeCloseTo(9.5555555); |     expect(dist.mean(env).value).toBeCloseTo(9.5555555); | ||||||
|   }); |   }); | ||||||
|   test("pdf", () => { |   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", () => { |   test("cdf", () => { | ||||||
|     expect(dist.cdf(5.0).value).toBeCloseTo( |     expect(dist.cdf(env, 5.0).value).toBeCloseTo( | ||||||
|       dist1Samples.filter((x) => x <= 5).length / dist1SampleCount, |       dist1Samples.filter((x) => x <= 5).length / dist1SampleCount, | ||||||
|       1 |       1 | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|   test("inv", () => { |   test("inv", () => { | ||||||
|     expect(dist.inv(0.5).value).toBeCloseTo(6); |     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("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁")); |  | ||||||
|   }); |   }); | ||||||
|  |   // 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 * as fc from "fast-check"; | ||||||
| import { testRun } from "./TestHelpers"; | import { testRun } from "./TestHelpers"; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,3 @@ | ||||||
| import { |  | ||||||
|   run, |  | ||||||
|   squiggleExpression, |  | ||||||
|   errorValue, |  | ||||||
|   result, |  | ||||||
| } from "../../src/js/index"; |  | ||||||
| import { testRun } from "./TestHelpers"; | import { testRun } from "./TestHelpers"; | ||||||
| import * as fc from "fast-check"; | import * as fc from "fast-check"; | ||||||
| 
 | 
 | ||||||
|  | @ -42,9 +36,9 @@ describe("Squiggle's parser is whitespace insensitive", () => { | ||||||
|         whitespaceGen(), |         whitespaceGen(), | ||||||
|         whitespaceGen(), |         whitespaceGen(), | ||||||
|         (a, b, c, d, e, f, g, h) => { |         (a, b, c, d, e, f, g, h) => { | ||||||
|           expect(testRun(squiggleString(a, b, c, d, e, f, g, h))).toEqual( |           expect( | ||||||
|             squiggleOutput |             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, SqValueTag } from "./TestHelpers"; | ||||||
| import { testRun, expectErrorToBeBounded } from "./TestHelpers"; |  | ||||||
| import * as fc from "fast-check"; | import * as fc from "fast-check"; | ||||||
| 
 | 
 | ||||||
| describe("Mean of mixture is weighted average of means", () => { | 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 lognormalWeight = y / weightDenom; | ||||||
|           let betaMean = 1 / (1 + b / a); |           let betaMean = 1 / (1 + b / a); | ||||||
|           let lognormalMean = m + s ** 2 / 2; |           let lognormalMean = m + s ** 2 / 2; | ||||||
|           if (res.tag == "number") { |           if (res.tag === SqValueTag.Number) { | ||||||
|             expectErrorToBeBounded( |             expectErrorToBeBounded( | ||||||
|               res.value, |               res.value, | ||||||
|               betaWeight * betaMean + lognormalWeight * lognormalMean, |               betaWeight * betaMean + lognormalWeight * lognormalMean, | ||||||
|  |  | ||||||
|  | @ -1,12 +1,13 @@ | ||||||
| import { Distribution } from "../../src/js/index"; | import { expectErrorToBeBounded, testRun, SqValueTag } from "./TestHelpers"; | ||||||
| import { expectErrorToBeBounded, failDefault, testRun } from "./TestHelpers"; |  | ||||||
| import * as fc from "fast-check"; | import * as fc from "fast-check"; | ||||||
| 
 | 
 | ||||||
| // Beware: float64Array makes it appear in an infinite loop.
 | // Beware: float64Array makes it appear in an infinite loop.
 | ||||||
| let arrayGen = () => | let arrayGen = () => | ||||||
|   fc |   fc | ||||||
|     .float32Array({ |     .float64Array({ | ||||||
|       minLength: 10, |       minLength: 10, | ||||||
|  |       max: 999999999999999, | ||||||
|  |       min: -999999999999999, | ||||||
|       maxLength: 10000, |       maxLength: 10000, | ||||||
|       noDefaultInfinity: true, |       noDefaultInfinity: true, | ||||||
|       noNaN: true, |       noNaN: true, | ||||||
|  | @ -14,36 +15,41 @@ let arrayGen = () => | ||||||
|     .filter( |     .filter( | ||||||
|       (xs_) => Math.min(...Array.from(xs_)) != Math.max(...Array.from(xs_)) |       (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.
 |   // We should fix this.
 | ||||||
|   test.skip("'s codomain is bounded above", () => { |   test.skip("'s codomain is bounded above", () => { | ||||||
|     fc.assert( |     fc.assert( | ||||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { |       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||||
|         let xs = Array.from(xs_); |         let xs = Array.from(xs_); | ||||||
|         // Should compute with squiggle strings once interpreter has `sample`
 |         // Should compute with squiggle strings once interpreter has `sample`
 | ||||||
|         let dist = new Distribution( |         let result = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let cdfValue = result.cdf(env, x).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |  | ||||||
|         ); |  | ||||||
|         let cdfValue = dist.cdf(x).value; |  | ||||||
|         let epsilon = 5e-7; |         let epsilon = 5e-7; | ||||||
|         expect(cdfValue).toBeLessThanOrEqual(1 + epsilon); |         expect(cdfValue).toBeLessThanOrEqual(1 + epsilon); | ||||||
|       }) |       }) | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test("'s codomain is bounded below", () => { |   test.skip("'s codomain is bounded below", () => { | ||||||
|     fc.assert( |     fc.assert( | ||||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { |       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||||
|         let xs = Array.from(xs_); |         let xs = Array.from(xs_); | ||||||
|         // Should compute with squiggle strings once interpreter has `sample`
 |         // Should compute with squiggle strings once interpreter has `sample`
 | ||||||
|         let dist = new Distribution( |         let result = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let cdfValue = result.cdf(env, x).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |  | ||||||
|         ); |  | ||||||
|         let cdfValue = dist.cdf(x).value; |  | ||||||
|         expect(cdfValue).toBeGreaterThanOrEqual(0); |         expect(cdfValue).toBeGreaterThanOrEqual(0); | ||||||
|       }) |       }) | ||||||
|     ); |     ); | ||||||
|  | @ -57,11 +63,8 @@ describe("cumulative density function", () => { | ||||||
|         let xs = Array.from(xs_); |         let xs = Array.from(xs_); | ||||||
|         let max = Math.max(...xs); |         let max = Math.max(...xs); | ||||||
|         // Should compute with squiggle strings once interpreter has `sample`
 |         // Should compute with squiggle strings once interpreter has `sample`
 | ||||||
|         let dist = new Distribution( |         let result = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let cdfValue = result.cdf(env, max).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |  | ||||||
|         ); |  | ||||||
|         let cdfValue = dist.cdf(max).value; |  | ||||||
|         expect(cdfValue).toBeCloseTo(1.0, 2); |         expect(cdfValue).toBeCloseTo(1.0, 2); | ||||||
|       }) |       }) | ||||||
|     ); |     ); | ||||||
|  | @ -74,11 +77,8 @@ describe("cumulative density function", () => { | ||||||
|         let xs = Array.from(xs_); |         let xs = Array.from(xs_); | ||||||
|         let min = Math.min(...xs); |         let min = Math.min(...xs); | ||||||
|         // Should compute with squiggle strings once interpreter has `sample`
 |         // Should compute with squiggle strings once interpreter has `sample`
 | ||||||
|         let dist = new Distribution( |         let result = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let cdfValue = result.cdf(env, min).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |  | ||||||
|         ); |  | ||||||
|         let cdfValue = dist.cdf(min).value; |  | ||||||
|         let max = Math.max(...xs); |         let max = Math.max(...xs); | ||||||
|         let epsilon = 5e-3; |         let epsilon = 5e-3; | ||||||
|         if (max - min < epsilon) { |         if (max - min < epsilon) { | ||||||
|  | @ -95,11 +95,8 @@ describe("cumulative density function", () => { | ||||||
|     fc.assert( |     fc.assert( | ||||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { |       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||||
|         let xs = Array.from(xs_); |         let xs = Array.from(xs_); | ||||||
|         let dist = new Distribution( |         let dist = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let cdfValue = dist.cdf(env, x).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |  | ||||||
|         ); |  | ||||||
|         let cdfValue = dist.cdf(x).value; |  | ||||||
|         let max = Math.max(...xs); |         let max = Math.max(...xs); | ||||||
|         if (x > max) { |         if (x > max) { | ||||||
|           let epsilon = (x - max) / x; |           let epsilon = (x - max) / x; | ||||||
|  | @ -107,21 +104,18 @@ describe("cumulative density function", () => { | ||||||
|         } else if (typeof cdfValue == "number") { |         } else if (typeof cdfValue == "number") { | ||||||
|           expect(Math.round(1e5 * cdfValue) / 1e5).toBeLessThanOrEqual(1); |           expect(Math.round(1e5 * cdfValue) / 1e5).toBeLessThanOrEqual(1); | ||||||
|         } else { |         } 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.assert( | ||||||
|       fc.property(arrayGen(), fc.float(), (xs_, x) => { |       fc.property(arrayGen(), fc.float(), (xs_, x) => { | ||||||
|         let xs = Array.from(xs_); |         let xs = Array.from(xs_); | ||||||
|         let dist = new Distribution( |         let dist = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let cdfValue = dist.cdf(env, x).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |  | ||||||
|         ); |  | ||||||
|         let cdfValue = dist.cdf(x).value; |  | ||||||
|         expect(cdfValue).toBeGreaterThanOrEqual(0); |         expect(cdfValue).toBeGreaterThanOrEqual(0); | ||||||
|       }) |       }) | ||||||
|     ); |     ); | ||||||
|  | @ -130,7 +124,7 @@ describe("cumulative density function", () => { | ||||||
| 
 | 
 | ||||||
| // I no longer believe this is true.
 | // I no longer believe this is true.
 | ||||||
| describe("probability density function", () => { | 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", () => { |   test.skip("assigns to the max at most the weight of the mean", () => { | ||||||
|     fc.assert( |     fc.assert( | ||||||
|  | @ -139,12 +133,9 @@ describe("probability density function", () => { | ||||||
|         let max = Math.max(...xs); |         let max = Math.max(...xs); | ||||||
|         let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length; |         let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length; | ||||||
|         // Should be from squiggleString once interpreter exposes sampleset
 |         // Should be from squiggleString once interpreter exposes sampleset
 | ||||||
|         let dist = new Distribution( |         let dist = makeSampleSet(xs); | ||||||
|           { tag: "SampleSet", value: xs }, |         let pdfValueMean = dist.pdf(env, mean).value; | ||||||
|           { sampleCount: n, xyPointLength: 100 } |         let pdfValueMax = dist.pdf(env, max).value; | ||||||
|         ); |  | ||||||
|         let pdfValueMean = dist.pdf(mean).value; |  | ||||||
|         let pdfValueMax = dist.pdf(max).value; |  | ||||||
|         if (typeof pdfValueMean == "number" && typeof pdfValueMax == "number") { |         if (typeof pdfValueMean == "number" && typeof pdfValueMax == "number") { | ||||||
|           expect(pdfValueMax).toBeLessThanOrEqual(pdfValueMean); |           expect(pdfValueMax).toBeLessThanOrEqual(pdfValueMean); | ||||||
|         } else { |         } else { | ||||||
|  | @ -164,11 +155,9 @@ describe("mean is mean", () => { | ||||||
|         (xs_) => { |         (xs_) => { | ||||||
|           let xs = Array.from(xs_); |           let xs = Array.from(xs_); | ||||||
|           let n = xs.length; |           let n = xs.length; | ||||||
|           let dist = new Distribution( |           let dist = makeSampleSet(xs); | ||||||
|             { tag: "SampleSet", value: xs }, |           let myEnv = { sampleCount: 2 * n, xyPointLength: 4 * n }; | ||||||
|             { sampleCount: 2 * n, xyPointLength: 4 * n } |           let mean = dist.mean(myEnv); | ||||||
|           ); |  | ||||||
|           let mean = dist.mean(); |  | ||||||
|           if (typeof mean.value == "number") { |           if (typeof mean.value == "number") { | ||||||
|             expectErrorToBeBounded( |             expectErrorToBeBounded( | ||||||
|               mean.value, |               mean.value, | ||||||
|  | @ -177,7 +166,7 @@ describe("mean is mean", () => { | ||||||
|               1 |               1 | ||||||
|             ); |             ); | ||||||
|           } else { |           } else { | ||||||
|             failDefault(); |             fail(); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       ) |       ) | ||||||
|  | @ -191,11 +180,9 @@ describe("mean is mean", () => { | ||||||
|         (xs_) => { |         (xs_) => { | ||||||
|           let xs = Array.from(xs_); |           let xs = Array.from(xs_); | ||||||
|           let n = xs.length; |           let n = xs.length; | ||||||
|           let dist = new Distribution( |           let dist = makeSampleSet(xs); | ||||||
|             { tag: "SampleSet", value: xs }, |           let myEnv = { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n }; | ||||||
|             { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n } |           let mean = dist.mean(myEnv); | ||||||
|           ); |  | ||||||
|           let mean = dist.mean(); |  | ||||||
|           if (typeof mean.value == "number") { |           if (typeof mean.value == "number") { | ||||||
|             expectErrorToBeBounded( |             expectErrorToBeBounded( | ||||||
|               mean.value, |               mean.value, | ||||||
|  | @ -204,7 +191,7 @@ describe("mean is mean", () => { | ||||||
|               1 |               1 | ||||||
|             ); |             ); | ||||||
|           } else { |           } else { | ||||||
|             failDefault(); |             fail(); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       ) |       ) | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import * as fc from "fast-check"; | ||||||
| describe("Scalar manipulation is well-modeled by javascript math", () => { | describe("Scalar manipulation is well-modeled by javascript math", () => { | ||||||
|   test("in the case of natural logarithms", () => { |   test("in the case of natural logarithms", () => { | ||||||
|     fc.assert( |     fc.assert( | ||||||
|       fc.property(fc.integer(), (x) => { |       fc.property(fc.nat(), (x) => { | ||||||
|         let squiggleString = `log(${x})`; |         let squiggleString = `log(${x})`; | ||||||
|         let squiggleResult = testRun(squiggleString); |         let squiggleResult = testRun(squiggleString); | ||||||
|         if (x == 0) { |         if (x == 0) { | ||||||
|  |  | ||||||
|  | @ -1,68 +1,31 @@ | ||||||
| import { | import { run, SqValueTag } from "../../src/js"; | ||||||
|   run, | export { SqValueTag }; | ||||||
|   runPartial, |  | ||||||
|   bindings, |  | ||||||
|   squiggleExpression, |  | ||||||
|   errorValueToString, |  | ||||||
|   defaultImports, |  | ||||||
|   defaultBindings, |  | ||||||
|   jsImports, |  | ||||||
| } from "../../src/js/index"; |  | ||||||
| 
 | 
 | ||||||
| export function testRun( | expect.extend({ | ||||||
|   x: string, |   toEqualSqValue(x, y) { | ||||||
|   bindings: bindings = defaultBindings, |     // hack via https://github.com/facebook/jest/issues/10329#issuecomment-820656061
 | ||||||
|   imports: jsImports = defaultImports |     const { getMatchers } = require("expect/build/jestMatchersObject"); | ||||||
| ): squiggleExpression { |     return getMatchers().toEqual.call(this, x.toString(), y.toString()); | ||||||
|   let squiggleResult = run( |   }, | ||||||
|     x, | }); | ||||||
|     bindings, | 
 | ||||||
|     { | export function testRun(x: string) { | ||||||
|  |   const { result, bindings } = run(x, { | ||||||
|  |     environment: { | ||||||
|       sampleCount: 1000, |       sampleCount: 1000, | ||||||
|       xyPointLength: 100, |       xyPointLength: 100, | ||||||
|     }, |     }, | ||||||
|     imports |   }); | ||||||
|   ); | 
 | ||||||
|   if (squiggleResult.tag === "Ok") { |   if (result.tag === "Ok") { | ||||||
|     return squiggleResult.value; |     return result.value; | ||||||
|   } else { |   } else { | ||||||
|     throw new Error( |     throw new Error( | ||||||
|       `Expected squiggle expression to evaluate but got error: ${errorValueToString( |       `Expected squiggle expression to evaluate but got error: ${result.value}` | ||||||
|         squiggleResult.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
 |  * 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. |  * absolute error when numerical stability concerns make me not want to compute relative error. | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@quri/squiggle-lang", |   "name": "@quri/squiggle-lang", | ||||||
|   "version": "0.3.1", |   "version": "0.4.0-alpha.0", | ||||||
|   "homepage": "https://squiggle-language.com", |   "homepage": "https://squiggle-language.com", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "scripts": { |   "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 { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen"; | ||||||
| import type { | import { SqProject } from "./SqProject"; | ||||||
|   environment, | import { SqValue, SqValueTag } from "./SqValue"; | ||||||
|   expressionValue, | export { SqValueLocation } from "./SqValueLocation"; | ||||||
|   externalBindings, | export { result } from "../rescript/ForTS/ForTS_Result_tag"; | ||||||
|   errorValue, | export { SqDistribution, SqDistributionTag } from "./SqDistribution"; | ||||||
| } from "../rescript/TypescriptInterface.gen"; | export { SqDistributionError } from "./SqDistributionError"; | ||||||
| import { | export { SqRecord } from "./SqRecord"; | ||||||
|   defaultEnvironment, | export { SqLambda } from "./SqLambda"; | ||||||
|   evaluatePartialUsingExternalBindings, | export { SqProject }; | ||||||
|   evaluateUsingOptions, | export { SqValue, SqValueTag }; | ||||||
|   foreignFunctionInterface, |  | ||||||
| } from "../rescript/TypescriptInterface.gen"; |  | ||||||
| export { | export { | ||||||
|   makeSampleSetDist, |   environment, | ||||||
|   errorValueToString, |   defaultEnvironment, | ||||||
|   distributionErrorToString, | } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen"; | ||||||
| } from "../rescript/TypescriptInterface.gen"; | export { SqError } from "./SqError"; | ||||||
| export type { | export { SqShape } from "./SqPointSetDist"; | ||||||
|   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"; |  | ||||||
| 
 | 
 | ||||||
| export { Distribution, resultMap, defaultEnvironment }; | export { resultMap } from "./types"; | ||||||
| export type { result, shape, environment, lambdaValue, squiggleExpression }; |  | ||||||
| 
 | 
 | ||||||
| export { parse } from "./parse"; | export const run = ( | ||||||
| 
 |   code: string, | ||||||
| export let defaultSamplingInputs: environment = { |   options?: { | ||||||
|   sampleCount: 1000, |     environment?: environment; | ||||||
|   xyPointLength: 1000, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 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; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | ) => { | ||||||
|  |   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 }; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @ -1,23 +1,23 @@ | ||||||
| import { | // import {
 | ||||||
|   errorValue, | //   errorValue,
 | ||||||
|   parse as parseRescript, | //   parse as parseRescript,
 | ||||||
| } from "../rescript/TypescriptInterface.gen"; | // } from "../rescript/TypescriptInterface.gen";
 | ||||||
| import { result } from "./types"; | // import { result } from "./types";
 | ||||||
| import { AnyPeggyNode } from "../rescript/Reducer/Reducer_Peggy/helpers"; | // import { AnyPeggyNode } from "../rescript/Reducer/Reducer_Peggy/helpers";
 | ||||||
| 
 | 
 | ||||||
| export function parse( | // export function parse(
 | ||||||
|   squiggleString: string | //   squiggleString: string
 | ||||||
| ): result<AnyPeggyNode, Extract<errorValue, { tag: "RESyntaxError" }>> { | // ): result<AnyPeggyNode, Extract<errorValue, { tag: "RESyntaxError" }>> {
 | ||||||
|   const maybeExpression = parseRescript(squiggleString); | //   const maybeExpression = parseRescript(squiggleString);
 | ||||||
|   if (maybeExpression.tag === "Ok") { | //   if (maybeExpression.tag === "Ok") {
 | ||||||
|     return { tag: "Ok", value: maybeExpression.value as AnyPeggyNode }; | //     return { tag: "Ok", value: maybeExpression.value as AnyPeggyNode };
 | ||||||
|   } else { | //   } else {
 | ||||||
|     if ( | //     if (
 | ||||||
|       typeof maybeExpression.value !== "object" || | //       typeof maybeExpression.value !== "object" ||
 | ||||||
|       maybeExpression.value.tag !== "RESyntaxError" | //       maybeExpression.value.tag !== "RESyntaxError"
 | ||||||
|     ) { | //     ) {
 | ||||||
|       throw new Error("Expected syntax error"); | //       throw new Error("Expected syntax error");
 | ||||||
|     } | //     }
 | ||||||
|     return { tag: "Error", value: maybeExpression.value }; | //     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> = | import { result } from "../rescript/ForTS/ForTS_Result_tag"; | ||||||
|   | { | export { result }; | ||||||
|       tag: "Ok"; |  | ||||||
|       value: a; |  | ||||||
|     } |  | ||||||
|   | { |  | ||||||
|       tag: "Error"; |  | ||||||
|       value: b; |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
| export function resultMap<a, b, c>( | export function resultMap<a, b, c>( | ||||||
|   r: result<a, c>, |   r: result<a, b>, | ||||||
|   mapFn: (x: a) => b |   mapValue: (x: a) => c | ||||||
| ): result<b, c> { | ): result<c, b> { | ||||||
|   if (r.tag === "Ok") { |   if (r.tag === "Ok") { | ||||||
|     return { tag: "Ok", value: mapFn(r.value) }; |     return { tag: "Ok", value: mapValue(r.value) }; | ||||||
|   } else { |   } 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> { | export function Ok<a, b>(x: a): result<a, b> { | ||||||
|   return { tag: "Ok", value: x }; |   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 = | type genericDist = | ||||||
|   | PointSet(PointSetTypes.pointSetDist) |   | PointSet(PointSetTypes.pointSetDist) | ||||||
|   | SampleSet(SampleSetDist.t) |   | SampleSet(SampleSetDist.t) | ||||||
|  | @ -6,7 +7,7 @@ type genericDist = | ||||||
| 
 | 
 | ||||||
| type asAlgebraicCombinationStrategy = AsDefault | AsSymbolic | AsMonteCarlo | AsConvolution | type asAlgebraicCombinationStrategy = AsDefault | AsSymbolic | AsMonteCarlo | AsConvolution | ||||||
| 
 | 
 | ||||||
| @genType | @genType.opaque | ||||||
| type error = | type error = | ||||||
|   | NotYetImplemented |   | NotYetImplemented | ||||||
|   | Unreachable |   | Unreachable | ||||||
|  | @ -27,7 +28,6 @@ module Error = { | ||||||
| 
 | 
 | ||||||
|   let fromString = (s: string): t => OtherError(s) |   let fromString = (s: string): t => OtherError(s) | ||||||
| 
 | 
 | ||||||
|   @genType |  | ||||||
|   let toString = (err: error): string => |   let toString = (err: error): string => | ||||||
|     switch err { |     switch err { | ||||||
|     | NotYetImplemented => "Function not yet implemented" |     | NotYetImplemented => "Function not yet implemented" | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| //TODO: multimodal, add interface, test somehow, track performance, refactor sampleSet, refactor ASTEvaluator.res. | //TODO: multimodal, add interface, test somehow, track performance, refactor sampleSet, refactor ASTEvaluator.res. | ||||||
|  | 
 | ||||||
| type t = DistributionTypes.genericDist | type t = DistributionTypes.genericDist | ||||||
| type error = DistributionTypes.error | type error = DistributionTypes.error | ||||||
| type toPointSetFn = t => result<PointSetTypes.pointSetDist, error> | type toPointSetFn = t => result<PointSetTypes.pointSetDist, error> | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ type pointSetDistMonad<'a, 'b, 'c> = | ||||||
|   | Discrete('b) |   | Discrete('b) | ||||||
|   | Continuous('c) |   | Continuous('c) | ||||||
| 
 | 
 | ||||||
| @genType | @genType.opaque | ||||||
| type pointSetDist = pointSetDistMonad<mixedShape, discreteShape, continuousShape> | type pointSetDist = pointSetDistMonad<mixedShape, discreteShape, continuousShape> | ||||||
| 
 | 
 | ||||||
| module ShapeMonad = { | 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