openalgo-script 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1050 -0
- package/README.md +111 -27
- package/dist/adapters/charts/driving.d.ts +50 -0
- package/dist/adapters/charts/driving.d.ts.map +1 -0
- package/dist/adapters/charts/driving.js +57 -0
- package/dist/adapters/charts/driving.js.map +1 -0
- package/dist/adapters/charts/run.d.ts +20 -0
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +89 -16
- package/dist/adapters/charts/run.js.map +1 -1
- package/dist/adapters/charts/tables.d.ts +6 -19
- package/dist/adapters/charts/tables.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/charts/undrawable.d.ts +4 -0
- package/dist/adapters/charts/undrawable.d.ts.map +1 -0
- package/dist/adapters/charts/undrawable.js +47 -0
- package/dist/adapters/charts/undrawable.js.map +1 -0
- package/dist/adapters/charts/venue.d.ts +73 -0
- package/dist/adapters/charts/venue.d.ts.map +1 -0
- package/dist/adapters/charts/venue.js +104 -0
- package/dist/adapters/charts/venue.js.map +1 -0
- package/dist/core/accounting/analysis.d.ts +111 -0
- package/dist/core/accounting/analysis.d.ts.map +1 -0
- package/dist/core/accounting/analysis.js +123 -0
- package/dist/core/accounting/analysis.js.map +1 -0
- package/dist/core/accounting/equity.d.ts +33 -0
- package/dist/core/accounting/equity.d.ts.map +1 -1
- package/dist/core/accounting/equity.js +12 -0
- package/dist/core/accounting/equity.js.map +1 -1
- package/dist/core/accounting/index.d.ts +2 -0
- package/dist/core/accounting/index.d.ts.map +1 -1
- package/dist/core/accounting/index.js +1 -0
- package/dist/core/accounting/index.js.map +1 -1
- package/dist/core/accounting/report.d.ts +3 -0
- package/dist/core/accounting/report.d.ts.map +1 -1
- package/dist/core/accounting/report.js +2 -0
- package/dist/core/accounting/report.js.map +1 -1
- package/dist/core/accounting/statistics.d.ts +12 -0
- package/dist/core/accounting/statistics.d.ts.map +1 -1
- package/dist/core/accounting/statistics.js +23 -1
- package/dist/core/accounting/statistics.js.map +1 -1
- package/dist/core/backtest/case.d.ts +60 -0
- package/dist/core/backtest/case.d.ts.map +1 -0
- package/dist/core/backtest/case.js +319 -0
- package/dist/core/backtest/case.js.map +1 -0
- package/dist/core/backtest/compare.d.ts.map +1 -1
- package/dist/core/backtest/compare.js +2 -0
- package/dist/core/backtest/compare.js.map +1 -1
- package/dist/core/backtest/deliver.d.ts +93 -0
- package/dist/core/backtest/deliver.d.ts.map +1 -0
- package/dist/core/backtest/deliver.js +94 -0
- package/dist/core/backtest/deliver.js.map +1 -0
- package/dist/core/backtest/drive.d.ts +101 -8
- package/dist/core/backtest/drive.d.ts.map +1 -1
- package/dist/core/backtest/drive.js +114 -158
- package/dist/core/backtest/drive.js.map +1 -1
- package/dist/core/backtest/index.d.ts +26 -12
- package/dist/core/backtest/index.d.ts.map +1 -1
- package/dist/core/backtest/index.js +21 -10
- package/dist/core/backtest/index.js.map +1 -1
- package/dist/core/backtest/record.d.ts +63 -2
- package/dist/core/backtest/record.d.ts.map +1 -1
- package/dist/core/backtest/record.js +71 -4
- package/dist/core/backtest/record.js.map +1 -1
- package/dist/core/backtest/replay.d.ts.map +1 -1
- package/dist/core/backtest/replay.js +11 -1
- package/dist/core/backtest/replay.js.map +1 -1
- package/dist/core/backtest/simulate.d.ts +113 -1
- package/dist/core/backtest/simulate.d.ts.map +1 -1
- package/dist/core/backtest/simulate.js +123 -5
- package/dist/core/backtest/simulate.js.map +1 -1
- package/dist/core/backtest/walk.d.ts +39 -0
- package/dist/core/backtest/walk.d.ts.map +1 -0
- package/dist/core/backtest/walk.js +125 -0
- package/dist/core/backtest/walk.js.map +1 -0
- package/dist/core/catalogue/catalogue.generated.d.ts +179 -2
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +19 -2
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/catalogue/types.d.ts +8 -2
- package/dist/core/catalogue/types.d.ts.map +1 -1
- package/dist/core/catalogue/values.generated.d.ts +51 -0
- package/dist/core/catalogue/values.generated.d.ts.map +1 -1
- package/dist/core/check/assigned.d.ts +19 -0
- package/dist/core/check/assigned.d.ts.map +1 -0
- package/dist/core/check/assigned.js +14 -0
- package/dist/core/check/assigned.js.map +1 -0
- package/dist/core/check/call-sites.d.ts.map +1 -1
- package/dist/core/check/call-sites.js +9 -4
- package/dist/core/check/call-sites.js.map +1 -1
- package/dist/core/check/calls.d.ts.map +1 -1
- package/dist/core/check/calls.js +12 -4
- package/dist/core/check/calls.js.map +1 -1
- package/dist/core/check/check.d.ts.map +1 -1
- package/dist/core/check/check.js +15 -2
- package/dist/core/check/check.js.map +1 -1
- package/dist/core/check/checker.d.ts +26 -0
- package/dist/core/check/checker.d.ts.map +1 -1
- package/dist/core/check/checker.js +38 -2
- package/dist/core/check/checker.js.map +1 -1
- package/dist/core/check/constant.d.ts +16 -0
- package/dist/core/check/constant.d.ts.map +1 -1
- package/dist/core/check/constant.js +52 -0
- package/dist/core/check/constant.js.map +1 -1
- package/dist/core/check/deleted.d.ts +3 -0
- package/dist/core/check/deleted.d.ts.map +1 -0
- package/dist/core/check/deleted.js +109 -0
- package/dist/core/check/deleted.js.map +1 -0
- package/dist/core/check/expressions.js +1 -1
- package/dist/core/check/expressions.js.map +1 -1
- package/dist/core/check/library-orders.js +2 -2
- package/dist/core/check/library-orders.js.map +1 -1
- package/dist/core/check/library-output.d.ts.map +1 -1
- package/dist/core/check/library-output.js +1 -0
- package/dist/core/check/library-output.js.map +1 -1
- package/dist/core/check/library-prose.generated.js +12 -12
- package/dist/core/check/library-prose.generated.js.map +1 -1
- package/dist/core/check/library.d.ts +7 -0
- package/dist/core/check/library.d.ts.map +1 -1
- package/dist/core/check/library.js +1 -0
- package/dist/core/check/library.js.map +1 -1
- package/dist/core/check/surface.d.ts +9 -0
- package/dist/core/check/surface.d.ts.map +1 -1
- package/dist/core/check/surface.js +13 -0
- package/dist/core/check/surface.js.map +1 -1
- package/dist/core/emit/canonical.d.ts +22 -8
- package/dist/core/emit/canonical.d.ts.map +1 -1
- package/dist/core/emit/canonical.js +67 -6
- package/dist/core/emit/canonical.js.map +1 -1
- package/dist/core/engine/arithmetic.d.ts +6 -25
- package/dist/core/engine/arithmetic.d.ts.map +1 -1
- package/dist/core/engine/arithmetic.js +48 -3
- package/dist/core/engine/arithmetic.js.map +1 -1
- package/dist/core/engine/bar-source.d.ts +65 -0
- package/dist/core/engine/bar-source.d.ts.map +1 -0
- package/dist/core/engine/bar-source.js +58 -0
- package/dist/core/engine/bar-source.js.map +1 -0
- package/dist/core/engine/engine.d.ts +9 -11
- package/dist/core/engine/engine.d.ts.map +1 -1
- package/dist/core/engine/engine.js +24 -24
- package/dist/core/engine/engine.js.map +1 -1
- package/dist/core/engine/guard.d.ts.map +1 -1
- package/dist/core/engine/guard.js +10 -0
- package/dist/core/engine/guard.js.map +1 -1
- package/dist/core/engine/host.d.ts +4 -2
- package/dist/core/engine/host.d.ts.map +1 -1
- package/dist/core/engine/host.js.map +1 -1
- package/dist/core/engine/index.d.ts +2 -1
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/index.js.map +1 -1
- package/dist/core/engine/library/arrays.d.ts.map +1 -1
- package/dist/core/engine/library/arrays.js +8 -2
- package/dist/core/engine/library/arrays.js.map +1 -1
- package/dist/core/engine/library/binding.d.ts +2 -0
- package/dist/core/engine/library/binding.d.ts.map +1 -1
- package/dist/core/engine/library/binding.js.map +1 -1
- package/dist/core/engine/library/code-points.d.ts +40 -0
- package/dist/core/engine/library/code-points.d.ts.map +1 -0
- package/dist/core/engine/library/code-points.js +74 -0
- package/dist/core/engine/library/code-points.js.map +1 -0
- package/dist/core/engine/library/index.d.ts +13 -0
- package/dist/core/engine/library/index.d.ts.map +1 -1
- package/dist/core/engine/library/index.js +15 -0
- package/dist/core/engine/library/index.js.map +1 -1
- package/dist/core/engine/library/objects.d.ts.map +1 -1
- package/dist/core/engine/library/objects.js +1 -1
- package/dist/core/engine/library/objects.js.map +1 -1
- package/dist/core/engine/library/text.d.ts.map +1 -1
- package/dist/core/engine/library/text.js +51 -34
- package/dist/core/engine/library/text.js.map +1 -1
- package/dist/core/engine/load.d.ts +20 -0
- package/dist/core/engine/load.d.ts.map +1 -1
- package/dist/core/engine/load.js +62 -0
- package/dist/core/engine/load.js.map +1 -1
- package/dist/core/engine/request-body.d.ts +3 -1
- package/dist/core/engine/request-body.d.ts.map +1 -1
- package/dist/core/engine/request-body.js +3 -1
- package/dist/core/engine/request-body.js.map +1 -1
- package/dist/core/engine/request-plan.d.ts +3 -3
- package/dist/core/engine/request-plan.js +3 -3
- package/dist/core/engine/requests.d.ts +2 -2
- package/dist/core/engine/requests.d.ts.map +1 -1
- package/dist/core/engine/requests.js +5 -4
- package/dist/core/engine/requests.js.map +1 -1
- package/dist/core/engine/series.d.ts +18 -6
- package/dist/core/engine/series.d.ts.map +1 -1
- package/dist/core/engine/series.js +24 -5
- package/dist/core/engine/series.js.map +1 -1
- package/dist/core/engine/session/record.d.ts +4 -4
- package/dist/core/engine/session/record.js +4 -4
- package/dist/core/engine/verify-tables.d.ts.map +1 -1
- package/dist/core/engine/verify-tables.js +41 -0
- package/dist/core/engine/verify-tables.js.map +1 -1
- package/dist/core/importer/calls.d.ts +29 -0
- package/dist/core/importer/calls.d.ts.map +1 -0
- package/dist/core/importer/calls.js +267 -0
- package/dist/core/importer/calls.js.map +1 -0
- package/dist/core/importer/context.d.ts +118 -0
- package/dist/core/importer/context.d.ts.map +1 -0
- package/dist/core/importer/context.js +120 -0
- package/dist/core/importer/context.js.map +1 -0
- package/dist/core/importer/expressions.d.ts +44 -0
- package/dist/core/importer/expressions.d.ts.map +1 -0
- package/dist/core/importer/expressions.js +223 -0
- package/dist/core/importer/expressions.js.map +1 -0
- package/dist/core/importer/importer.d.ts +13 -0
- package/dist/core/importer/importer.d.ts.map +1 -0
- package/dist/core/importer/importer.js +220 -0
- package/dist/core/importer/importer.js.map +1 -0
- package/dist/core/importer/index.d.ts +20 -0
- package/dist/core/importer/index.d.ts.map +1 -0
- package/dist/core/importer/index.js +18 -0
- package/dist/core/importer/index.js.map +1 -0
- package/dist/core/importer/lexer.d.ts +68 -0
- package/dist/core/importer/lexer.d.ts.map +1 -0
- package/dist/core/importer/lexer.js +207 -0
- package/dist/core/importer/lexer.js.map +1 -0
- package/dist/core/importer/names.d.ts +41 -0
- package/dist/core/importer/names.d.ts.map +1 -0
- package/dist/core/importer/names.js +78 -0
- package/dist/core/importer/names.js.map +1 -0
- package/dist/core/importer/orders.d.ts +8 -0
- package/dist/core/importer/orders.d.ts.map +1 -0
- package/dist/core/importer/orders.js +332 -0
- package/dist/core/importer/orders.js.map +1 -0
- package/dist/core/importer/outputs.d.ts +19 -0
- package/dist/core/importer/outputs.d.ts.map +1 -0
- package/dist/core/importer/outputs.js +266 -0
- package/dist/core/importer/outputs.js.map +1 -0
- package/dist/core/importer/parser.d.ts +5 -0
- package/dist/core/importer/parser.d.ts.map +1 -0
- package/dist/core/importer/parser.js +439 -0
- package/dist/core/importer/parser.js.map +1 -0
- package/dist/core/importer/presence.d.ts +32 -0
- package/dist/core/importer/presence.d.ts.map +1 -0
- package/dist/core/importer/presence.js +146 -0
- package/dist/core/importer/presence.js.map +1 -0
- package/dist/core/importer/settle.d.ts +23 -0
- package/dist/core/importer/settle.d.ts.map +1 -0
- package/dist/core/importer/settle.js +135 -0
- package/dist/core/importer/settle.js.map +1 -0
- package/dist/core/importer/statements.d.ts +4 -0
- package/dist/core/importer/statements.d.ts.map +1 -0
- package/dist/core/importer/statements.js +339 -0
- package/dist/core/importer/statements.js.map +1 -0
- package/dist/core/importer/syntax.d.ts +182 -0
- package/dist/core/importer/syntax.d.ts.map +1 -0
- package/dist/core/importer/syntax.js +14 -0
- package/dist/core/importer/syntax.js.map +1 -0
- package/dist/core/importer/table.d.ts +52 -0
- package/dist/core/importer/table.d.ts.map +1 -0
- package/dist/core/importer/table.js +156 -0
- package/dist/core/importer/table.js.map +1 -0
- package/dist/core/importer/values.d.ts +27 -0
- package/dist/core/importer/values.d.ts.map +1 -0
- package/dist/core/importer/values.js +236 -0
- package/dist/core/importer/values.js.map +1 -0
- package/dist/core/index.d.ts +22 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +19 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/stdlib/calendar/civil.d.ts.map +1 -1
- package/dist/core/stdlib/calendar/civil.js +5 -2
- package/dist/core/stdlib/calendar/civil.js.map +1 -1
- package/dist/core/stdlib/index.d.ts +1 -1
- package/dist/core/stdlib/index.d.ts.map +1 -1
- package/dist/core/stdlib/index.js +1 -1
- package/dist/core/stdlib/index.js.map +1 -1
- package/dist/core/stdlib/maths/index.d.ts +1 -1
- package/dist/core/stdlib/maths/index.d.ts.map +1 -1
- package/dist/core/stdlib/maths/index.js +1 -1
- package/dist/core/stdlib/maths/index.js.map +1 -1
- package/dist/core/stdlib/maths/rounding.d.ts +5 -0
- package/dist/core/stdlib/maths/rounding.d.ts.map +1 -1
- package/dist/core/stdlib/maths/rounding.js +19 -1
- package/dist/core/stdlib/maths/rounding.js.map +1 -1
- package/dist/core/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/package.json +17 -2
- package/spec/README.md +2 -1
- package/spec/errors.json +383 -23
- package/src/adapters/charts/driving.ts +109 -0
- package/src/adapters/charts/run.ts +126 -28
- package/src/adapters/charts/tables.ts +6 -19
- package/src/adapters/charts/undrawable.ts +51 -0
- package/src/adapters/charts/venue.ts +132 -0
- package/src/core/accounting/analysis.ts +188 -0
- package/src/core/accounting/equity.ts +38 -0
- package/src/core/accounting/index.ts +2 -0
- package/src/core/accounting/report.ts +5 -0
- package/src/core/accounting/statistics.ts +38 -1
- package/src/core/backtest/case.ts +395 -0
- package/src/core/backtest/compare.ts +2 -0
- package/src/core/backtest/deliver.ts +161 -0
- package/src/core/backtest/drive.ts +233 -217
- package/src/core/backtest/index.ts +26 -12
- package/src/core/backtest/record.ts +134 -5
- package/src/core/backtest/replay.ts +11 -1
- package/src/core/backtest/simulate.ts +201 -6
- package/src/core/backtest/walk.ts +182 -0
- package/src/core/catalogue/catalogue.generated.ts +19 -2
- package/src/core/catalogue/types.ts +8 -2
- package/src/core/catalogue/values.generated.ts +16 -0
- package/src/core/check/assigned.ts +31 -0
- package/src/core/check/call-sites.ts +11 -4
- package/src/core/check/calls.ts +13 -4
- package/src/core/check/check.ts +14 -2
- package/src/core/check/checker.ts +39 -2
- package/src/core/check/constant.ts +57 -0
- package/src/core/check/deleted.ts +135 -0
- package/src/core/check/expressions.ts +1 -1
- package/src/core/check/library-orders.ts +2 -2
- package/src/core/check/library-output.ts +1 -0
- package/src/core/check/library-prose.generated.ts +12 -12
- package/src/core/check/library.ts +8 -0
- package/src/core/check/surface.ts +16 -0
- package/src/core/emit/canonical.ts +67 -9
- package/src/core/engine/arithmetic.ts +23 -3
- package/src/core/engine/bar-source.ts +113 -0
- package/src/core/engine/engine.ts +26 -25
- package/src/core/engine/guard.ts +12 -1
- package/src/core/engine/host.ts +4 -2
- package/src/core/engine/index.ts +2 -1
- package/src/core/engine/library/arrays.ts +8 -2
- package/src/core/engine/library/binding.ts +2 -0
- package/src/core/engine/library/code-points.ts +73 -0
- package/src/core/engine/library/index.ts +17 -0
- package/src/core/engine/library/objects.ts +1 -6
- package/src/core/engine/library/text.ts +53 -35
- package/src/core/engine/load.ts +68 -0
- package/src/core/engine/request-body.ts +5 -7
- package/src/core/engine/request-plan.ts +3 -3
- package/src/core/engine/requests.ts +11 -8
- package/src/core/engine/series.ts +30 -6
- package/src/core/engine/session/record.ts +4 -4
- package/src/core/engine/verify-tables.ts +43 -0
- package/src/core/importer/calls.ts +274 -0
- package/src/core/importer/context.ts +197 -0
- package/src/core/importer/expressions.ts +247 -0
- package/src/core/importer/importer.ts +226 -0
- package/src/core/importer/index.ts +19 -0
- package/src/core/importer/lexer.ts +285 -0
- package/src/core/importer/names.ts +104 -0
- package/src/core/importer/orders.ts +307 -0
- package/src/core/importer/outputs.ts +245 -0
- package/src/core/importer/parser.ts +436 -0
- package/src/core/importer/presence.ts +164 -0
- package/src/core/importer/settle.ts +141 -0
- package/src/core/importer/statements.ts +331 -0
- package/src/core/importer/syntax.ts +144 -0
- package/src/core/importer/table.ts +191 -0
- package/src/core/importer/values.ts +241 -0
- package/src/core/index.ts +29 -2
- package/src/core/stdlib/calendar/civil.ts +5 -2
- package/src/core/stdlib/index.ts +1 -0
- package/src/core/stdlib/maths/index.ts +1 -0
- package/src/core/stdlib/maths/rounding.ts +23 -1
- package/src/core/version/version.generated.ts +1 -1
|
@@ -49,7 +49,9 @@ import type { CompiledProgram, Position } from './types.js';
|
|
|
49
49
|
import type { RequestPlan } from './request-plan.js';
|
|
50
50
|
import { NO_SESSION, sessionReader } from './session/index.js';
|
|
51
51
|
import type { SessionReader } from './session/index.js';
|
|
52
|
-
import {
|
|
52
|
+
import { handOver, noBars } from './series.js';
|
|
53
|
+
import { KnownBars, sourceOf } from './bar-source.js';
|
|
54
|
+
import type { BarColumns } from './bar-source.js';
|
|
53
55
|
import { barMinutesOf } from './timeframe.js';
|
|
54
56
|
import { RequestSet } from './requests.js';
|
|
55
57
|
import type { Value } from './values/index.js';
|
|
@@ -96,7 +98,7 @@ export class Engine {
|
|
|
96
98
|
* chart bar is inside, and on a live feed there is nothing there yet. That is
|
|
97
99
|
* the mode repainting on history, permanently and by design.
|
|
98
100
|
*/
|
|
99
|
-
private known
|
|
101
|
+
private readonly known = new KnownBars();
|
|
100
102
|
|
|
101
103
|
private supplied = 0;
|
|
102
104
|
private index = -1;
|
|
@@ -230,7 +232,7 @@ export class Engine {
|
|
|
230
232
|
// that has already failed keeps the failure it has, which is the one that
|
|
231
233
|
// explains what went wrong first.
|
|
232
234
|
if (this.failure === undefined) {
|
|
233
|
-
const problem =
|
|
235
|
+
const problem = handOver(bar, this.known.at(this.index), this.index + 1);
|
|
234
236
|
if (problem !== undefined) return this.refuse(problem);
|
|
235
237
|
}
|
|
236
238
|
if (this.started) this.checkpoint();
|
|
@@ -239,7 +241,7 @@ export class Engine {
|
|
|
239
241
|
this.updates = 1;
|
|
240
242
|
this.started = true;
|
|
241
243
|
this.previousClose = this.lastClose;
|
|
242
|
-
if (this.known.length <= this.index) this.known
|
|
244
|
+
if (this.known.length <= this.index) this.known.set(this.index, bar);
|
|
243
245
|
return this.execute(bar, state, true);
|
|
244
246
|
}
|
|
245
247
|
|
|
@@ -257,14 +259,14 @@ export class Engine {
|
|
|
257
259
|
// update whose time has moved back onto the bar before it is a series the
|
|
258
260
|
// engine cannot run on, whatever it is called.
|
|
259
261
|
if (this.failure === undefined) {
|
|
260
|
-
const problem =
|
|
262
|
+
const problem = handOver(bar, this.known.at(this.index - 1), this.index);
|
|
261
263
|
if (problem !== undefined) return this.refuse(problem);
|
|
262
264
|
}
|
|
263
265
|
this.rollback();
|
|
264
266
|
this.updates += 1;
|
|
265
267
|
// A revision replaces the bar the fold already read, so the bucket it is
|
|
266
268
|
// inside is rebuilt from this reading rather than from the one it replaced.
|
|
267
|
-
this.known
|
|
269
|
+
this.known.set(this.index, bar);
|
|
268
270
|
return this.execute(bar, state, false);
|
|
269
271
|
}
|
|
270
272
|
|
|
@@ -282,27 +284,34 @@ export class Engine {
|
|
|
282
284
|
* pane with nothing drawn on it is what a study that computed nothing also
|
|
283
285
|
* produces and only the engine can tell the two apart.
|
|
284
286
|
*/
|
|
285
|
-
run(bars: readonly HostBar[], states: readonly BarState[] = []): RunResult {
|
|
287
|
+
run(bars: readonly HostBar[] | BarColumns, states: readonly BarState[] = []): RunResult {
|
|
288
|
+
const source = sourceOf(bars);
|
|
286
289
|
const out: BarResult[] = [];
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
// which would run no iterations and return a clean, empty, wordless result.
|
|
290
|
-
if (bars.length === 0 && !this.started) {
|
|
290
|
+
// No bars at all is answered here: the loop below would return an empty, wordless result.
|
|
291
|
+
if (source.length === 0 && !this.started) {
|
|
291
292
|
const diagnostic = this.failure ?? noBars(this.options.host?.instrument);
|
|
292
293
|
this.failure = diagnostic;
|
|
293
294
|
return { bars: [], diagnostic };
|
|
294
295
|
}
|
|
295
|
-
this.known
|
|
296
|
-
for (let i = 0; i <
|
|
297
|
-
const bar =
|
|
296
|
+
this.known.reset(source);
|
|
297
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
298
|
+
const bar = source.at(i);
|
|
298
299
|
if (bar === undefined) continue;
|
|
299
|
-
const result = this.append(bar, states[i] ?? {},
|
|
300
|
+
const result = this.append(bar, states[i] ?? {}, source.length);
|
|
300
301
|
out.push(result);
|
|
301
302
|
if (result.diagnostic !== undefined) return { bars: out, diagnostic: result.diagnostic };
|
|
302
303
|
}
|
|
303
304
|
return { bars: out, diagnostic: undefined };
|
|
304
305
|
}
|
|
305
306
|
|
|
307
|
+
/**
|
|
308
|
+
* The dataset a driver appends one bar at a time, handed to the fold before
|
|
309
|
+
* bar 0 as `run` hands it, so a `"lookahead"` read reads it as history.
|
|
310
|
+
*/
|
|
311
|
+
history(bars: readonly HostBar[] | BarColumns): void {
|
|
312
|
+
if (!this.started) this.known.reset(sourceOf(bars));
|
|
313
|
+
}
|
|
314
|
+
|
|
306
315
|
/**
|
|
307
316
|
* A frame from the destination, `host-interface.md` 7.2. Held until the next
|
|
308
317
|
* bar begins rather than folded where it lands: nothing reaches a running
|
|
@@ -339,7 +348,7 @@ export class Engine {
|
|
|
339
348
|
// session or is inside the one that bar was already in. Read from the bars
|
|
340
349
|
// themselves rather than carried, so a re-executed bar compares against the
|
|
341
350
|
// same neighbour it compared against the first time.
|
|
342
|
-
const previous = this.known
|
|
351
|
+
const previous = this.known.at(index - 1)?.time ?? null;
|
|
343
352
|
this.facts = factsFor(
|
|
344
353
|
index,
|
|
345
354
|
this.supplied,
|
|
@@ -418,15 +427,6 @@ export class Engine {
|
|
|
418
427
|
};
|
|
419
428
|
}
|
|
420
429
|
|
|
421
|
-
/**
|
|
422
|
-
* A bar that failed.
|
|
423
|
-
*
|
|
424
|
-
* Steps 7 to 11 do not run, the bar's output columns keep whatever the
|
|
425
|
-
* previous execution published or stay absent, and the engine reports the
|
|
426
|
-
* error with its code and source position. It does not continue to the next
|
|
427
|
-
* bar with a half executed state, so the journal is rolled back and the
|
|
428
|
-
* script is marked stopped.
|
|
429
|
-
*/
|
|
430
430
|
/**
|
|
431
431
|
* A hand-over the engine refuses, before any of it has been executed.
|
|
432
432
|
*
|
|
@@ -449,6 +449,7 @@ export class Engine {
|
|
|
449
449
|
};
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
/** A bar that failed: steps 7 to 11 do not run, the journal rolls back, the script stops. */
|
|
452
453
|
private stopped(thrown: unknown): BarResult {
|
|
453
454
|
const diagnostic =
|
|
454
455
|
thrown instanceof ScriptError ? thrown.diagnostic : unexpected('the bar', thrown);
|
package/src/core/engine/guard.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* catalogue has: an array past its element ceiling is OS5002, a string past its
|
|
11
11
|
* character ceiling is OS5008, one more drawing object than the host will hold
|
|
12
12
|
* is OS5010, an argument outside its contract is OS4003, an index outside an
|
|
13
|
-
* array is OS4004, a change to an object a script already deleted is OS4005,
|
|
13
|
+
* array is OS4004, a cell outside a table's declared shape is OS4008, a change to an object a script already deleted is OS4005,
|
|
14
14
|
* and a timezone name the host's table does not hold is OS6005.
|
|
15
15
|
*/
|
|
16
16
|
import type { Span } from '../span/index.js';
|
|
@@ -52,6 +52,17 @@ export function guardFor(budget: Budget): Guard {
|
|
|
52
52
|
raise('OS4004', span, { index, name, size });
|
|
53
53
|
},
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* A cell outside the grid its table declared, OS4008.
|
|
57
|
+
*
|
|
58
|
+
* Not OS4004, which is about an array and would name an index and a
|
|
59
|
+
* flattened element count: a table's shape is two numbers the declaration
|
|
60
|
+
* fixed, and the fix is to declare the shape the script writes.
|
|
61
|
+
*/
|
|
62
|
+
badCell(span: Span, row: number, column: number, rows: number, columns: number): never {
|
|
63
|
+
raise('OS4008', span, { row, column, rows, columns });
|
|
64
|
+
},
|
|
65
|
+
|
|
55
66
|
deleted(span: Span, kind: string, bar: number): never {
|
|
56
67
|
raise('OS4005', span, { kind, bar });
|
|
57
68
|
},
|
package/src/core/engine/host.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* stop rather than what a script computes.
|
|
26
26
|
*/
|
|
27
27
|
import type { HostBar } from './bars.js';
|
|
28
|
+
import type { BarColumns } from './bar-source.js';
|
|
28
29
|
import type { PendingEffect } from './channels.js';
|
|
29
30
|
import type { OrderIntent } from './ledger/index.js';
|
|
30
31
|
import type { HostFacts } from './library/index.js';
|
|
@@ -154,10 +155,11 @@ export interface RequestRefusal {
|
|
|
154
155
|
* answer between bars and then recalculates the study over its whole history,
|
|
155
156
|
* which is a fresh load rather than an answer spliced into a run already past
|
|
156
157
|
* the bars it would have changed. A host that wants the engine to fold the
|
|
157
|
-
* chart's own bars instead returns nothing at all.
|
|
158
|
+
* chart's own bars instead returns nothing at all. The bars may be records or
|
|
159
|
+
* columns, as a run's may (`bar-source.ts`).
|
|
158
160
|
*/
|
|
159
161
|
export type RequestAnswer =
|
|
160
|
-
| { readonly bars: readonly HostBar[] }
|
|
162
|
+
| { readonly bars: readonly HostBar[] | BarColumns }
|
|
161
163
|
| { readonly pending: true }
|
|
162
164
|
| { readonly refused: RequestRefusal };
|
|
163
165
|
|
package/src/core/engine/index.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
export { Engine } from './engine.js';
|
|
25
25
|
export type { BarResult, RunResult } from './results.js';
|
|
26
26
|
|
|
27
|
-
export { load } from './load.js';
|
|
27
|
+
export { load, loadText } from './load.js';
|
|
28
28
|
export type { LoadOptions, LoadResult } from './load.js';
|
|
29
29
|
|
|
30
30
|
export type { AlertFiring } from './alerts.js';
|
|
@@ -32,6 +32,7 @@ export type { AlertFiring } from './alerts.js';
|
|
|
32
32
|
export type { Grid } from './grids.js';
|
|
33
33
|
|
|
34
34
|
export type { BarFacts, BarState, HostBar } from './bars.js';
|
|
35
|
+
export type { BarColumn, BarColumns } from './bar-source.js';
|
|
35
36
|
export { BAR_FIELDS, barField, isBarField } from './bars.js';
|
|
36
37
|
|
|
37
38
|
export type { Clock, EngineLimits } from './budget.js';
|
|
@@ -22,6 +22,7 @@ import type { Value } from '../values/index.js';
|
|
|
22
22
|
import { isNumber, reference, valuesEqual } from '../values/index.js';
|
|
23
23
|
import { entry, refAt, stringAt, valueAt, wholeAt } from './binding.js';
|
|
24
24
|
import type { CallContext, ManifestEntry } from './binding.js';
|
|
25
|
+
import { compareStrings } from './code-points.js';
|
|
25
26
|
import { result } from '../../stdlib/index.js';
|
|
26
27
|
|
|
27
28
|
/** The array a handle names, or nothing when the handle is absent or stale. */
|
|
@@ -68,12 +69,17 @@ function within(
|
|
|
68
69
|
return index;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The order `sort` uses, written down so two engines cannot differ.
|
|
74
|
+
*
|
|
75
|
+
* Two strings order by code point (`language.md` 9.3), which is not the order
|
|
76
|
+
* the host's `<` gives once a string holds a symbol outside the basic plane.
|
|
77
|
+
*/
|
|
72
78
|
function rank(a: Value, b: Value): number {
|
|
73
79
|
if (a === null) return b === null ? 0 : 1;
|
|
74
80
|
if (b === null) return -1;
|
|
75
81
|
if (typeof a === 'number' && typeof b === 'number') return a < b ? -1 : a > b ? 1 : 0;
|
|
76
|
-
if (typeof a === 'string' && typeof b === 'string') return a
|
|
82
|
+
if (typeof a === 'string' && typeof b === 'string') return compareStrings(a, b);
|
|
77
83
|
if (typeof a === 'boolean' && typeof b === 'boolean') return a === b ? 0 : a ? 1 : -1;
|
|
78
84
|
return 0;
|
|
79
85
|
}
|
|
@@ -160,6 +160,8 @@ export interface Guard {
|
|
|
160
160
|
badArgument(span: Span, name: string, argument: string, found: string): never;
|
|
161
161
|
/** Raise a run-time diagnostic against an index outside an array. */
|
|
162
162
|
badIndex(span: Span, name: string, index: string, size: number): never;
|
|
163
|
+
/** Raise a run-time diagnostic against a cell outside a table's declared shape. */
|
|
164
|
+
badCell(span: Span, row: number, column: number, rows: number, columns: number): never;
|
|
163
165
|
/** Raise a run-time diagnostic against an object a script already deleted. */
|
|
164
166
|
deleted(span: Span, kind: string, bar: number): never;
|
|
165
167
|
/** Raise a run-time diagnostic against a zone name the host does not know. */
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two string rules two engines have to share, written from the page.
|
|
3
|
+
*
|
|
4
|
+
* **A string is a sequence of code points** (`compiled-program.md` 3.1), and the
|
|
5
|
+
* host this engine is written in stores one as sixteen bit units. Every rule
|
|
6
|
+
* here counts code points and not units, because the two disagree the moment a
|
|
7
|
+
* string holds a symbol outside the basic plane, and a rule written on the
|
|
8
|
+
* host's units would be a rule a second engine could not follow.
|
|
9
|
+
*
|
|
10
|
+
* **Ordering.** Two strings are ordered by comparing code points from the
|
|
11
|
+
* front, the first difference deciding, and a string that runs out first
|
|
12
|
+
* ordering first (`language.md` 9.3, `stdlib.md` section 10). The host's own
|
|
13
|
+
* `<` orders by unit, which puts every symbol outside the basic plane, stored
|
|
14
|
+
* as a surrogate pair from U+D800, below the code points from U+E000 to U+FFFF
|
|
15
|
+
* that come after it, so it is not used.
|
|
16
|
+
*
|
|
17
|
+
* **Whitespace.** `str.trim` removes, and `toNumber` ignores, exactly the code
|
|
18
|
+
* points `stdlib.md` section 10 lists, which are the ones with the Unicode
|
|
19
|
+
* White_Space property. The host's own `trim` removes a set that is nearly this
|
|
20
|
+
* one: it takes the byte order mark U+FEFF as well, and a second engine's host
|
|
21
|
+
* takes the four separators U+001C to U+001F and leaves the byte order mark, so
|
|
22
|
+
* neither host's default is the set, and the set is implemented from the list.
|
|
23
|
+
* `tests/engine/strings.test.ts` walks every code point of the basic plane
|
|
24
|
+
* against the list read out of the page.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** The code points `str.trim` removes, as `stdlib.md` section 10 lists them. */
|
|
28
|
+
const WHITESPACE: ReadonlySet<number> = new Set([
|
|
29
|
+
0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x0020, 0x0085, 0x00a0, 0x1680,
|
|
30
|
+
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
|
|
31
|
+
0x2028, 0x2029, 0x202f, 0x205f, 0x3000,
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
/** Code points, which is what every index over a string counts. */
|
|
35
|
+
export function points(text: string): string[] {
|
|
36
|
+
return [...text];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Whether one code point is in the trimmed set. */
|
|
40
|
+
export function isWhitespace(point: number): boolean {
|
|
41
|
+
return WHITESPACE.has(point);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `text` with the whitespace of the written set taken off both ends. */
|
|
45
|
+
export function trimmed(text: string): string {
|
|
46
|
+
const all = points(text);
|
|
47
|
+
let from = 0;
|
|
48
|
+
let to = all.length;
|
|
49
|
+
while (from < to && isWhitespace(all[from]?.codePointAt(0) ?? -1)) from += 1;
|
|
50
|
+
while (to > from && isWhitespace(all[to - 1]?.codePointAt(0) ?? -1)) to -= 1;
|
|
51
|
+
return from === 0 && to === all.length ? text : all.slice(from, to).join('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The order of two strings, negative when `a` comes first, by code point.
|
|
56
|
+
*
|
|
57
|
+
* Walked with the string iterator so a surrogate pair is one code point, and
|
|
58
|
+
* compared as numbers so U+10000 comes after U+FFFF, which is the order the
|
|
59
|
+
* page states and the one an engine with four byte strings gets for free.
|
|
60
|
+
*/
|
|
61
|
+
export function compareStrings(a: string, b: string): number {
|
|
62
|
+
const left = a[Symbol.iterator]();
|
|
63
|
+
const right = b[Symbol.iterator]();
|
|
64
|
+
for (;;) {
|
|
65
|
+
const one = left.next();
|
|
66
|
+
const other = right.next();
|
|
67
|
+
if (one.done) return other.done ? 0 : -1;
|
|
68
|
+
if (other.done) return 1;
|
|
69
|
+
const x = one.value.codePointAt(0) ?? 0;
|
|
70
|
+
const y = other.value.codePointAt(0) ?? 0;
|
|
71
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -37,6 +37,12 @@ import { TREND_ENTRIES } from './trend.js';
|
|
|
37
37
|
import { VOLUME_ENTRIES } from './volume.js';
|
|
38
38
|
import type { ManifestEntry } from './binding.js';
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The two string rules, for the operators outside this module and the tests:
|
|
42
|
+
* `code-points.ts` says why neither is the host's own.
|
|
43
|
+
*/
|
|
44
|
+
export { compareStrings, isWhitespace, trimmed } from './code-points.js';
|
|
45
|
+
|
|
40
46
|
const ENTRIES: readonly ManifestEntry[] = [
|
|
41
47
|
...COLOUR_ENTRIES,
|
|
42
48
|
...MATHS_ENTRIES,
|
|
@@ -68,6 +74,17 @@ for (const one of ENTRIES) {
|
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
/** The entry for one name and argument count, or nothing when there is none. */
|
|
77
|
+
/**
|
|
78
|
+
* Every name and argument count this engine holds, as `name/arity`, sorted.
|
|
79
|
+
*
|
|
80
|
+
* `scripts/check-manifests.mjs` compares it with the second engine's, so a call
|
|
81
|
+
* one engine holds and the other does not is found by a build rather than by the
|
|
82
|
+
* first case that reaches it.
|
|
83
|
+
*/
|
|
84
|
+
export function manifestKeys(): readonly string[] {
|
|
85
|
+
return [...INDEX.keys()].sort();
|
|
86
|
+
}
|
|
87
|
+
|
|
71
88
|
export function manifestEntry(name: string, arity: number): ManifestEntry | undefined {
|
|
72
89
|
return INDEX.get(keyOf(name, arity));
|
|
73
90
|
}
|
|
@@ -234,12 +234,7 @@ export const OBJECT_ENTRIES: readonly ManifestEntry[] = [
|
|
|
234
234
|
const col = wholeAt(ctx, 'cell', 'col', args, 2);
|
|
235
235
|
if (row === null || col === null) return null;
|
|
236
236
|
if (row >= object.rows || col >= object.cols) {
|
|
237
|
-
ctx.guard.
|
|
238
|
-
ctx.span,
|
|
239
|
-
ctx.nameOf(valueAt(args, 0)),
|
|
240
|
-
`${row}, ${col}`,
|
|
241
|
-
object.rows * object.cols,
|
|
242
|
-
);
|
|
237
|
+
ctx.guard.badCell(ctx.span, row, col, object.rows, object.cols);
|
|
243
238
|
}
|
|
244
239
|
const written: GridCell = {
|
|
245
240
|
row,
|
|
@@ -8,34 +8,50 @@
|
|
|
8
8
|
* one element, or two engines will disagree about the length of a string
|
|
9
9
|
* holding a symbol outside the basic plane and about every substring taken
|
|
10
10
|
* after one. That is what the spread into an array is doing everywhere below:
|
|
11
|
-
* it costs a pass over the string and buys agreement.
|
|
11
|
+
* it costs a pass over the string and buys agreement. The trimmed set and the
|
|
12
|
+
* order of two strings are in `code-points.ts`, written from the page.
|
|
12
13
|
*
|
|
13
14
|
* **Number to string is specified, not the host's default.** `text(x)` is the
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* reader of a price expects.
|
|
15
|
+
* rule of `language.md` 5.5 and goes through the one writer that implements
|
|
16
|
+
* it, `canonicalNumber`, as does every digit this file writes: a host's own
|
|
17
|
+
* conversion is never asked. `text(x, d)` rounds to `d` decimals with halves
|
|
18
|
+
* away from zero and always emits exactly `d` digits after the point, because
|
|
19
|
+
* this is a display conversion and half up is what a reader of a price expects.
|
|
19
20
|
*/
|
|
20
|
-
import {
|
|
21
|
+
import { canonicalNumber } from '../../emit/index.js';
|
|
22
|
+
import { roundHalfAway, scaleOf } from '../../stdlib/index.js';
|
|
21
23
|
import type { Value } from '../values/index.js';
|
|
22
24
|
import { isColour, reference } from '../values/index.js';
|
|
23
25
|
import { entry, numberAt, refAt, stringAt, valueAt, wholeAt } from './binding.js';
|
|
24
26
|
import type { CallContext, ManifestEntry } from './binding.js';
|
|
27
|
+
import { points, trimmed } from './code-points.js';
|
|
25
28
|
|
|
26
29
|
/** A value as `text(x)` spells it. */
|
|
27
30
|
export function spell(ctx: CallContext, value: Value): string {
|
|
28
31
|
if (value === null) return 'none';
|
|
29
|
-
if (typeof value === 'number') return
|
|
30
|
-
if (typeof value === 'boolean') return
|
|
32
|
+
if (typeof value === 'number') return canonicalNumber(value);
|
|
33
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
31
34
|
if (typeof value === 'string') return value;
|
|
32
35
|
if (isColour(value)) {
|
|
33
|
-
const
|
|
34
|
-
return `#${
|
|
36
|
+
const alpha = roundHalfAway(value.a * 255);
|
|
37
|
+
return `#${hexByte(value.r)}${hexByte(value.g)}${hexByte(value.b)}${hexByte(alpha)}`;
|
|
35
38
|
}
|
|
36
39
|
return ctx.heap.get(value.id)?.kind ?? 'none';
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
/**
|
|
43
|
+
* A whole number from 0 to 255 as two hex digits, from a table.
|
|
44
|
+
*
|
|
45
|
+
* A colour's channels are whole numbers (`compiled-program.md` 3.1), so this
|
|
46
|
+
* is not the decimal rendering rule and asks no host conversion: each nibble
|
|
47
|
+
* indexes a string of sixteen characters.
|
|
48
|
+
*/
|
|
49
|
+
const HEX_DIGITS = '0123456789abcdef';
|
|
50
|
+
|
|
51
|
+
function hexByte(channel: number): string {
|
|
52
|
+
return `${HEX_DIGITS[channel >> 4] ?? '0'}${HEX_DIGITS[channel & 15] ?? '0'}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
/** A magnitude as decimal digits, and how many of them fall before the point. */
|
|
40
56
|
interface Spread {
|
|
41
57
|
readonly digits: string;
|
|
@@ -108,24 +124,31 @@ function written(of: Spread): string {
|
|
|
108
124
|
* least one digit, and exactly `d` digits after the point. `spread` is what
|
|
109
125
|
* makes that true of every magnitude rather than of the ones below a threshold.
|
|
110
126
|
*
|
|
111
|
-
* **
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
127
|
+
* **The scale is `round(x, d)`'s**, `scaleOf` from the numeric library, so
|
|
128
|
+
* the display conversion and the rounding call multiply by one value
|
|
129
|
+
* (`stdlib.md` 20.7) and cannot part by an ulp at the one count where the
|
|
130
|
+
* host's power does.
|
|
131
|
+
*
|
|
132
|
+
* **The digits are the shortest form's, zero filled, at every magnitude.**
|
|
133
|
+
* Below 2 ** 53 a rounded whole number has no digits but its own, so nothing
|
|
134
|
+
* ordinary changes; at or above it the shortest form is what both engines
|
|
135
|
+
* write, and it is what this uses inside the scaling range as well as past it,
|
|
136
|
+
* where scaling by ten to the `d` leaves binary64 altogether and there is
|
|
137
|
+
* nothing to round: a binary64 that large is a whole number already, and a
|
|
138
|
+
* decimal place that far from the leading digit is past every digit the value
|
|
139
|
+
* carries. One rule for the digits at every magnitude, rather than the exact
|
|
140
|
+
* binary expansion below a threshold and the shortest form above it.
|
|
119
141
|
*/
|
|
120
142
|
function fixed(x: number, decimals: number): string {
|
|
121
|
-
const scaled = roundHalfAway(x *
|
|
143
|
+
const scaled = roundHalfAway(x * scaleOf(decimals));
|
|
122
144
|
const usable = Number.isFinite(scaled);
|
|
123
145
|
const sign = (usable ? scaled < 0 : x < 0) ? '-' : '';
|
|
124
|
-
//
|
|
125
|
-
//
|
|
146
|
+
// The digits are the shortest form's at every magnitude, through the one
|
|
147
|
+
// writer: a rounded whole below 2 ** 53 has no other digits, and above it
|
|
148
|
+
// the shortest form is the one rule both engines write (language.md 5.5).
|
|
126
149
|
const magnitude = usable
|
|
127
|
-
? spread(Math.abs(scaled)
|
|
128
|
-
: spread(Math.abs(x)
|
|
150
|
+
? spread(canonicalNumber(Math.abs(scaled)), 0)
|
|
151
|
+
: spread(canonicalNumber(Math.abs(x)), decimals);
|
|
129
152
|
const digits = written(magnitude).padStart(decimals + 1, '0');
|
|
130
153
|
if (decimals === 0) return sign + digits;
|
|
131
154
|
const whole = digits.slice(0, digits.length - decimals);
|
|
@@ -143,7 +166,7 @@ function fixed(x: number, decimals: number): string {
|
|
|
143
166
|
* have. `str.repeat` measures first for the same reason.
|
|
144
167
|
*/
|
|
145
168
|
function lengthOf(x: number, decimals: number): number {
|
|
146
|
-
const before = Math.max(1, spread(Math.abs(x)
|
|
169
|
+
const before = Math.max(1, spread(canonicalNumber(Math.abs(x)), 0).point);
|
|
147
170
|
return before + decimals + (decimals > 0 ? 1 : 0);
|
|
148
171
|
}
|
|
149
172
|
|
|
@@ -154,22 +177,17 @@ function lengthOf(x: number, decimals: number): number {
|
|
|
154
177
|
* which accepts hexadecimal, infinities and a bare leading point in some
|
|
155
178
|
* languages and not others. Absence rather than zero for text that is not a
|
|
156
179
|
* number, so a script can tell text that is not a number apart from the number
|
|
157
|
-
* zero.
|
|
180
|
+
* zero. The whitespace ignored at either end is `str.trim`'s set and no other.
|
|
158
181
|
*/
|
|
159
182
|
const NUMERIC = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
160
183
|
|
|
161
184
|
function parseNumber(text: string): Value {
|
|
162
|
-
const
|
|
163
|
-
if (!NUMERIC.test(
|
|
164
|
-
const parsed = Number(
|
|
185
|
+
const bare = trimmed(text);
|
|
186
|
+
if (!NUMERIC.test(bare)) return null;
|
|
187
|
+
const parsed = Number(bare);
|
|
165
188
|
return Number.isFinite(parsed) ? (parsed === 0 ? 0 : parsed) : null;
|
|
166
189
|
}
|
|
167
190
|
|
|
168
|
-
/** Code points, which is what every index in this file counts. */
|
|
169
|
-
function points(text: string): string[] {
|
|
170
|
-
return [...text];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
191
|
function makeArray(ctx: CallContext, items: Value[]): Value {
|
|
174
192
|
ctx.guard.array(ctx.span, 'the array', items.length);
|
|
175
193
|
return reference(ctx.heap.allocate({ kind: 'array', items }));
|
|
@@ -198,7 +216,7 @@ export const TEXT_ENTRIES: readonly ManifestEntry[] = [
|
|
|
198
216
|
|
|
199
217
|
entry('str.upper', 's', (ctx, args) => map(ctx, args, (s) => s.toUpperCase())),
|
|
200
218
|
entry('str.lower', 's', (ctx, args) => map(ctx, args, (s) => s.toLowerCase())),
|
|
201
|
-
entry('str.trim', 's', (ctx, args) => map(ctx, args,
|
|
219
|
+
entry('str.trim', 's', (ctx, args) => map(ctx, args, trimmed)),
|
|
202
220
|
|
|
203
221
|
entry('str.contains', 's part', (_ctx, args) => pair(args, (s, part) => s.includes(part))),
|
|
204
222
|
entry('str.startsWith', 's part', (_ctx, args) => pair(args, (s, part) => s.startsWith(part))),
|
package/src/core/engine/load.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* bar four thousand with half a study on the screen.
|
|
11
11
|
*/
|
|
12
12
|
import type { Diagnostic } from '../diagnostics/index.js';
|
|
13
|
+
import { canonicalise } from '../emit/index.js';
|
|
13
14
|
import type { SourceFile } from '../source/index.js';
|
|
14
15
|
import { BAR_FIELDS } from './bars.js';
|
|
15
16
|
import { limitsWith } from './budget.js';
|
|
@@ -116,3 +117,70 @@ export function load(program: unknown, options: LoadOptions = {}): LoadResult {
|
|
|
116
117
|
inputs: resolved.inputs,
|
|
117
118
|
};
|
|
118
119
|
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Loads a compiled program from text, which is where canonicity is required.
|
|
123
|
+
*
|
|
124
|
+
* `load` takes a parsed object because a host that compiled in this process
|
|
125
|
+
* never serialised, and there is nothing for such an object to be canonical
|
|
126
|
+
* about. Text is the other boundary, `compiled-program.md` 9.4 step 1 and
|
|
127
|
+
* section 13 (decision 57): a program an engine reads from outside its process
|
|
128
|
+
* arrives as the canonical encoding of 2.14, and text that parses to a program
|
|
129
|
+
* but is not that encoding is refused, because the hash a host recorded was
|
|
130
|
+
* taken over canonical bytes and text in any other spelling is text that hash
|
|
131
|
+
* does not name. So the text is parsed, written out again by the one canonical
|
|
132
|
+
* writer, and the two are compared character for character. Either failure is
|
|
133
|
+
* OS6018 naming where the text stops being readable or stops being canonical,
|
|
134
|
+
* counted in characters of the text, which is where an editor's cursor lands.
|
|
135
|
+
*
|
|
136
|
+
* What passes here is the object `load` verifies from step 2 on, so every
|
|
137
|
+
* refusal that applies to an object applies to text as well and in the same
|
|
138
|
+
* order.
|
|
139
|
+
*/
|
|
140
|
+
export function loadText(text: string, options: LoadOptions = {}): LoadResult {
|
|
141
|
+
let parsed: unknown;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(text);
|
|
144
|
+
} catch (thrown) {
|
|
145
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
146
|
+
const at = /position (\d+)/.exec(message);
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
diagnostic: malformed(
|
|
150
|
+
at === null ? 'the text' : `character ${at[1]}`,
|
|
151
|
+
'the text stops being the object notation of section 2.14 there, so it is not a program',
|
|
152
|
+
),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let canonical: string;
|
|
157
|
+
try {
|
|
158
|
+
canonical = canonicalise(parsed);
|
|
159
|
+
} catch (thrown) {
|
|
160
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
diagnostic: malformed('the text', `it parses but cannot be written in the canonical form: ${message}`),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (canonical !== text) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
diagnostic: malformed(
|
|
170
|
+
`character ${firstDifference(text, canonical)}`,
|
|
171
|
+
'the text is not the canonical encoding of the program it parses to, and a program ' +
|
|
172
|
+
'read from outside the process has to be, because that is what its hash was taken over',
|
|
173
|
+
),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return load(parsed, options);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Where two texts part, as an index into either, or the shorter one's length. */
|
|
180
|
+
function firstDifference(a: string, b: string): number {
|
|
181
|
+
const shorter = Math.min(a.length, b.length);
|
|
182
|
+
for (let i = 0; i < shorter; i += 1) {
|
|
183
|
+
if (a[i] !== b[i]) return i;
|
|
184
|
+
}
|
|
185
|
+
return shorter;
|
|
186
|
+
}
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
import type { Span } from '../span/index.js';
|
|
29
29
|
import { barField, factsFor } from './bars.js';
|
|
30
30
|
import type { HostBar } from './bars.js';
|
|
31
|
+
import { recordSource } from './bar-source.js';
|
|
32
|
+
import type { BarSource } from './bar-source.js';
|
|
31
33
|
import { Budget, stepBound } from './budget.js';
|
|
32
34
|
import type { Clock, EngineLimits } from './budget.js';
|
|
33
35
|
import { Channels } from './channels.js';
|
|
@@ -59,12 +61,7 @@ export interface BodyInput {
|
|
|
59
61
|
*/
|
|
60
62
|
export interface NestedRequests {
|
|
61
63
|
readonly empty: boolean;
|
|
62
|
-
fill(
|
|
63
|
-
registers: Registers,
|
|
64
|
-
bars: readonly HostBar[],
|
|
65
|
-
index: number,
|
|
66
|
-
fresh: boolean,
|
|
67
|
-
): void;
|
|
64
|
+
fill(registers: Registers, bars: BarSource, index: number, fresh: boolean): void;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
export interface BodyParts {
|
|
@@ -120,6 +117,7 @@ export class RequestBody {
|
|
|
120
117
|
private lastClose: number | null = null;
|
|
121
118
|
/** The requested bars themselves, kept only when a read inside folds them. */
|
|
122
119
|
private readonly history: HostBar[] = [];
|
|
120
|
+
private readonly historySource = recordSource(this.history);
|
|
123
121
|
/**
|
|
124
122
|
* The value the last closed requested bar handed back.
|
|
125
123
|
*
|
|
@@ -251,7 +249,7 @@ export class RequestBody {
|
|
|
251
249
|
const nested = this.parts.nested;
|
|
252
250
|
if (nested !== undefined && !nested.empty) {
|
|
253
251
|
this.history[index] = bar;
|
|
254
|
-
nested.fill(this.registers, this.
|
|
252
|
+
nested.fill(this.registers, this.historySource, index, this.touches === 0);
|
|
255
253
|
}
|
|
256
254
|
|
|
257
255
|
this.machine.run();
|
|
@@ -218,9 +218,9 @@ export function undatable(plan: RequestPlan): boolean {
|
|
|
218
218
|
*
|
|
219
219
|
* OS6012's own message, because the zone is an instrument fact the host did not
|
|
220
220
|
* supply and that is exactly what this read needs and cannot default. Nothing
|
|
221
|
-
* is raised: a fact the host did not state leaves the read absent,
|
|
222
|
-
*
|
|
223
|
-
*
|
|
221
|
+
* is raised: a fact the host did not state leaves the read absent, as
|
|
222
|
+
* `host-interface.md` 4.5 settles it, and this is the sentence that explains
|
|
223
|
+
* the absence rather than a second answer to it.
|
|
224
224
|
*/
|
|
225
225
|
export function undatableReason(query: RequestQuery): string {
|
|
226
226
|
return diagnosticFor('OS6012', NO_POSITION, { fact: 'a timezone', symbol: named(query) }).message;
|