openalgo-script 0.5.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 +121 -0
- package/README.md +53 -22
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +6 -0
- 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/core/backtest/drive.d.ts +36 -1
- package/dist/core/backtest/drive.d.ts.map +1 -1
- package/dist/core/backtest/drive.js +13 -107
- package/dist/core/backtest/drive.js.map +1 -1
- package/dist/core/backtest/index.d.ts +2 -0
- package/dist/core/backtest/index.d.ts.map +1 -1
- package/dist/core/backtest/index.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 +178 -1
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +18 -1
- 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-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 +10 -10
- 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/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 +1 -0
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.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/index.d.ts +8 -0
- package/dist/core/engine/library/index.d.ts.map +1 -1
- package/dist/core/engine/library/index.js +10 -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/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/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 +12 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +11 -0
- 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/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/package.json +5 -2
- package/spec/errors.json +380 -20
- package/src/adapters/charts/run.ts +6 -0
- package/src/adapters/charts/tables.ts +6 -19
- package/src/adapters/charts/undrawable.ts +51 -0
- package/src/core/backtest/drive.ts +66 -154
- package/src/core/backtest/index.ts +2 -0
- package/src/core/backtest/walk.ts +182 -0
- package/src/core/catalogue/catalogue.generated.ts +18 -1
- 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-output.ts +1 -0
- package/src/core/check/library-prose.generated.ts +10 -10
- package/src/core/check/library.ts +8 -0
- package/src/core/check/surface.ts +16 -0
- 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 +1 -0
- package/src/core/engine/library/binding.ts +2 -0
- package/src/core/engine/library/index.ts +11 -0
- package/src/core/engine/library/objects.ts +1 -6
- 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/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 +13 -0
- package/src/core/stdlib/calendar/civil.ts +5 -2
- package/src/core/version/version.generated.ts +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tree the importer reads a source script into.
|
|
3
|
+
*
|
|
4
|
+
* It holds the subset of the source dialect the importer can say something
|
|
5
|
+
* about, and one node for everything else: a statement the reader could not
|
|
6
|
+
* take apart is kept whole as `refused`, carrying the few words that name what
|
|
7
|
+
* it is, so the translator can report it and keep it as a comment without
|
|
8
|
+
* having to understand it.
|
|
9
|
+
*/
|
|
10
|
+
import type { Remark } from './lexer.js';
|
|
11
|
+
|
|
12
|
+
/** A stretch of the input text: where a finding's caret goes. */
|
|
13
|
+
export interface Place {
|
|
14
|
+
readonly offset: number;
|
|
15
|
+
readonly length: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type Expr =
|
|
19
|
+
| { readonly kind: 'number'; readonly text: string; readonly at: Place }
|
|
20
|
+
| { readonly kind: 'string'; readonly value: string; readonly at: Place }
|
|
21
|
+
| { readonly kind: 'bool'; readonly value: boolean; readonly at: Place }
|
|
22
|
+
| { readonly kind: 'color'; readonly text: string; readonly at: Place }
|
|
23
|
+
| { readonly kind: 'name'; readonly name: string; readonly at: Place }
|
|
24
|
+
| { readonly kind: 'member'; readonly object: Expr; readonly property: string; readonly at: Place }
|
|
25
|
+
| { readonly kind: 'call'; readonly callee: Expr; readonly args: readonly Arg[]; readonly at: Place }
|
|
26
|
+
| { readonly kind: 'index'; readonly target: Expr; readonly index: Expr; readonly at: Place }
|
|
27
|
+
| { readonly kind: 'unary'; readonly op: string; readonly operand: Expr; readonly at: Place }
|
|
28
|
+
| {
|
|
29
|
+
readonly kind: 'binary';
|
|
30
|
+
readonly op: string;
|
|
31
|
+
readonly left: Expr;
|
|
32
|
+
readonly right: Expr;
|
|
33
|
+
readonly at: Place;
|
|
34
|
+
readonly opAt: Place;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
readonly kind: 'ternary';
|
|
38
|
+
readonly condition: Expr;
|
|
39
|
+
readonly then: Expr;
|
|
40
|
+
readonly otherwise: Expr;
|
|
41
|
+
readonly at: Place;
|
|
42
|
+
}
|
|
43
|
+
| { readonly kind: 'group'; readonly inner: Expr; readonly at: Place }
|
|
44
|
+
| { readonly kind: 'list'; readonly items: readonly Expr[]; readonly at: Place };
|
|
45
|
+
|
|
46
|
+
export type CallExpr = Extract<Expr, { kind: 'call' }>;
|
|
47
|
+
|
|
48
|
+
export interface Arg {
|
|
49
|
+
/** The label of a named argument, or undefined for a positional one. */
|
|
50
|
+
readonly label: string | undefined;
|
|
51
|
+
readonly value: Expr;
|
|
52
|
+
readonly at: Place;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The physical lines of the input a statement covers, its block included. */
|
|
56
|
+
export interface Lines {
|
|
57
|
+
readonly first: number;
|
|
58
|
+
readonly last: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface Common {
|
|
62
|
+
/** The statement's head, where a finding about the whole statement points. */
|
|
63
|
+
readonly at: Place;
|
|
64
|
+
readonly lines: Lines;
|
|
65
|
+
readonly remarks: readonly Remark[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Named {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly at: Place;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Param {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly at: Place;
|
|
76
|
+
readonly fallback: Expr | undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface Branch {
|
|
80
|
+
readonly condition: Expr;
|
|
81
|
+
readonly body: Body;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface Arm {
|
|
85
|
+
/** The value or condition the arm matches, undefined for the default arm. */
|
|
86
|
+
readonly match: Expr | undefined;
|
|
87
|
+
readonly body: Body;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type Stmt =
|
|
91
|
+
| (Common & {
|
|
92
|
+
readonly kind: 'declare';
|
|
93
|
+
readonly mode: 'plain' | 'var' | 'varip';
|
|
94
|
+
readonly name: Named;
|
|
95
|
+
readonly value: Expr;
|
|
96
|
+
})
|
|
97
|
+
| (Common & { readonly kind: 'tuple'; readonly names: readonly Named[]; readonly value: Expr })
|
|
98
|
+
| (Common & { readonly kind: 'assign'; readonly op: string; readonly name: Named; readonly value: Expr })
|
|
99
|
+
| (Common & { readonly kind: 'evaluate'; readonly expr: Expr })
|
|
100
|
+
| (Common & { readonly kind: 'if'; readonly branches: readonly Branch[]; readonly otherwise: Body | undefined })
|
|
101
|
+
| (Common & {
|
|
102
|
+
readonly kind: 'for';
|
|
103
|
+
readonly variable: Named;
|
|
104
|
+
readonly from: Expr;
|
|
105
|
+
readonly to: Expr;
|
|
106
|
+
readonly by: Expr | undefined;
|
|
107
|
+
readonly body: Body;
|
|
108
|
+
})
|
|
109
|
+
| (Common & { readonly kind: 'while'; readonly condition: Expr; readonly body: Body })
|
|
110
|
+
| (Common & { readonly kind: 'switch'; readonly subject: Expr | undefined; readonly arms: readonly Arm[] })
|
|
111
|
+
| (Common & { readonly kind: 'break' | 'continue' })
|
|
112
|
+
| (Common & {
|
|
113
|
+
readonly kind: 'function';
|
|
114
|
+
readonly name: Named;
|
|
115
|
+
readonly params: readonly Param[];
|
|
116
|
+
readonly body: Expr | Body;
|
|
117
|
+
})
|
|
118
|
+
| (Common & {
|
|
119
|
+
readonly kind: 'refused';
|
|
120
|
+
/** A few words naming the form, which fill the finding's placeholder. */
|
|
121
|
+
readonly construct: string;
|
|
122
|
+
/** Names the statement would have declared, so what reads them follows it. */
|
|
123
|
+
readonly declares: readonly Named[];
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export type Entry =
|
|
127
|
+
| { readonly kind: 'stmt'; readonly stmt: Stmt }
|
|
128
|
+
| { readonly kind: 'remark'; readonly remark: Remark }
|
|
129
|
+
| { readonly kind: 'blank'; readonly line: number };
|
|
130
|
+
|
|
131
|
+
export type Body = readonly Entry[];
|
|
132
|
+
|
|
133
|
+
/** `ta.sma` for a member chain of plain names, and undefined for anything else. */
|
|
134
|
+
export function pathOf(expr: Expr): string | undefined {
|
|
135
|
+
if (expr.kind === 'name') return expr.name;
|
|
136
|
+
if (expr.kind !== 'member') return undefined;
|
|
137
|
+
const base = pathOf(expr.object);
|
|
138
|
+
return base === undefined ? undefined : `${base}.${expr.property}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The place covering two places and everything between them. */
|
|
142
|
+
export function joined(first: Place, last: Place): Place {
|
|
143
|
+
return { offset: first.offset, length: Math.max(last.offset + last.length - first.offset, 0) };
|
|
144
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every built-in the importer translates, and what it becomes.
|
|
3
|
+
*
|
|
4
|
+
* This table is the one statement of that mapping. The importer reads it, and
|
|
5
|
+
* the documentation page's table is compared with it row for row by
|
|
6
|
+
* `tests/importer/table.test.ts`, so the page cannot teach a mapping the
|
|
7
|
+
* importer does not make. A built-in with no row here is refused with OS9003
|
|
8
|
+
* rather than guessed at.
|
|
9
|
+
*
|
|
10
|
+
* `difference` says how far a translation can be trusted, and it is the reason
|
|
11
|
+
* a row exists at all rather than a remark about it:
|
|
12
|
+
*
|
|
13
|
+
* - `exact`: the OpenScript call computes the same value on every bar where
|
|
14
|
+
* the source's value is finite, absent bars included, or the importer
|
|
15
|
+
* writes the expression that does. An OpenScript number is never infinite.
|
|
16
|
+
* - `warmup`: the same quantity, whose first bars, seed, arithmetic order and
|
|
17
|
+
* handling of an absent value inside its window are OpenScript's own
|
|
18
|
+
* (`stdlib.md` 20). The first call of each raises OS9007.
|
|
19
|
+
* - `orders`: an order call, placed under OpenScript's order model. The first
|
|
20
|
+
* call of each raises OS9010.
|
|
21
|
+
*
|
|
22
|
+
* `stateful` marks a call that keeps state at its call site in OpenScript, so
|
|
23
|
+
* that skipping it on a bar changes what it returns later (`language.md`
|
|
24
|
+
* 11.4). It decides where the source dialect's evaluation order and
|
|
25
|
+
* OpenScript's differ, which is what the logic operators' translation is about.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type Difference = 'exact' | 'warmup' | 'orders';
|
|
29
|
+
|
|
30
|
+
export interface Mapping {
|
|
31
|
+
/** The built-in as the source writes it, namespace included. */
|
|
32
|
+
readonly source: string;
|
|
33
|
+
/** What it becomes, as the documentation page prints it. */
|
|
34
|
+
readonly target: string;
|
|
35
|
+
/** The source's parameter names, in positional order. Empty for a value. */
|
|
36
|
+
readonly params: readonly string[];
|
|
37
|
+
/** The OpenScript parameter names the source's line up with, where they line up one to one. */
|
|
38
|
+
readonly targets: readonly string[];
|
|
39
|
+
readonly difference: Difference;
|
|
40
|
+
readonly stateful: boolean;
|
|
41
|
+
/** Set where the arguments do not line up one to one, naming the translator that handles it. */
|
|
42
|
+
readonly special: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type Row = readonly [string, string, string, string, Difference, boolean, string?];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The rows, as source, target, source parameters, target parameters,
|
|
49
|
+
* difference, stateful and the special translator. Parameters are written as
|
|
50
|
+
* one space separated string so that a row stays one line.
|
|
51
|
+
*/
|
|
52
|
+
const ROWS: readonly Row[] = [
|
|
53
|
+
// Bar data and bar facts.
|
|
54
|
+
['open', 'open', '', '', 'exact', false],
|
|
55
|
+
['high', 'high', '', '', 'exact', false],
|
|
56
|
+
['low', 'low', '', '', 'exact', false],
|
|
57
|
+
['close', 'close', '', '', 'exact', false],
|
|
58
|
+
['volume', 'volume', '', '', 'exact', false],
|
|
59
|
+
['hl2', 'hl2', '', '', 'exact', false],
|
|
60
|
+
['hlc3', 'hlc3', '', '', 'exact', false],
|
|
61
|
+
['ohlc4', 'ohlc4', '', '', 'exact', false],
|
|
62
|
+
['hlcc4', 'hlcc4', '', '', 'exact', false],
|
|
63
|
+
['time', 'time', '', '', 'exact', false],
|
|
64
|
+
['bar_index', 'bar.index', '', '', 'exact', false],
|
|
65
|
+
['barstate.isfirst', 'bar.isFirst', '', '', 'exact', false],
|
|
66
|
+
['barstate.islast', 'bar.isLast', '', '', 'exact', false],
|
|
67
|
+
['barstate.isconfirmed', 'bar.isConfirmed', '', '', 'exact', false],
|
|
68
|
+
['barstate.isrealtime', 'bar.isRealtime', '', '', 'exact', false],
|
|
69
|
+
['barstate.isnew', 'bar.isNew', '', '', 'exact', false],
|
|
70
|
+
['na', 'none', '', '', 'exact', false],
|
|
71
|
+
['math.pi', 'math.pi', '', '', 'exact', false],
|
|
72
|
+
['math.e', 'math.e', '', '', 'exact', false],
|
|
73
|
+
['strategy.position_size', 'pos.size', '', '', 'exact', false],
|
|
74
|
+
['strategy.position_avg_price', 'pos.avgPrice', '', '', 'exact', false],
|
|
75
|
+
|
|
76
|
+
// Moving averages and trend.
|
|
77
|
+
['ta.sma', 'sma', 'source length', 'src len', 'warmup', true],
|
|
78
|
+
['ta.ema', 'ema', 'source length', 'src len', 'warmup', true],
|
|
79
|
+
['ta.wma', 'wma', 'source length', 'src len', 'warmup', true],
|
|
80
|
+
['ta.rma', 'rma', 'source length', 'src len', 'warmup', true],
|
|
81
|
+
['ta.hma', 'hma', 'source length', 'src len', 'warmup', true],
|
|
82
|
+
['ta.vwma', 'vwma', 'source length', 'src len', 'warmup', true],
|
|
83
|
+
['ta.swma', 'swma', 'source', 'src', 'warmup', true],
|
|
84
|
+
['ta.linreg', 'linreg', 'source length offset', 'src len offset', 'warmup', true],
|
|
85
|
+
['ta.macd', 'macd', 'source fastlen slowlen siglen', 'src fast slow signal', 'warmup', true, 'tuple'],
|
|
86
|
+
['ta.supertrend', 'supertrend', 'factor atrPeriod', 'factor atrLen', 'warmup', true, 'tuple'],
|
|
87
|
+
['ta.dmi', 'adx', 'diLength adxSmoothing', 'diLen adxLen', 'warmup', true, 'tuple'],
|
|
88
|
+
|
|
89
|
+
// Momentum, volatility and the series helpers.
|
|
90
|
+
['ta.rsi', 'rsi', 'source length', 'src len', 'warmup', true],
|
|
91
|
+
['ta.mom', 'mom', 'source length', 'src len', 'exact', true],
|
|
92
|
+
['ta.roc', 'roc', 'source length', 'src len', 'warmup', true],
|
|
93
|
+
['ta.change', 'change', 'source length', 'src len', 'exact', true],
|
|
94
|
+
['ta.atr', 'atr', 'length', 'len', 'warmup', true],
|
|
95
|
+
['ta.tr', 'trueRange', 'handle_na', '', 'exact', false, 'trueRange'],
|
|
96
|
+
['ta.stdev', 'stdev', 'source length biased', 'src len sample', 'warmup', true, 'deviation'],
|
|
97
|
+
['ta.variance', 'variance', 'source length biased', 'src len sample', 'warmup', true, 'deviation'],
|
|
98
|
+
['ta.bb', 'bollinger', 'series length mult', 'src len mult', 'warmup', true, 'tuple'],
|
|
99
|
+
['ta.highest', 'highest', 'source length', 'src len', 'warmup', true, 'extreme'],
|
|
100
|
+
['ta.lowest', 'lowest', 'source length', 'src len', 'warmup', true, 'extreme'],
|
|
101
|
+
['ta.crossover', 'crossUp', 'source1 source2', 'a b', 'exact', true],
|
|
102
|
+
['ta.crossunder', 'crossDown', 'source1 source2', 'a b', 'exact', true],
|
|
103
|
+
['ta.cross', 'cross', 'source1 source2', 'a b', 'exact', true],
|
|
104
|
+
['ta.cum', 'cum', 'source', 'src', 'warmup', true],
|
|
105
|
+
['ta.barssince', 'barsSince', 'condition', 'cond', 'warmup', true, 'condition'],
|
|
106
|
+
['ta.valuewhen', 'valueWhen', 'condition source occurrence', 'cond src occurrence', 'warmup', true, 'condition'],
|
|
107
|
+
['math.sum', 'sum', 'source length', 'src len', 'warmup', true],
|
|
108
|
+
|
|
109
|
+
// Maths, absence and conversion.
|
|
110
|
+
['math.abs', 'abs', 'number', 'x', 'exact', false],
|
|
111
|
+
['math.sign', 'sign', 'number', 'x', 'exact', false],
|
|
112
|
+
['math.floor', 'floor', 'number', 'x', 'exact', false],
|
|
113
|
+
['math.ceil', 'ceil', 'number', 'x', 'exact', false],
|
|
114
|
+
['math.sqrt', 'sqrt', 'number', 'x', 'exact', false],
|
|
115
|
+
['math.pow', 'pow', 'base exponent', 'x y', 'exact', false],
|
|
116
|
+
['math.exp', 'exp', 'number', 'x', 'exact', false],
|
|
117
|
+
['math.log', 'log', 'number', 'x', 'exact', false],
|
|
118
|
+
['math.log10', 'log10', 'number', 'x', 'exact', false],
|
|
119
|
+
['math.sin', 'math.sin', 'angle', 'x', 'exact', false],
|
|
120
|
+
['math.cos', 'math.cos', 'angle', 'x', 'exact', false],
|
|
121
|
+
['math.tan', 'math.tan', 'angle', 'x', 'exact', false],
|
|
122
|
+
['math.asin', 'math.asin', 'angle', 'x', 'exact', false],
|
|
123
|
+
['math.acos', 'math.acos', 'angle', 'x', 'exact', false],
|
|
124
|
+
['math.atan', 'math.atan', 'angle', 'x', 'exact', false],
|
|
125
|
+
['math.todegrees', 'math.toDegrees', 'radians', 'x', 'exact', false],
|
|
126
|
+
['math.toradians', 'math.toRadians', 'degrees', 'x', 'exact', false],
|
|
127
|
+
['math.max', 'max', 'number0 number1', 'a b', 'exact', false, 'extremes'],
|
|
128
|
+
['math.min', 'min', 'number0 number1', 'a b', 'exact', false, 'extremes'],
|
|
129
|
+
['na', 'isNone', 'x', 'x', 'exact', false],
|
|
130
|
+
['nz', 'orElse', 'source replacement', 'x fallback', 'exact', false, 'fallback'],
|
|
131
|
+
['int', 'trunc', 'x', 'x', 'exact', false],
|
|
132
|
+
['float', 'float', 'x', 'x', 'exact', false, 'float'],
|
|
133
|
+
['color.new', 'fade', 'color transp', 'color percent', 'exact', false],
|
|
134
|
+
['color.rgb', 'rgb', 'red green blue transp', 'r g b', 'exact', false, 'rgb'],
|
|
135
|
+
|
|
136
|
+
// Declarations, inputs and what lands on the chart.
|
|
137
|
+
['indicator', 'study', 'title', '', 'exact', false, 'declaration'],
|
|
138
|
+
['strategy', 'strategy', 'title', '', 'exact', false, 'declaration'],
|
|
139
|
+
['input', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
140
|
+
['input.int', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
141
|
+
['input.float', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
142
|
+
['input.bool', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
143
|
+
['input.color', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
144
|
+
['input.string', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
145
|
+
['input.source', 'input', 'defval title', '', 'exact', false, 'input'],
|
|
146
|
+
['plot', 'plot', 'series title color', '', 'exact', false, 'plot'],
|
|
147
|
+
['hline', 'level', 'price title color', '', 'exact', false, 'hline'],
|
|
148
|
+
['fill', 'fill', 'plot1 plot2 color', '', 'exact', false, 'fill'],
|
|
149
|
+
['bgcolor', 'background', 'color', '', 'exact', false, 'paint'],
|
|
150
|
+
['barcolor', 'barColor', 'color', '', 'exact', false, 'paint'],
|
|
151
|
+
['plotshape', 'signal', 'series title style', '', 'exact', false, 'shape'],
|
|
152
|
+
|
|
153
|
+
// Orders.
|
|
154
|
+
['strategy.entry', 'buy or sell', 'id direction qty', '', 'orders', false, 'entry'],
|
|
155
|
+
['strategy.close', 'close', 'id', '', 'orders', false, 'close'],
|
|
156
|
+
['strategy.close_all', 'close', '', '', 'orders', false, 'closeAll'],
|
|
157
|
+
['strategy.exit', 'exit', 'id from_entry', '', 'orders', false, 'exit'],
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const words = (text: string): readonly string[] => (text === '' ? [] : text.split(' '));
|
|
161
|
+
|
|
162
|
+
export const MAPPINGS: readonly Mapping[] = ROWS.map(
|
|
163
|
+
([source, target, params, targets, difference, stateful, special]) => ({
|
|
164
|
+
source,
|
|
165
|
+
target,
|
|
166
|
+
params: words(params),
|
|
167
|
+
targets: words(targets),
|
|
168
|
+
difference,
|
|
169
|
+
stateful,
|
|
170
|
+
special,
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
/** The rows that are read as a value, keyed by the source spelling. */
|
|
175
|
+
export const VALUES: ReadonlyMap<string, Mapping> = new Map(
|
|
176
|
+
MAPPINGS.filter((row) => row.params.length === 0 && row.special === undefined).map((row) => [row.source, row]),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
/** The rows that are called, keyed by the source spelling. */
|
|
180
|
+
export const CALLS: ReadonlyMap<string, Mapping> = new Map(
|
|
181
|
+
MAPPINGS.filter((row) => row.params.length > 0 || row.special !== undefined).map((row) => [row.source, row]),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The colour names both languages have, which translate to OpenScript's colour
|
|
186
|
+
* of the same name. The channel values are OpenScript's (`spec/colours.json`).
|
|
187
|
+
*/
|
|
188
|
+
export const COLOURS: ReadonlySet<string> = new Set([
|
|
189
|
+
'aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy',
|
|
190
|
+
'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow',
|
|
191
|
+
]);
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expressions, written out as OpenScript text.
|
|
3
|
+
*
|
|
4
|
+
* Each translation carries the precedence of its outermost operator, and a
|
|
5
|
+
* parent wraps a child in brackets only where OpenScript's grammar would
|
|
6
|
+
* otherwise group it differently, so the output groups exactly as the source
|
|
7
|
+
* did (`language.md` 9.1). Brackets the source wrote are kept as written.
|
|
8
|
+
*
|
|
9
|
+
* Three operators are where the two languages part, and each is translated to
|
|
10
|
+
* the source dialect's meaning rather than to the nearest spelling:
|
|
11
|
+
*
|
|
12
|
+
* - `not`, which the source dialect applies to a comparison it has already
|
|
13
|
+
* answered `false` on an absent side, is written over `orElse(x, false)`.
|
|
14
|
+
* - `and` and `or`, whose right operand version 5 always evaluates and
|
|
15
|
+
* version 6 evaluates only when its left operand did not decide, where
|
|
16
|
+
* OpenScript also evaluates it when the left is absent (`language.md` 9.4).
|
|
17
|
+
* That matters only where the right operand holds state, and there version
|
|
18
|
+
* 5's operand is moved to a line of its own and version 6's left operand is
|
|
19
|
+
* written over `orElse`.
|
|
20
|
+
* - `==` against `na`, which the source dialect answers `false` on every bar,
|
|
21
|
+
* is refused rather than translated to a test for absence.
|
|
22
|
+
*/
|
|
23
|
+
import { diagnosticFor } from '../diagnostics/index.js';
|
|
24
|
+
import { translateCall, trueRangeValue } from './calls.js';
|
|
25
|
+
import type { Context } from './context.js';
|
|
26
|
+
import { quoted } from './context.js';
|
|
27
|
+
import { holdsState, neverAbsent, wholeConstant } from './presence.js';
|
|
28
|
+
import { pathOf } from './syntax.js';
|
|
29
|
+
import type { Expr } from './syntax.js';
|
|
30
|
+
import { COLOURS, VALUES } from './table.js';
|
|
31
|
+
|
|
32
|
+
export const PREC = {
|
|
33
|
+
ternary: 1,
|
|
34
|
+
or: 2,
|
|
35
|
+
and: 3,
|
|
36
|
+
equality: 4,
|
|
37
|
+
comparison: 5,
|
|
38
|
+
additive: 6,
|
|
39
|
+
multiplicative: 7,
|
|
40
|
+
unary: 8,
|
|
41
|
+
atom: 9,
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export interface Out {
|
|
45
|
+
readonly text: string;
|
|
46
|
+
readonly prec: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const atom = (text: string): Out => ({ text, prec: PREC.atom });
|
|
50
|
+
|
|
51
|
+
/** Stands in for a translation that was refused; the statement is not written. */
|
|
52
|
+
export const NOTHING = atom('none');
|
|
53
|
+
|
|
54
|
+
export function wrap(out: Out, min: number): string {
|
|
55
|
+
return out.prec >= min ? out.text : `(${out.text})`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The words OS9002 names a strict operand the importer cannot move with. */
|
|
59
|
+
export const STRICT = 'a version 5 and or or whose second operand holds state, in a condition that runs only on some passes';
|
|
60
|
+
|
|
61
|
+
/** A number as OpenScript writes one: no trailing point, a lower case exponent. */
|
|
62
|
+
export function numberText(text: string): string {
|
|
63
|
+
const lower = text.toLowerCase();
|
|
64
|
+
const [mantissa = '', exponent] = lower.split('e');
|
|
65
|
+
const tidy = mantissa.endsWith('.') ? `${mantissa}0` : mantissa;
|
|
66
|
+
return exponent === undefined ? tidy : `${tidy}e${exponent}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function translate(ctx: Context, expr: Expr): Out {
|
|
70
|
+
switch (expr.kind) {
|
|
71
|
+
case 'number':
|
|
72
|
+
return atom(numberText(expr.text));
|
|
73
|
+
case 'string':
|
|
74
|
+
return atom(quoted(expr.value));
|
|
75
|
+
case 'bool':
|
|
76
|
+
return atom(expr.value ? 'true' : 'false');
|
|
77
|
+
case 'color':
|
|
78
|
+
return atom(expr.text.toLowerCase());
|
|
79
|
+
case 'name':
|
|
80
|
+
return nameOf(ctx, expr);
|
|
81
|
+
case 'member':
|
|
82
|
+
return memberOf(ctx, expr);
|
|
83
|
+
case 'call':
|
|
84
|
+
return translateCall(ctx, expr);
|
|
85
|
+
case 'index': {
|
|
86
|
+
const target = translate(ctx, expr.target);
|
|
87
|
+
const index = translate(ctx, expr.index);
|
|
88
|
+
return atom(`${wrap(target, PREC.atom)}[${index.text}]`);
|
|
89
|
+
}
|
|
90
|
+
case 'unary':
|
|
91
|
+
return unaryOf(ctx, expr);
|
|
92
|
+
case 'binary':
|
|
93
|
+
return binaryOf(ctx, expr);
|
|
94
|
+
case 'ternary':
|
|
95
|
+
return ternaryOf(ctx, expr);
|
|
96
|
+
case 'group':
|
|
97
|
+
return atom(`(${translate(ctx, expr.inner).text})`);
|
|
98
|
+
case 'list':
|
|
99
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(expr.at), { construct: 'an array or tuple literal' }));
|
|
100
|
+
return NOTHING;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function nameOf(ctx: Context, expr: Extract<Expr, { kind: 'name' }>): Out {
|
|
105
|
+
const binding = ctx.lookup(expr.name, expr.at);
|
|
106
|
+
if (binding !== undefined) {
|
|
107
|
+
if (binding.kind === 'function') {
|
|
108
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(expr.at), { construct: 'a function used as a value' }));
|
|
109
|
+
return NOTHING;
|
|
110
|
+
}
|
|
111
|
+
return atom(binding.output);
|
|
112
|
+
}
|
|
113
|
+
const row = VALUES.get(expr.name);
|
|
114
|
+
if (row !== undefined) return atom(row.target);
|
|
115
|
+
ctx.refuse(diagnosticFor('OS9003', ctx.span(expr.at), { name: expr.name }));
|
|
116
|
+
return NOTHING;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function memberOf(ctx: Context, expr: Extract<Expr, { kind: 'member' }>): Out {
|
|
120
|
+
const path = pathOf(expr);
|
|
121
|
+
const root = path?.split('.')[0];
|
|
122
|
+
if (path === undefined || root === undefined || ctx.lookup(root, expr.at) !== undefined) {
|
|
123
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(expr.at), { construct: 'a field read from a value' }));
|
|
124
|
+
return NOTHING;
|
|
125
|
+
}
|
|
126
|
+
if (path === 'ta.tr') return trueRangeValue();
|
|
127
|
+
const row = VALUES.get(path);
|
|
128
|
+
if (row !== undefined) return atom(row.target);
|
|
129
|
+
if (root === 'color' && COLOURS.has(expr.property)) return atom(expr.property);
|
|
130
|
+
ctx.refuse(diagnosticFor('OS9003', ctx.span(expr.at), { name: path }));
|
|
131
|
+
return NOTHING;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The expression inside any brackets the source wrapped it in. */
|
|
135
|
+
function unwrapped(expr: Expr): Expr {
|
|
136
|
+
return expr.kind === 'group' ? unwrapped(expr.inner) : expr;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function unaryOf(ctx: Context, expr: Extract<Expr, { kind: 'unary' }>): Out {
|
|
140
|
+
if (expr.op === 'not' && !neverAbsent(ctx, expr.operand)) {
|
|
141
|
+
return { text: `not orElse(${translate(ctx, unwrapped(expr.operand)).text}, false)`, prec: PREC.unary };
|
|
142
|
+
}
|
|
143
|
+
const operand = translate(ctx, expr.operand);
|
|
144
|
+
if (expr.op === 'not') return { text: `not ${wrap(operand, PREC.unary)}`, prec: PREC.unary };
|
|
145
|
+
const inner = wrap(operand, PREC.unary);
|
|
146
|
+
const text = inner.startsWith('-') || inner.startsWith('+') ? `${expr.op}(${inner})` : `${expr.op}${inner}`;
|
|
147
|
+
return { text, prec: PREC.unary };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A translation written as an argument, without the one pair of brackets the source put round it. */
|
|
151
|
+
function bare(out: Out): string {
|
|
152
|
+
const text = out.text;
|
|
153
|
+
if (!text.startsWith('(') || !text.endsWith(')')) return text;
|
|
154
|
+
let depth = 0;
|
|
155
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
156
|
+
if (text[i] === '(') depth += 1;
|
|
157
|
+
else if (text[i] === ')') depth -= 1;
|
|
158
|
+
if (depth === 0 && i < text.length - 1) return text;
|
|
159
|
+
}
|
|
160
|
+
return text.slice(1, -1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isNa(ctx: Context, expr: Expr): boolean {
|
|
164
|
+
return expr.kind === 'name' && expr.name === 'na' && ctx.scope.find('na') === undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function equalityOf(ctx: Context, expr: Extract<Expr, { kind: 'binary' }>): Out {
|
|
168
|
+
if (isNa(ctx, expr.left) || isNa(ctx, expr.right)) {
|
|
169
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(expr.at), { construct: 'a comparison with na' }));
|
|
170
|
+
return NOTHING;
|
|
171
|
+
}
|
|
172
|
+
const left = translate(ctx, expr.left);
|
|
173
|
+
const right = translate(ctx, expr.right);
|
|
174
|
+
let leftText = wrap(left, PREC.equality + 1);
|
|
175
|
+
let rightText = wrap(right, PREC.equality + 1);
|
|
176
|
+
const leftPresent = neverAbsent(ctx, expr.left);
|
|
177
|
+
const rightPresent = neverAbsent(ctx, expr.right);
|
|
178
|
+
if (expr.right.kind === 'bool' && !leftPresent) leftText = `orElse(${bare(left)}, false)`;
|
|
179
|
+
else if (expr.left.kind === 'bool' && !rightPresent) rightText = `orElse(${bare(right)}, false)`;
|
|
180
|
+
else if (!leftPresent && !rightPresent) {
|
|
181
|
+
ctx.note(diagnosticFor('OS9011', ctx.span(expr.opAt), { operator: expr.op }));
|
|
182
|
+
}
|
|
183
|
+
return { text: `${leftText} ${expr.op} ${rightText}`, prec: PREC.equality };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function logicOf(ctx: Context, expr: Extract<Expr, { kind: 'binary' }>): Out {
|
|
187
|
+
const prec = expr.op === 'and' ? PREC.and : PREC.or;
|
|
188
|
+
const left = translate(ctx, expr.left);
|
|
189
|
+
let leftText = wrap(left, prec);
|
|
190
|
+
let right: Out;
|
|
191
|
+
if (!holdsState(ctx, expr.right)) {
|
|
192
|
+
right = translate(ctx, expr.right);
|
|
193
|
+
} else if (ctx.facts.version === 5) {
|
|
194
|
+
const operand = translate(ctx, expr.right);
|
|
195
|
+
if (ctx.refused) return NOTHING;
|
|
196
|
+
const name = ctx.hoist(operand.text);
|
|
197
|
+
if (name === undefined) {
|
|
198
|
+
ctx.refuse(diagnosticFor('OS9002', ctx.span(expr.opAt), { construct: STRICT }));
|
|
199
|
+
return NOTHING;
|
|
200
|
+
}
|
|
201
|
+
right = atom(name);
|
|
202
|
+
} else {
|
|
203
|
+
right = translate(ctx, expr.right);
|
|
204
|
+
if (expr.op === 'and' && !neverAbsent(ctx, expr.left)) leftText = `orElse(${left.text}, false)`;
|
|
205
|
+
}
|
|
206
|
+
return { text: `${leftText} ${expr.op} ${wrap(right, prec + 1)}`, prec };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function binaryOf(ctx: Context, expr: Extract<Expr, { kind: 'binary' }>): Out {
|
|
210
|
+
switch (expr.op) {
|
|
211
|
+
case 'and':
|
|
212
|
+
case 'or':
|
|
213
|
+
return logicOf(ctx, expr);
|
|
214
|
+
case '==':
|
|
215
|
+
case '!=':
|
|
216
|
+
return equalityOf(ctx, expr);
|
|
217
|
+
}
|
|
218
|
+
const left = translate(ctx, expr.left);
|
|
219
|
+
const right = translate(ctx, expr.right);
|
|
220
|
+
let prec: number = PREC.comparison;
|
|
221
|
+
if (expr.op === '+' || expr.op === '-') prec = PREC.additive;
|
|
222
|
+
else if (expr.op === '*' || expr.op === '/' || expr.op === '%') prec = PREC.multiplicative;
|
|
223
|
+
if (expr.op === '/' && ctx.facts.version === 5 && wholeConstant(ctx, expr.left) && wholeConstant(ctx, expr.right)) {
|
|
224
|
+
ctx.note(diagnosticFor('OS9008', ctx.span(expr.opAt), {}));
|
|
225
|
+
}
|
|
226
|
+
// Comparisons do not chain in OpenScript, so both sides are wrapped past them.
|
|
227
|
+
const leftMin = prec === PREC.comparison ? prec + 1 : prec;
|
|
228
|
+
return { text: `${wrap(left, leftMin)} ${expr.op} ${wrap(right, prec + 1)}`, prec };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function ternaryOf(ctx: Context, expr: Extract<Expr, { kind: 'ternary' }>): Out {
|
|
232
|
+
const condition = translate(ctx, expr.condition);
|
|
233
|
+
ctx.lazy += 1;
|
|
234
|
+
try {
|
|
235
|
+
const then = translate(ctx, expr.then);
|
|
236
|
+
const otherwise = translate(ctx, expr.otherwise);
|
|
237
|
+
return { text: `${wrap(condition, PREC.or)} ? ${then.text} : ${wrap(otherwise, PREC.ternary)}`, prec: PREC.ternary };
|
|
238
|
+
} finally {
|
|
239
|
+
ctx.lazy -= 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -288,6 +288,19 @@ export type { Value } from './engine/index.js';
|
|
|
288
288
|
export { LANGUAGE_VERSIONS, capabilitiesFor, verify } from './engine/index.js';
|
|
289
289
|
export type { VerifyOptions, VerifyResult } from './engine/index.js';
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* The importer: a script written in another chart language in, OpenScript text
|
|
293
|
+
* and findings out (`docs/writing/importing-a-script.md`).
|
|
294
|
+
*
|
|
295
|
+
* It sits beside the compiler because it is the same kind of thing, a pure
|
|
296
|
+
* function from text to text with diagnostics, and because it compiles its own
|
|
297
|
+
* output before returning it so that what it hands back compiles. Every finding
|
|
298
|
+
* carries a catalogue code in the OS9xxx range and a position in the text that
|
|
299
|
+
* was imported, not in the text that came out.
|
|
300
|
+
*/
|
|
301
|
+
export { importScript } from './importer/index.js';
|
|
302
|
+
export type { ImportOptions, ImportResult } from './importer/index.js';
|
|
303
|
+
|
|
291
304
|
/**
|
|
292
305
|
* The money, and the run it is folded from (`stdlib.md` 17.1, 17.7).
|
|
293
306
|
*
|
|
@@ -29,7 +29,10 @@ export interface Civil {
|
|
|
29
29
|
/** Days from 1970-01-01 to this civil date. */
|
|
30
30
|
export function dayNumber(year: number, month: number, day: number): number {
|
|
31
31
|
const shifted = month <= 2 ? year - 1 : year;
|
|
32
|
-
|
|
32
|
+
// A floor division, and nothing added to it: the published form subtracts 399
|
|
33
|
+
// from a negative year so that a truncating division floors, and this one
|
|
34
|
+
// already does. Doing both put every date before about 1 BCE one day early.
|
|
35
|
+
const era = Math.floor(shifted / 400);
|
|
33
36
|
const yearOfEra = shifted - era * 400;
|
|
34
37
|
const dayOfYear = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
|
|
35
38
|
const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
|
|
@@ -39,7 +42,7 @@ export function dayNumber(year: number, month: number, day: number): number {
|
|
|
39
42
|
/** The civil date a day number names, the exact inverse of `dayNumber`. */
|
|
40
43
|
export function dateOfDay(days: number): { year: number; month: number; day: number } {
|
|
41
44
|
const shifted = days + 719468;
|
|
42
|
-
const era = Math.floor(
|
|
45
|
+
const era = Math.floor(shifted / 146097);
|
|
43
46
|
const dayOfEra = shifted - era * 146097;
|
|
44
47
|
const yearOfEra = Math.floor(
|
|
45
48
|
(dayOfEra - Math.floor(dayOfEra / 1460) + Math.floor(dayOfEra / 36524) - Math.floor(dayOfEra / 146096)) / 365,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// spec/compiled-program.md, so neither can drift from the thing it names.
|
|
5
5
|
|
|
6
6
|
/** This build's package version. The release refuses to publish unless the tag, the manifest and this agree. */
|
|
7
|
-
export const VERSION = "0.
|
|
7
|
+
export const VERSION = "0.6.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The compiled program format this compiler emits.
|