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
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
*/
|
|
49
49
|
import type { Span } from '../span/index.js';
|
|
50
50
|
import type { HostBar } from './bars.js';
|
|
51
|
+
import { sourceOf } from './bar-source.js';
|
|
52
|
+
import type { BarSource } from './bar-source.js';
|
|
51
53
|
import type { Clock, EngineLimits } from './budget.js';
|
|
52
54
|
import type { EngineHost } from './host.js';
|
|
53
55
|
import type { ResolvedInput } from './inputs.js';
|
|
@@ -97,7 +99,7 @@ interface Read {
|
|
|
97
99
|
readonly register: number;
|
|
98
100
|
readonly body: RequestBody;
|
|
99
101
|
/** The host's bars for a `symbol` read, or nothing when the fold is the chart's. */
|
|
100
|
-
readonly source:
|
|
102
|
+
readonly source: BarSource | undefined;
|
|
101
103
|
readonly ready: boolean;
|
|
102
104
|
readonly reason: string;
|
|
103
105
|
/** How far into the source the fold has read. */
|
|
@@ -146,7 +148,7 @@ export class RequestSet {
|
|
|
146
148
|
* have been revised since the last execution, and the bucket it is inside has
|
|
147
149
|
* to be rebuilt from the revision rather than from the reading it replaced.
|
|
148
150
|
*/
|
|
149
|
-
fill(registers: Registers, bars:
|
|
151
|
+
fill(registers: Registers, bars: BarSource, index: number, fresh: boolean): void {
|
|
150
152
|
const heap = this.parts.heapOf();
|
|
151
153
|
for (const read of this.reads) {
|
|
152
154
|
if (fresh) {
|
|
@@ -227,7 +229,7 @@ function build(parts: RequestParts, request: Request, plan: RequestPlan): Read {
|
|
|
227
229
|
plan,
|
|
228
230
|
register: request.series,
|
|
229
231
|
body,
|
|
230
|
-
source: answer !== undefined && 'bars' in answer ? answer.bars : undefined,
|
|
232
|
+
source: answer !== undefined && 'bars' in answer ? sourceOf(answer.bars) : undefined,
|
|
231
233
|
// A fold of the chart's own bars needs nothing from the host, so it is
|
|
232
234
|
// answered the moment it is planned. A read the host is still fetching is
|
|
233
235
|
// not: the read is absent, `req.isReady` is false, and the study keeps
|
|
@@ -247,9 +249,9 @@ function build(parts: RequestParts, request: Request, plan: RequestPlan): Read {
|
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
/** The read's value for the chart bar at `index`, and the fold that gets there. */
|
|
250
|
-
function valueOf(read: Read, bars:
|
|
252
|
+
function valueOf(read: Read, bars: BarSource, index: number): Value {
|
|
251
253
|
if (!read.ready) return ABSENT;
|
|
252
|
-
const bar = bars
|
|
254
|
+
const bar = bars.at(index);
|
|
253
255
|
const at = bar?.time ?? null;
|
|
254
256
|
if (at === null) return ABSENT;
|
|
255
257
|
const key = keyOf(read, at);
|
|
@@ -258,7 +260,7 @@ function valueOf(read: Read, bars: readonly HostBar[], index: number): Value {
|
|
|
258
260
|
const source = read.source ?? bars;
|
|
259
261
|
const lookahead = read.plan.request.mode === 'lookahead';
|
|
260
262
|
for (;;) {
|
|
261
|
-
const next = source
|
|
263
|
+
const next = source.at(read.cursor);
|
|
262
264
|
if (next === undefined) break;
|
|
263
265
|
const found = next.time === null ? undefined : keyOf(read, next.time);
|
|
264
266
|
if (found === undefined) {
|
|
@@ -310,7 +312,7 @@ function feed(read: Read, bar: HostBar, key: number): void {
|
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
/** A re-executed bar: the fold as it stood when the bar began. */
|
|
313
|
-
function restore(read: Read, bars:
|
|
315
|
+
function restore(read: Read, bars: BarSource): void {
|
|
314
316
|
read.cursor = read.mark.cursor;
|
|
315
317
|
read.openKey = read.mark.openKey;
|
|
316
318
|
read.openFrom = read.mark.openFrom;
|
|
@@ -318,7 +320,7 @@ function restore(read: Read, bars: readonly HostBar[]): void {
|
|
|
318
320
|
if (read.openKey === undefined) return;
|
|
319
321
|
const source = read.source ?? bars;
|
|
320
322
|
for (let at = read.openFrom; at < read.cursor; at += 1) {
|
|
321
|
-
const bar = source
|
|
323
|
+
const bar = source.at(at);
|
|
322
324
|
if (bar === undefined || bar.time === null) continue;
|
|
323
325
|
if (keyOf(read, bar.time) !== read.openKey) continue;
|
|
324
326
|
read.open = read.open === undefined ? start(bar) : extend(read.open, bar);
|
|
@@ -380,3 +382,4 @@ function lower(a: number | null, b: number | null | undefined): number | null {
|
|
|
380
382
|
function both(a: number | null, b: number | null): number | null {
|
|
381
383
|
return a === null || b === null ? null : a + b;
|
|
382
384
|
}
|
|
385
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The three things the engine checks about the bars it is handed,
|
|
3
3
|
* `host-interface.md` 3.5 and conformance item 1.
|
|
4
4
|
*
|
|
5
5
|
* Everything else about a series the engine takes exactly as stated: it does
|
|
@@ -55,6 +55,23 @@ export function noBars(instrument: Instrument | undefined): Diagnostic {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* A bar handed over with no time, OS6025.
|
|
60
|
+
*
|
|
61
|
+
* `host-interface.md` 3.1 states `time` for every bar with no absent case, and
|
|
62
|
+
* the order rule is built on it. Reading a missing one as the absent value, as
|
|
63
|
+
* `barField` would, steps the bar over every calendar fold and makes every
|
|
64
|
+
* session fact absent on a bar that is on the chart: a study draws a gap where
|
|
65
|
+
* the host has data, and nothing says why. It is a bar of the wrong shape, not
|
|
66
|
+
* one out of order, so it has a code and a fix of its own. A time that is not a
|
|
67
|
+
* finite number is no time either.
|
|
68
|
+
*/
|
|
69
|
+
export function undated(bar: HostBar, index: number): Diagnostic | undefined {
|
|
70
|
+
const time: unknown = bar.time;
|
|
71
|
+
if (typeof time === 'number' && Number.isFinite(time)) return undefined;
|
|
72
|
+
return failure('OS6025', NO_POSITION, { index });
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
/**
|
|
59
76
|
* A bar whose time does not follow the one before it, OS6011.
|
|
60
77
|
*
|
|
@@ -63,11 +80,9 @@ export function noBars(instrument: Instrument | undefined): Diagnostic {
|
|
|
63
80
|
* only one whose position tells the host where its own ordering went wrong.
|
|
64
81
|
*
|
|
65
82
|
* `previous` is the bar already handed over, or nothing for the first bar of a
|
|
66
|
-
* run, which follows nothing and is therefore always in order. A
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* so inventing this one for it would be a refusal the specification does not
|
|
70
|
-
* describe.
|
|
83
|
+
* run, which follows nothing and is therefore always in order. A bar with no
|
|
84
|
+
* time never reaches this comparison: `undated` refuses it first, because a
|
|
85
|
+
* bar dated nothing is not two instants in the wrong order.
|
|
71
86
|
*/
|
|
72
87
|
export function outOfOrder(
|
|
73
88
|
bar: HostBar,
|
|
@@ -80,3 +95,12 @@ export function outOfOrder(
|
|
|
80
95
|
if (time > before) return undefined;
|
|
81
96
|
return failure('OS6011', NO_POSITION, { index, time, previous: index - 1 });
|
|
82
97
|
}
|
|
98
|
+
|
|
99
|
+
/** Both checks of a bar handed over, in the order they are made: its shape, then its order. */
|
|
100
|
+
export function handOver(
|
|
101
|
+
bar: HostBar,
|
|
102
|
+
previous: HostBar | undefined,
|
|
103
|
+
index: number,
|
|
104
|
+
): Diagnostic | undefined {
|
|
105
|
+
return undated(bar, index) ?? outOfOrder(bar, previous, index);
|
|
106
|
+
}
|
|
@@ -106,10 +106,10 @@ function hoursOf(stated: SessionHours | undefined): Hours | null {
|
|
|
106
106
|
*
|
|
107
107
|
* Each of the three is reported at load rather than answered with absence,
|
|
108
108
|
* because absence there is indistinguishable from the honest case and the honest
|
|
109
|
-
* case is the one a reader will assume.
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
109
|
+
* case is the one a reader will assume. A bare read of an instrument fact the
|
|
110
|
+
* host did not state is a different question with the opposite answer: it is
|
|
111
|
+
* absent and never refused (`host-interface.md` 4.5, decision 66), because a
|
|
112
|
+
* record the host contradicts itself in is not a fact it left out.
|
|
113
113
|
*/
|
|
114
114
|
export function recordProblem(
|
|
115
115
|
stated: SessionHours | undefined,
|
|
@@ -21,8 +21,51 @@ const META_KINDS = ['study', 'strategy'] as const;
|
|
|
21
21
|
const CHANNEL_TYPES = ['number', 'string', 'color', 'bool'] as const;
|
|
22
22
|
const EFFECTS = ['none', 'signal', 'order', 'draw', 'log'] as const;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The tables a later minor of this format major added, with the minor that
|
|
26
|
+
* added each.
|
|
27
|
+
*
|
|
28
|
+
* 9.2's rule read from the other side, which 9.4 step 3 states and decision 56
|
|
29
|
+
* settled: a table a later minor added and an earlier program lacks reads as
|
|
30
|
+
* empty, never as a refusal, because 9.5's first line is a promise about that
|
|
31
|
+
* program. A program stamped at this minor or a later one has no such excuse.
|
|
32
|
+
* Section 2 says an empty table is written as an empty array and never
|
|
33
|
+
* omitted, so its absence there is the defect check 1 exists for, and the
|
|
34
|
+
* version is what tells the two apart.
|
|
35
|
+
*
|
|
36
|
+
* `spec/format-history.json` records the same additions, one entry per format
|
|
37
|
+
* version, and `tests/engine/format-minors.test.ts` holds this list to that
|
|
38
|
+
* file: a table the history says a later minor added has to be one a program
|
|
39
|
+
* at the earlier minor may lack.
|
|
40
|
+
*/
|
|
41
|
+
const ADDED_AT_MINOR: readonly { readonly table: string; readonly minor: number }[] = [
|
|
42
|
+
{ table: 'requests', minor: 1 },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** The minor of a `major.minor` the version step has already proved is one. */
|
|
46
|
+
function minorOf(raw: Readonly<Record<string, unknown>>): number {
|
|
47
|
+
const version = raw['openscript'] as Readonly<Record<string, unknown>>;
|
|
48
|
+
return Number(String(version['format']).split('.')[1] ?? '0');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Writes the empty table an earlier minor is owed into the program itself.
|
|
53
|
+
*
|
|
54
|
+
* Into the object rather than into a local, because every later step reads
|
|
55
|
+
* the program as its own type and would find the table missing again: the
|
|
56
|
+
* verifier hands the same object on, and the engine holds it.
|
|
57
|
+
*/
|
|
58
|
+
function supplyAddedTables(raw: Readonly<Record<string, unknown>>): void {
|
|
59
|
+
const minor = minorOf(raw);
|
|
60
|
+
for (const added of ADDED_AT_MINOR) {
|
|
61
|
+
if (raw[added.table] !== undefined || minor >= added.minor) continue;
|
|
62
|
+
(raw as Record<string, unknown>)[added.table] = [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
24
66
|
/** Check 1 over every table the machine indexes, and every declaration field. */
|
|
25
67
|
export function checkTables(shape: ShapeCheck, raw: Readonly<Record<string, unknown>>): boolean {
|
|
68
|
+
supplyAddedTables(raw);
|
|
26
69
|
for (const name of ['requires', 'inputs', 'channels', 'consts', 'series', 'cells', 'states',
|
|
27
70
|
'functions', 'callSites', 'loops', 'code', 'requests']) {
|
|
28
71
|
if (!shape.array(raw[name], name)) return false;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls, written out as OpenScript text.
|
|
3
|
+
*
|
|
4
|
+
* A call to a function the script declares keeps its arguments, with a named
|
|
5
|
+
* argument's label following the parameter's new spelling where it was renamed.
|
|
6
|
+
* A call to a built-in is translated from its row in the table, one argument to
|
|
7
|
+
* one argument where they line up, and by a translator of its own where they do
|
|
8
|
+
* not: the highest and lowest of a window that default their source, a
|
|
9
|
+
* deviation whose `biased` flag is the negation of OpenScript's `sample`, a
|
|
10
|
+
* true range whose absent first bar is a flag, and the handful of conversions
|
|
11
|
+
* that are an expression rather than a call in OpenScript.
|
|
12
|
+
*
|
|
13
|
+
* A call that draws or trades is a statement, not a value, and its translator
|
|
14
|
+
* lives with the other statements; used as a value it is refused.
|
|
15
|
+
*/
|
|
16
|
+
import { diagnosticFor } from '../diagnostics/index.js';
|
|
17
|
+
import { canonicalNumber } from '../emit/index.js';
|
|
18
|
+
import type { Context } from './context.js';
|
|
19
|
+
import type { Binding } from './names.js';
|
|
20
|
+
import { levelCall, plotCall, translateInput } from './outputs.js';
|
|
21
|
+
import { neverAbsent } from './presence.js';
|
|
22
|
+
import { pathOf } from './syntax.js';
|
|
23
|
+
import type { Arg, CallExpr, Place } from './syntax.js';
|
|
24
|
+
import { CALLS } from './table.js';
|
|
25
|
+
import type { Mapping } from './table.js';
|
|
26
|
+
import { NOTHING, PREC, atom, translate, wrap } from './values.js';
|
|
27
|
+
import type { Out } from './values.js';
|
|
28
|
+
|
|
29
|
+
/** A call's arguments against a parameter list: by name, and what fitted none. */
|
|
30
|
+
export interface Bound {
|
|
31
|
+
readonly byName: ReadonlyMap<string, Arg>;
|
|
32
|
+
/** Arguments past the positional list, named by their position. */
|
|
33
|
+
readonly stray: readonly { readonly arg: Arg; readonly name: string }[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function bind(call: CallExpr, positional: readonly string[]): Bound {
|
|
37
|
+
const byName = new Map<string, Arg>();
|
|
38
|
+
const stray: { arg: Arg; name: string }[] = [];
|
|
39
|
+
let position = 0;
|
|
40
|
+
for (const arg of call.args) {
|
|
41
|
+
if (arg.label !== undefined) {
|
|
42
|
+
byName.set(arg.label, arg);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const name = positional[position];
|
|
46
|
+
if (name === undefined) stray.push({ arg, name: `argument ${canonicalNumber(position + 1)}` });
|
|
47
|
+
else byName.set(name, arg);
|
|
48
|
+
position += 1;
|
|
49
|
+
}
|
|
50
|
+
return { byName, stray };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Refuses the first argument that is not one of `known`, with OS9006. Returns
|
|
55
|
+
* whether the call may go on.
|
|
56
|
+
*/
|
|
57
|
+
export function onlyKnown(ctx: Context, path: string, bound: Bound, known: readonly string[]): boolean {
|
|
58
|
+
const first = bound.stray[0];
|
|
59
|
+
if (first !== undefined) {
|
|
60
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(first.arg.at), { argument: first.name, call: path }));
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
for (const [name, arg] of bound.byName) {
|
|
64
|
+
if (known.includes(name)) continue;
|
|
65
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(arg.at), { argument: name, call: path }));
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** OS9007, once per built-in: its first bars follow OpenScript's own rules. */
|
|
72
|
+
export function warmupNote(ctx: Context, at: Place, call: string, target: string): void {
|
|
73
|
+
ctx.note(diagnosticFor('OS9007', ctx.span(at), { call, target }), `OS9007 ${call}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The translated text of one argument, or undefined where the call left it out. */
|
|
77
|
+
export function argText(ctx: Context, bound: Bound, name: string): string | undefined {
|
|
78
|
+
const arg = bound.byName.get(name);
|
|
79
|
+
return arg === undefined ? undefined : translate(ctx, arg.value).text;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Arguments by position up to the first one left out, and by name after it. */
|
|
83
|
+
function written(texts: readonly (string | undefined)[], labels: readonly string[]): string[] | undefined {
|
|
84
|
+
const parts: string[] = [];
|
|
85
|
+
let gap = false;
|
|
86
|
+
for (let i = 0; i < texts.length; i += 1) {
|
|
87
|
+
const text = texts[i];
|
|
88
|
+
if (text === undefined) {
|
|
89
|
+
gap = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!gap) parts.push(text);
|
|
93
|
+
else {
|
|
94
|
+
const label = labels[i];
|
|
95
|
+
if (label === undefined) return undefined;
|
|
96
|
+
parts.push(`${label} = ${text}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return parts;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A row whose arguments line up one to one, which is most of the table. */
|
|
103
|
+
export function generic(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
104
|
+
const bound = bind(call, row.params);
|
|
105
|
+
if (!onlyKnown(ctx, path, bound, row.params)) return NOTHING;
|
|
106
|
+
const parts = written(row.params.map((name) => argText(ctx, bound, name)), row.targets);
|
|
107
|
+
if (parts === undefined) {
|
|
108
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: 'an argument left out before another', call: path }));
|
|
109
|
+
return NOTHING;
|
|
110
|
+
}
|
|
111
|
+
if (row.difference === 'warmup') warmupNote(ctx, call.callee.at, path, row.target);
|
|
112
|
+
return atom(`${row.target}(${parts.join(', ')})`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function userCall(ctx: Context, call: CallExpr, binding: Binding): Out {
|
|
116
|
+
const parts = call.args.map((arg) => {
|
|
117
|
+
const text = translate(ctx, arg.value).text;
|
|
118
|
+
if (arg.label === undefined) return text;
|
|
119
|
+
return `${binding.labels?.get(arg.label) ?? arg.label} = ${text}`;
|
|
120
|
+
});
|
|
121
|
+
return atom(`${binding.output}(${parts.join(', ')})`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** `ta.tr` read as a value, which is the call with `handle_na` false. */
|
|
125
|
+
export function trueRangeValue(): Out {
|
|
126
|
+
return { text: 'isNone(close[1]) ? none : trueRange()', prec: PREC.ternary };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function trueRangeCall(ctx: Context, call: CallExpr, path: string): Out {
|
|
130
|
+
const bound = bind(call, ['handle_na']);
|
|
131
|
+
if (!onlyKnown(ctx, path, bound, ['handle_na'])) return NOTHING;
|
|
132
|
+
const flag = bound.byName.get('handle_na')?.value;
|
|
133
|
+
if (flag === undefined || (flag.kind === 'bool' && !flag.value)) return trueRangeValue();
|
|
134
|
+
if (flag.kind === 'bool') return atom('trueRange()');
|
|
135
|
+
const handled = translate(ctx, flag);
|
|
136
|
+
return { text: `${wrap(handled, PREC.or)} or not isNone(close[1]) ? trueRange() : none`, prec: PREC.ternary };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function extreme(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
140
|
+
const one = call.args.length === 1 && call.args[0]?.label === undefined;
|
|
141
|
+
const bound = bind(call, one ? ['length'] : ['source', 'length']);
|
|
142
|
+
if (!onlyKnown(ctx, path, bound, ['source', 'length'])) return NOTHING;
|
|
143
|
+
const source = argText(ctx, bound, 'source') ?? (row.target === 'highest' ? 'high' : 'low');
|
|
144
|
+
const length = argText(ctx, bound, 'length');
|
|
145
|
+
if (length === undefined) {
|
|
146
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: 'length', call: path }));
|
|
147
|
+
return NOTHING;
|
|
148
|
+
}
|
|
149
|
+
warmupNote(ctx, call.callee.at, path, row.target);
|
|
150
|
+
return atom(`${row.target}(${source}, ${length})`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function deviation(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
154
|
+
const bound = bind(call, row.params);
|
|
155
|
+
if (!onlyKnown(ctx, path, bound, row.params)) return NOTHING;
|
|
156
|
+
const source = argText(ctx, bound, 'source');
|
|
157
|
+
const length = argText(ctx, bound, 'length');
|
|
158
|
+
if (source === undefined || length === undefined) {
|
|
159
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: source === undefined ? 'source' : 'length', call: path }));
|
|
160
|
+
return NOTHING;
|
|
161
|
+
}
|
|
162
|
+
const biased = bound.byName.get('biased')?.value;
|
|
163
|
+
let sample = '';
|
|
164
|
+
if (biased !== undefined && !(biased.kind === 'bool' && biased.value)) {
|
|
165
|
+
sample = biased.kind === 'bool' ? ', sample = true' : `, sample = not ${wrap(translate(ctx, biased), PREC.unary)}`;
|
|
166
|
+
}
|
|
167
|
+
warmupNote(ctx, call.callee.at, path, row.target);
|
|
168
|
+
return atom(`${row.target}(${source}, ${length}${sample})`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A condition handed to a counting built-in, which the source dialect never sees absent. */
|
|
172
|
+
function condition(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
173
|
+
const bound = bind(call, row.params);
|
|
174
|
+
if (!onlyKnown(ctx, path, bound, row.params)) return NOTHING;
|
|
175
|
+
const texts = row.params.map((name) => {
|
|
176
|
+
const arg = bound.byName.get(name);
|
|
177
|
+
if (arg === undefined) return undefined;
|
|
178
|
+
const text = translate(ctx, arg.value).text;
|
|
179
|
+
return name === 'condition' && !neverAbsent(ctx, arg.value) ? `orElse(${text}, false)` : text;
|
|
180
|
+
});
|
|
181
|
+
const parts = written(texts, row.targets);
|
|
182
|
+
if (parts === undefined || texts[0] === undefined) {
|
|
183
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: 'condition', call: path }));
|
|
184
|
+
return NOTHING;
|
|
185
|
+
}
|
|
186
|
+
warmupNote(ctx, call.callee.at, path, row.target);
|
|
187
|
+
return atom(`${row.target}(${parts.join(', ')})`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** `math.max` and `math.min` take any number of arguments; OpenScript's take two. */
|
|
191
|
+
function extremes(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
192
|
+
const labelled = call.args.find((arg) => arg.label !== undefined);
|
|
193
|
+
if (labelled !== undefined || call.args.length < 2) {
|
|
194
|
+
const at = labelled?.at ?? call.at;
|
|
195
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(at), { argument: labelled?.label ?? 'a single argument', call: path }));
|
|
196
|
+
return NOTHING;
|
|
197
|
+
}
|
|
198
|
+
const texts = call.args.map((arg) => translate(ctx, arg.value).text);
|
|
199
|
+
let text = texts[0] ?? '';
|
|
200
|
+
for (const next of texts.slice(1)) text = `${row.target}(${text}, ${next})`;
|
|
201
|
+
return atom(text);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function fallback(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
205
|
+
const bound = bind(call, row.params);
|
|
206
|
+
if (!onlyKnown(ctx, path, bound, row.params)) return NOTHING;
|
|
207
|
+
const source = argText(ctx, bound, 'source');
|
|
208
|
+
if (source === undefined) {
|
|
209
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: 'source', call: path }));
|
|
210
|
+
return NOTHING;
|
|
211
|
+
}
|
|
212
|
+
return atom(`orElse(${source}, ${argText(ctx, bound, 'replacement') ?? '0'})`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function rgb(ctx: Context, call: CallExpr, row: Mapping, path: string): Out {
|
|
216
|
+
const bound = bind(call, row.params);
|
|
217
|
+
if (!onlyKnown(ctx, path, bound, row.params)) return NOTHING;
|
|
218
|
+
const channels = ['red', 'green', 'blue'].map((name) => argText(ctx, bound, name));
|
|
219
|
+
if (channels.some((one) => one === undefined)) {
|
|
220
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: 'a colour channel', call: path }));
|
|
221
|
+
return NOTHING;
|
|
222
|
+
}
|
|
223
|
+
const colour = `rgb(${channels.join(', ')})`;
|
|
224
|
+
const transp = argText(ctx, bound, 'transp');
|
|
225
|
+
return atom(transp === undefined ? colour : `fade(${colour}, ${transp})`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function translateCall(ctx: Context, call: CallExpr): Out {
|
|
229
|
+
const path = pathOf(call.callee);
|
|
230
|
+
const root = path?.split('.')[0];
|
|
231
|
+
const binding = root === undefined ? undefined : ctx.lookup(root, call.callee.at);
|
|
232
|
+
if (path === undefined || (binding !== undefined && (path !== root || binding.kind !== 'function'))) {
|
|
233
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(call.callee.at), { construct: 'a call on a value' }));
|
|
234
|
+
return NOTHING;
|
|
235
|
+
}
|
|
236
|
+
if (binding !== undefined) return userCall(ctx, call, binding);
|
|
237
|
+
const row = CALLS.get(path);
|
|
238
|
+
if (row === undefined) {
|
|
239
|
+
ctx.refuse(diagnosticFor('OS9003', ctx.span(call.callee.at), { name: path }));
|
|
240
|
+
return NOTHING;
|
|
241
|
+
}
|
|
242
|
+
switch (row.special) {
|
|
243
|
+
case undefined:
|
|
244
|
+
return generic(ctx, call, row, path);
|
|
245
|
+
case 'trueRange':
|
|
246
|
+
return trueRangeCall(ctx, call, path);
|
|
247
|
+
case 'extreme':
|
|
248
|
+
return extreme(ctx, call, row, path);
|
|
249
|
+
case 'deviation':
|
|
250
|
+
return deviation(ctx, call, row, path);
|
|
251
|
+
case 'condition':
|
|
252
|
+
return condition(ctx, call, row, path);
|
|
253
|
+
case 'extremes':
|
|
254
|
+
return extremes(ctx, call, row, path);
|
|
255
|
+
case 'fallback':
|
|
256
|
+
return fallback(ctx, call, row, path);
|
|
257
|
+
case 'rgb':
|
|
258
|
+
return rgb(ctx, call, row, path);
|
|
259
|
+
case 'float': {
|
|
260
|
+
const bound = bind(call, row.params);
|
|
261
|
+
if (!onlyKnown(ctx, path, bound, row.params)) return NOTHING;
|
|
262
|
+
const arg = bound.byName.get('x');
|
|
263
|
+
return arg === undefined ? atom('none') : atom(`(${translate(ctx, arg.value).text})`);
|
|
264
|
+
}
|
|
265
|
+
case 'input':
|
|
266
|
+
return translateInput(ctx, call, path, false);
|
|
267
|
+
case 'plot':
|
|
268
|
+
return plotCall(ctx, call, path);
|
|
269
|
+
case 'hline':
|
|
270
|
+
return levelCall(ctx, call, path);
|
|
271
|
+
}
|
|
272
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(call.callee.at), { construct: `${path} used as a value` }));
|
|
273
|
+
return NOTHING;
|
|
274
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the translators share while they work through one script.
|
|
3
|
+
*
|
|
4
|
+
* A translation is kept or refused per top level statement, never per line
|
|
5
|
+
* inside one: a block with one line missing is a block that means something
|
|
6
|
+
* the original did not, so the unit that is kept as a comment is the whole
|
|
7
|
+
* statement at the top level. `Node` is that unit, and everything a refusal or
|
|
8
|
+
* a warning needs to point at is recorded against it rather than decided on the
|
|
9
|
+
* spot, because a statement translated now can still be refused later: by the
|
|
10
|
+
* compiler, or because an earlier statement it reads was refused.
|
|
11
|
+
*/
|
|
12
|
+
import type { Diagnostic } from '../diagnostics/index.js';
|
|
13
|
+
import { canonicalNumber } from '../emit/index.js';
|
|
14
|
+
import type { SourceFile } from '../source/index.js';
|
|
15
|
+
import type { Span } from '../span/index.js';
|
|
16
|
+
import { Namer, Scope } from './names.js';
|
|
17
|
+
import type { Binding } from './names.js';
|
|
18
|
+
import type { Expr, Lines, Named, Place, Stmt } from './syntax.js';
|
|
19
|
+
|
|
20
|
+
export interface OutLine {
|
|
21
|
+
readonly depth: number;
|
|
22
|
+
readonly text: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A finding on a statement that is kept, and the key that makes it once per script where it is once per built-in. */
|
|
26
|
+
export interface Note {
|
|
27
|
+
readonly diagnostic: Diagnostic;
|
|
28
|
+
readonly once: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Node {
|
|
32
|
+
readonly stmt: Stmt;
|
|
33
|
+
readonly lines: Lines;
|
|
34
|
+
/** Top level names the statement declares, so what reads them can follow it. */
|
|
35
|
+
readonly declares: readonly Named[];
|
|
36
|
+
/** Top level names it reads, in source order, with where. */
|
|
37
|
+
readonly reads: Named[];
|
|
38
|
+
output: OutLine[];
|
|
39
|
+
/** Why its translation was refused, if it was. */
|
|
40
|
+
refusal: Diagnostic | undefined;
|
|
41
|
+
/** Why the compiler refused the translation, if it did. */
|
|
42
|
+
failed: Diagnostic | undefined;
|
|
43
|
+
/** Why it reads a name a refused statement declared, recomputed on every pass. */
|
|
44
|
+
lost: Diagnostic | undefined;
|
|
45
|
+
readonly notes: Note[];
|
|
46
|
+
/** The declaration, which is placed first and is never kept as a comment. */
|
|
47
|
+
readonly declaration: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isKept(node: Node): boolean {
|
|
51
|
+
return node.refusal === undefined && node.failed === undefined && node.lost === undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The first reason a node is not kept, in the order the reasons are found. */
|
|
55
|
+
export function reasonOf(node: Node): Diagnostic | undefined {
|
|
56
|
+
return node.refusal ?? node.failed ?? node.lost;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type Version = 5 | 6;
|
|
60
|
+
|
|
61
|
+
export interface Reassignment {
|
|
62
|
+
readonly op: string;
|
|
63
|
+
readonly value: Expr;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Everything about the script the translators need before they start. */
|
|
67
|
+
export interface Facts {
|
|
68
|
+
readonly file: SourceFile;
|
|
69
|
+
readonly version: Version;
|
|
70
|
+
/** Every identifier the source writes. */
|
|
71
|
+
readonly used: ReadonlySet<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Every reassignment the source makes, by name, anywhere in the file. A name
|
|
74
|
+
* with any is never a constant, and is present only if every value it is
|
|
75
|
+
* given is.
|
|
76
|
+
*/
|
|
77
|
+
readonly reassigned: ReadonlyMap<string, readonly Reassignment[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Every name the file declares at the top level, wherever. OpenScript lets a
|
|
80
|
+
* block or a function see one declared below it, so an inner declaration of
|
|
81
|
+
* the same name is renamed, which the source dialect's meaning allows.
|
|
82
|
+
*/
|
|
83
|
+
readonly globals: ReadonlySet<string>;
|
|
84
|
+
/** The side each literal entry id opens, from the script's entry calls. */
|
|
85
|
+
readonly entries: ReadonlyMap<string, 'long' | 'short'>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class Context {
|
|
89
|
+
readonly namer: Namer;
|
|
90
|
+
readonly fileScope = new Scope(undefined);
|
|
91
|
+
scope: Scope = this.fileScope;
|
|
92
|
+
/** The statement being translated. */
|
|
93
|
+
node: Node | undefined;
|
|
94
|
+
/** Lines a statement needs evaluated before it, and whether it may have them. */
|
|
95
|
+
hoisted: OutLine[] = [];
|
|
96
|
+
mayHoist = false;
|
|
97
|
+
/** Inside a ternary arm, where the source dialect evaluates lazily as OpenScript does. */
|
|
98
|
+
lazy = 0;
|
|
99
|
+
depth = 0;
|
|
100
|
+
/** The declaration's pyramiding, read as the source dialect reads it: 0 is 1. */
|
|
101
|
+
pyramiding = 1;
|
|
102
|
+
/**
|
|
103
|
+
* Whether the declaration sizes orders in units. The source dialect counts a
|
|
104
|
+
* quantity written on an entry in contracts whatever the declaration says,
|
|
105
|
+
* and OpenScript counts it in the declaration's own unit.
|
|
106
|
+
*/
|
|
107
|
+
qtyInUnits = true;
|
|
108
|
+
readonly titles = new Map<string, Set<string>>();
|
|
109
|
+
readonly facts: Facts;
|
|
110
|
+
|
|
111
|
+
constructor(facts: Facts) {
|
|
112
|
+
this.facts = facts;
|
|
113
|
+
this.namer = new Namer(facts.used);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
span(at: Place): Span {
|
|
117
|
+
return this.facts.file.spanAt(at.offset, at.length);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Records the first reason the current statement cannot be translated. */
|
|
121
|
+
refuse(diagnostic: Diagnostic): void {
|
|
122
|
+
if (this.node !== undefined && this.node.refusal === undefined) this.node.refusal = diagnostic;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get refused(): boolean {
|
|
126
|
+
return this.node?.refusal !== undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
note(diagnostic: Diagnostic, once?: string): void {
|
|
130
|
+
this.node?.notes.push({ diagnostic, once });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Whether declaring `name` in the current scope needs a new spelling to keep it apart. */
|
|
134
|
+
hides(name: string): boolean {
|
|
135
|
+
return this.scope.find(name) !== undefined || (!this.scope.isGlobal && this.facts.globals.has(name));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The binding a source name has here, recording a read of a top level one. */
|
|
139
|
+
lookup(name: string, at: Place): Binding | undefined {
|
|
140
|
+
const found = this.scope.find(name);
|
|
141
|
+
if (found !== undefined && found.topLevel) this.node?.reads.push({ name, at });
|
|
142
|
+
return found;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Enters a block scope for the length of `work`. */
|
|
146
|
+
within<T>(work: () => T): T {
|
|
147
|
+
const outer = this.scope;
|
|
148
|
+
this.scope = new Scope(outer);
|
|
149
|
+
try {
|
|
150
|
+
return work();
|
|
151
|
+
} finally {
|
|
152
|
+
this.scope = outer;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Moves an expression to its own line before the current statement, and
|
|
158
|
+
* returns the name it is read under, or undefined where it cannot be moved.
|
|
159
|
+
*/
|
|
160
|
+
hoist(text: string): string | undefined {
|
|
161
|
+
if (!this.mayHoist || this.lazy > 0) return undefined;
|
|
162
|
+
const name = this.namer.fresh('everyBar');
|
|
163
|
+
this.hoisted.push({ depth: this.depth, text: `${name} = ${text}` });
|
|
164
|
+
return name;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A title for a declared output that no other output of its kind has. */
|
|
168
|
+
title(kind: string, wanted: string): string {
|
|
169
|
+
let seen = this.titles.get(kind);
|
|
170
|
+
if (seen === undefined) {
|
|
171
|
+
seen = new Set();
|
|
172
|
+
this.titles.set(kind, seen);
|
|
173
|
+
}
|
|
174
|
+
let title = wanted;
|
|
175
|
+
for (let n = 2; seen.has(title); n += 1) title = `${wanted} ${canonicalNumber(n)}`;
|
|
176
|
+
seen.add(title);
|
|
177
|
+
return title;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const HEX = '0123456789abcdef';
|
|
182
|
+
|
|
183
|
+
/** A string literal in OpenScript's spelling of it (`language.md` 3.6). */
|
|
184
|
+
export function quoted(value: string): string {
|
|
185
|
+
let out = '"';
|
|
186
|
+
for (const c of value) {
|
|
187
|
+
const code = c.codePointAt(0) ?? 0;
|
|
188
|
+
if (c === '"') out += '\\"';
|
|
189
|
+
else if (c === '\\') out += '\\\\';
|
|
190
|
+
else if (c === '\n') out += '\\n';
|
|
191
|
+
else if (c === '\t') out += '\\t';
|
|
192
|
+
else if (c === '\r') out += '\\r';
|
|
193
|
+
else if (code < 0x20) out += `\\u00${HEX[code >> 4] ?? '0'}${HEX[code & 15] ?? '0'}`;
|
|
194
|
+
else out += c;
|
|
195
|
+
}
|
|
196
|
+
return `${out}"`;
|
|
197
|
+
}
|