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.
Files changed (242) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +53 -22
  3. package/dist/adapters/charts/run.d.ts.map +1 -1
  4. package/dist/adapters/charts/run.js +6 -0
  5. package/dist/adapters/charts/run.js.map +1 -1
  6. package/dist/adapters/charts/tables.d.ts +6 -19
  7. package/dist/adapters/charts/tables.d.ts.map +1 -1
  8. package/dist/adapters/charts/tables.js.map +1 -1
  9. package/dist/adapters/charts/undrawable.d.ts +4 -0
  10. package/dist/adapters/charts/undrawable.d.ts.map +1 -0
  11. package/dist/adapters/charts/undrawable.js +47 -0
  12. package/dist/adapters/charts/undrawable.js.map +1 -0
  13. package/dist/core/backtest/drive.d.ts +36 -1
  14. package/dist/core/backtest/drive.d.ts.map +1 -1
  15. package/dist/core/backtest/drive.js +13 -107
  16. package/dist/core/backtest/drive.js.map +1 -1
  17. package/dist/core/backtest/index.d.ts +2 -0
  18. package/dist/core/backtest/index.d.ts.map +1 -1
  19. package/dist/core/backtest/index.js.map +1 -1
  20. package/dist/core/backtest/walk.d.ts +39 -0
  21. package/dist/core/backtest/walk.d.ts.map +1 -0
  22. package/dist/core/backtest/walk.js +125 -0
  23. package/dist/core/backtest/walk.js.map +1 -0
  24. package/dist/core/catalogue/catalogue.generated.d.ts +178 -1
  25. package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
  26. package/dist/core/catalogue/catalogue.generated.js +18 -1
  27. package/dist/core/catalogue/catalogue.generated.js.map +1 -1
  28. package/dist/core/catalogue/types.d.ts +8 -2
  29. package/dist/core/catalogue/types.d.ts.map +1 -1
  30. package/dist/core/catalogue/values.generated.d.ts +51 -0
  31. package/dist/core/catalogue/values.generated.d.ts.map +1 -1
  32. package/dist/core/check/assigned.d.ts +19 -0
  33. package/dist/core/check/assigned.d.ts.map +1 -0
  34. package/dist/core/check/assigned.js +14 -0
  35. package/dist/core/check/assigned.js.map +1 -0
  36. package/dist/core/check/call-sites.d.ts.map +1 -1
  37. package/dist/core/check/call-sites.js +9 -4
  38. package/dist/core/check/call-sites.js.map +1 -1
  39. package/dist/core/check/calls.d.ts.map +1 -1
  40. package/dist/core/check/calls.js +12 -4
  41. package/dist/core/check/calls.js.map +1 -1
  42. package/dist/core/check/check.d.ts.map +1 -1
  43. package/dist/core/check/check.js +15 -2
  44. package/dist/core/check/check.js.map +1 -1
  45. package/dist/core/check/checker.d.ts +26 -0
  46. package/dist/core/check/checker.d.ts.map +1 -1
  47. package/dist/core/check/checker.js +38 -2
  48. package/dist/core/check/checker.js.map +1 -1
  49. package/dist/core/check/constant.d.ts +16 -0
  50. package/dist/core/check/constant.d.ts.map +1 -1
  51. package/dist/core/check/constant.js +52 -0
  52. package/dist/core/check/constant.js.map +1 -1
  53. package/dist/core/check/deleted.d.ts +3 -0
  54. package/dist/core/check/deleted.d.ts.map +1 -0
  55. package/dist/core/check/deleted.js +109 -0
  56. package/dist/core/check/deleted.js.map +1 -0
  57. package/dist/core/check/expressions.js +1 -1
  58. package/dist/core/check/expressions.js.map +1 -1
  59. package/dist/core/check/library-output.d.ts.map +1 -1
  60. package/dist/core/check/library-output.js +1 -0
  61. package/dist/core/check/library-output.js.map +1 -1
  62. package/dist/core/check/library-prose.generated.js +10 -10
  63. package/dist/core/check/library-prose.generated.js.map +1 -1
  64. package/dist/core/check/library.d.ts +7 -0
  65. package/dist/core/check/library.d.ts.map +1 -1
  66. package/dist/core/check/library.js +1 -0
  67. package/dist/core/check/library.js.map +1 -1
  68. package/dist/core/check/surface.d.ts +9 -0
  69. package/dist/core/check/surface.d.ts.map +1 -1
  70. package/dist/core/check/surface.js +13 -0
  71. package/dist/core/check/surface.js.map +1 -1
  72. package/dist/core/engine/bar-source.d.ts +65 -0
  73. package/dist/core/engine/bar-source.d.ts.map +1 -0
  74. package/dist/core/engine/bar-source.js +58 -0
  75. package/dist/core/engine/bar-source.js.map +1 -0
  76. package/dist/core/engine/engine.d.ts +9 -11
  77. package/dist/core/engine/engine.d.ts.map +1 -1
  78. package/dist/core/engine/engine.js +24 -24
  79. package/dist/core/engine/engine.js.map +1 -1
  80. package/dist/core/engine/guard.d.ts.map +1 -1
  81. package/dist/core/engine/guard.js +10 -0
  82. package/dist/core/engine/guard.js.map +1 -1
  83. package/dist/core/engine/host.d.ts +4 -2
  84. package/dist/core/engine/host.d.ts.map +1 -1
  85. package/dist/core/engine/host.js.map +1 -1
  86. package/dist/core/engine/index.d.ts +1 -0
  87. package/dist/core/engine/index.d.ts.map +1 -1
  88. package/dist/core/engine/index.js.map +1 -1
  89. package/dist/core/engine/library/binding.d.ts +2 -0
  90. package/dist/core/engine/library/binding.d.ts.map +1 -1
  91. package/dist/core/engine/library/binding.js.map +1 -1
  92. package/dist/core/engine/library/index.d.ts +8 -0
  93. package/dist/core/engine/library/index.d.ts.map +1 -1
  94. package/dist/core/engine/library/index.js +10 -0
  95. package/dist/core/engine/library/index.js.map +1 -1
  96. package/dist/core/engine/library/objects.d.ts.map +1 -1
  97. package/dist/core/engine/library/objects.js +1 -1
  98. package/dist/core/engine/library/objects.js.map +1 -1
  99. package/dist/core/engine/request-body.d.ts +3 -1
  100. package/dist/core/engine/request-body.d.ts.map +1 -1
  101. package/dist/core/engine/request-body.js +3 -1
  102. package/dist/core/engine/request-body.js.map +1 -1
  103. package/dist/core/engine/request-plan.d.ts +3 -3
  104. package/dist/core/engine/request-plan.js +3 -3
  105. package/dist/core/engine/requests.d.ts +2 -2
  106. package/dist/core/engine/requests.d.ts.map +1 -1
  107. package/dist/core/engine/requests.js +5 -4
  108. package/dist/core/engine/requests.js.map +1 -1
  109. package/dist/core/engine/series.d.ts +18 -6
  110. package/dist/core/engine/series.d.ts.map +1 -1
  111. package/dist/core/engine/series.js +24 -5
  112. package/dist/core/engine/series.js.map +1 -1
  113. package/dist/core/engine/session/record.d.ts +4 -4
  114. package/dist/core/engine/session/record.js +4 -4
  115. package/dist/core/importer/calls.d.ts +29 -0
  116. package/dist/core/importer/calls.d.ts.map +1 -0
  117. package/dist/core/importer/calls.js +267 -0
  118. package/dist/core/importer/calls.js.map +1 -0
  119. package/dist/core/importer/context.d.ts +118 -0
  120. package/dist/core/importer/context.d.ts.map +1 -0
  121. package/dist/core/importer/context.js +120 -0
  122. package/dist/core/importer/context.js.map +1 -0
  123. package/dist/core/importer/expressions.d.ts +44 -0
  124. package/dist/core/importer/expressions.d.ts.map +1 -0
  125. package/dist/core/importer/expressions.js +223 -0
  126. package/dist/core/importer/expressions.js.map +1 -0
  127. package/dist/core/importer/importer.d.ts +13 -0
  128. package/dist/core/importer/importer.d.ts.map +1 -0
  129. package/dist/core/importer/importer.js +220 -0
  130. package/dist/core/importer/importer.js.map +1 -0
  131. package/dist/core/importer/index.d.ts +20 -0
  132. package/dist/core/importer/index.d.ts.map +1 -0
  133. package/dist/core/importer/index.js +18 -0
  134. package/dist/core/importer/index.js.map +1 -0
  135. package/dist/core/importer/lexer.d.ts +68 -0
  136. package/dist/core/importer/lexer.d.ts.map +1 -0
  137. package/dist/core/importer/lexer.js +207 -0
  138. package/dist/core/importer/lexer.js.map +1 -0
  139. package/dist/core/importer/names.d.ts +41 -0
  140. package/dist/core/importer/names.d.ts.map +1 -0
  141. package/dist/core/importer/names.js +78 -0
  142. package/dist/core/importer/names.js.map +1 -0
  143. package/dist/core/importer/orders.d.ts +8 -0
  144. package/dist/core/importer/orders.d.ts.map +1 -0
  145. package/dist/core/importer/orders.js +332 -0
  146. package/dist/core/importer/orders.js.map +1 -0
  147. package/dist/core/importer/outputs.d.ts +19 -0
  148. package/dist/core/importer/outputs.d.ts.map +1 -0
  149. package/dist/core/importer/outputs.js +266 -0
  150. package/dist/core/importer/outputs.js.map +1 -0
  151. package/dist/core/importer/parser.d.ts +5 -0
  152. package/dist/core/importer/parser.d.ts.map +1 -0
  153. package/dist/core/importer/parser.js +439 -0
  154. package/dist/core/importer/parser.js.map +1 -0
  155. package/dist/core/importer/presence.d.ts +32 -0
  156. package/dist/core/importer/presence.d.ts.map +1 -0
  157. package/dist/core/importer/presence.js +146 -0
  158. package/dist/core/importer/presence.js.map +1 -0
  159. package/dist/core/importer/settle.d.ts +23 -0
  160. package/dist/core/importer/settle.d.ts.map +1 -0
  161. package/dist/core/importer/settle.js +135 -0
  162. package/dist/core/importer/settle.js.map +1 -0
  163. package/dist/core/importer/statements.d.ts +4 -0
  164. package/dist/core/importer/statements.d.ts.map +1 -0
  165. package/dist/core/importer/statements.js +339 -0
  166. package/dist/core/importer/statements.js.map +1 -0
  167. package/dist/core/importer/syntax.d.ts +182 -0
  168. package/dist/core/importer/syntax.d.ts.map +1 -0
  169. package/dist/core/importer/syntax.js +14 -0
  170. package/dist/core/importer/syntax.js.map +1 -0
  171. package/dist/core/importer/table.d.ts +52 -0
  172. package/dist/core/importer/table.d.ts.map +1 -0
  173. package/dist/core/importer/table.js +156 -0
  174. package/dist/core/importer/table.js.map +1 -0
  175. package/dist/core/importer/values.d.ts +27 -0
  176. package/dist/core/importer/values.d.ts.map +1 -0
  177. package/dist/core/importer/values.js +236 -0
  178. package/dist/core/importer/values.js.map +1 -0
  179. package/dist/core/index.d.ts +12 -0
  180. package/dist/core/index.d.ts.map +1 -1
  181. package/dist/core/index.js +11 -0
  182. package/dist/core/index.js.map +1 -1
  183. package/dist/core/stdlib/calendar/civil.d.ts.map +1 -1
  184. package/dist/core/stdlib/calendar/civil.js +5 -2
  185. package/dist/core/stdlib/calendar/civil.js.map +1 -1
  186. package/dist/core/version/version.generated.d.ts +1 -1
  187. package/dist/core/version/version.generated.js +1 -1
  188. package/package.json +5 -2
  189. package/spec/errors.json +380 -20
  190. package/src/adapters/charts/run.ts +6 -0
  191. package/src/adapters/charts/tables.ts +6 -19
  192. package/src/adapters/charts/undrawable.ts +51 -0
  193. package/src/core/backtest/drive.ts +66 -154
  194. package/src/core/backtest/index.ts +2 -0
  195. package/src/core/backtest/walk.ts +182 -0
  196. package/src/core/catalogue/catalogue.generated.ts +18 -1
  197. package/src/core/catalogue/types.ts +8 -2
  198. package/src/core/catalogue/values.generated.ts +16 -0
  199. package/src/core/check/assigned.ts +31 -0
  200. package/src/core/check/call-sites.ts +11 -4
  201. package/src/core/check/calls.ts +13 -4
  202. package/src/core/check/check.ts +14 -2
  203. package/src/core/check/checker.ts +39 -2
  204. package/src/core/check/constant.ts +57 -0
  205. package/src/core/check/deleted.ts +135 -0
  206. package/src/core/check/expressions.ts +1 -1
  207. package/src/core/check/library-output.ts +1 -0
  208. package/src/core/check/library-prose.generated.ts +10 -10
  209. package/src/core/check/library.ts +8 -0
  210. package/src/core/check/surface.ts +16 -0
  211. package/src/core/engine/bar-source.ts +113 -0
  212. package/src/core/engine/engine.ts +26 -25
  213. package/src/core/engine/guard.ts +12 -1
  214. package/src/core/engine/host.ts +4 -2
  215. package/src/core/engine/index.ts +1 -0
  216. package/src/core/engine/library/binding.ts +2 -0
  217. package/src/core/engine/library/index.ts +11 -0
  218. package/src/core/engine/library/objects.ts +1 -6
  219. package/src/core/engine/request-body.ts +5 -7
  220. package/src/core/engine/request-plan.ts +3 -3
  221. package/src/core/engine/requests.ts +11 -8
  222. package/src/core/engine/series.ts +30 -6
  223. package/src/core/engine/session/record.ts +4 -4
  224. package/src/core/importer/calls.ts +274 -0
  225. package/src/core/importer/context.ts +197 -0
  226. package/src/core/importer/expressions.ts +247 -0
  227. package/src/core/importer/importer.ts +226 -0
  228. package/src/core/importer/index.ts +19 -0
  229. package/src/core/importer/lexer.ts +285 -0
  230. package/src/core/importer/names.ts +104 -0
  231. package/src/core/importer/orders.ts +307 -0
  232. package/src/core/importer/outputs.ts +245 -0
  233. package/src/core/importer/parser.ts +436 -0
  234. package/src/core/importer/presence.ts +164 -0
  235. package/src/core/importer/settle.ts +141 -0
  236. package/src/core/importer/statements.ts +331 -0
  237. package/src/core/importer/syntax.ts +144 -0
  238. package/src/core/importer/table.ts +191 -0
  239. package/src/core/importer/values.ts +241 -0
  240. package/src/core/index.ts +13 -0
  241. package/src/core/stdlib/calendar/civil.ts +5 -2
  242. package/src/core/version/version.generated.ts +1 -1
@@ -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
+ }