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
@@ -49,7 +49,9 @@ import type { CompiledProgram, Position } from './types.js';
49
49
  import type { RequestPlan } from './request-plan.js';
50
50
  import { NO_SESSION, sessionReader } from './session/index.js';
51
51
  import type { SessionReader } from './session/index.js';
52
- import { noBars, outOfOrder } from './series.js';
52
+ import { handOver, noBars } from './series.js';
53
+ import { KnownBars, sourceOf } from './bar-source.js';
54
+ import type { BarColumns } from './bar-source.js';
53
55
  import { barMinutesOf } from './timeframe.js';
54
56
  import { RequestSet } from './requests.js';
55
57
  import type { Value } from './values/index.js';
@@ -96,7 +98,7 @@ export class Engine {
96
98
  * chart bar is inside, and on a live feed there is nothing there yet. That is
97
99
  * the mode repainting on history, permanently and by design.
98
100
  */
99
- private known: HostBar[] = [];
101
+ private readonly known = new KnownBars();
100
102
 
101
103
  private supplied = 0;
102
104
  private index = -1;
@@ -230,7 +232,7 @@ export class Engine {
230
232
  // that has already failed keeps the failure it has, which is the one that
231
233
  // explains what went wrong first.
232
234
  if (this.failure === undefined) {
233
- const problem = outOfOrder(bar, this.known[this.index], this.index + 1);
235
+ const problem = handOver(bar, this.known.at(this.index), this.index + 1);
234
236
  if (problem !== undefined) return this.refuse(problem);
235
237
  }
236
238
  if (this.started) this.checkpoint();
@@ -239,7 +241,7 @@ export class Engine {
239
241
  this.updates = 1;
240
242
  this.started = true;
241
243
  this.previousClose = this.lastClose;
242
- if (this.known.length <= this.index) this.known[this.index] = bar;
244
+ if (this.known.length <= this.index) this.known.set(this.index, bar);
243
245
  return this.execute(bar, state, true);
244
246
  }
245
247
 
@@ -257,14 +259,14 @@ export class Engine {
257
259
  // update whose time has moved back onto the bar before it is a series the
258
260
  // engine cannot run on, whatever it is called.
259
261
  if (this.failure === undefined) {
260
- const problem = outOfOrder(bar, this.known[this.index - 1], this.index);
262
+ const problem = handOver(bar, this.known.at(this.index - 1), this.index);
261
263
  if (problem !== undefined) return this.refuse(problem);
262
264
  }
263
265
  this.rollback();
264
266
  this.updates += 1;
265
267
  // A revision replaces the bar the fold already read, so the bucket it is
266
268
  // inside is rebuilt from this reading rather than from the one it replaced.
267
- this.known[this.index] = bar;
269
+ this.known.set(this.index, bar);
268
270
  return this.execute(bar, state, false);
269
271
  }
270
272
 
@@ -282,27 +284,34 @@ export class Engine {
282
284
  * pane with nothing drawn on it is what a study that computed nothing also
283
285
  * produces and only the engine can tell the two apart.
284
286
  */
285
- run(bars: readonly HostBar[], states: readonly BarState[] = []): RunResult {
287
+ run(bars: readonly HostBar[] | BarColumns, states: readonly BarState[] = []): RunResult {
288
+ const source = sourceOf(bars);
286
289
  const out: BarResult[] = [];
287
- // A dataset with nothing in it is the one hand-over that has no bar to
288
- // report against, so it is answered here rather than by the loop below,
289
- // which would run no iterations and return a clean, empty, wordless result.
290
- if (bars.length === 0 && !this.started) {
290
+ // No bars at all is answered here: the loop below would return an empty, wordless result.
291
+ if (source.length === 0 && !this.started) {
291
292
  const diagnostic = this.failure ?? noBars(this.options.host?.instrument);
292
293
  this.failure = diagnostic;
293
294
  return { bars: [], diagnostic };
294
295
  }
295
- this.known = [...bars];
296
- for (let i = 0; i < bars.length; i += 1) {
297
- const bar = bars[i];
296
+ this.known.reset(source);
297
+ for (let i = 0; i < source.length; i += 1) {
298
+ const bar = source.at(i);
298
299
  if (bar === undefined) continue;
299
- const result = this.append(bar, states[i] ?? {}, bars.length);
300
+ const result = this.append(bar, states[i] ?? {}, source.length);
300
301
  out.push(result);
301
302
  if (result.diagnostic !== undefined) return { bars: out, diagnostic: result.diagnostic };
302
303
  }
303
304
  return { bars: out, diagnostic: undefined };
304
305
  }
305
306
 
307
+ /**
308
+ * The dataset a driver appends one bar at a time, handed to the fold before
309
+ * bar 0 as `run` hands it, so a `"lookahead"` read reads it as history.
310
+ */
311
+ history(bars: readonly HostBar[] | BarColumns): void {
312
+ if (!this.started) this.known.reset(sourceOf(bars));
313
+ }
314
+
306
315
  /**
307
316
  * A frame from the destination, `host-interface.md` 7.2. Held until the next
308
317
  * bar begins rather than folded where it lands: nothing reaches a running
@@ -339,7 +348,7 @@ export class Engine {
339
348
  // session or is inside the one that bar was already in. Read from the bars
340
349
  // themselves rather than carried, so a re-executed bar compares against the
341
350
  // same neighbour it compared against the first time.
342
- const previous = this.known[index - 1]?.time ?? null;
351
+ const previous = this.known.at(index - 1)?.time ?? null;
343
352
  this.facts = factsFor(
344
353
  index,
345
354
  this.supplied,
@@ -418,15 +427,6 @@ export class Engine {
418
427
  };
419
428
  }
420
429
 
421
- /**
422
- * A bar that failed.
423
- *
424
- * Steps 7 to 11 do not run, the bar's output columns keep whatever the
425
- * previous execution published or stay absent, and the engine reports the
426
- * error with its code and source position. It does not continue to the next
427
- * bar with a half executed state, so the journal is rolled back and the
428
- * script is marked stopped.
429
- */
430
430
  /**
431
431
  * A hand-over the engine refuses, before any of it has been executed.
432
432
  *
@@ -449,6 +449,7 @@ export class Engine {
449
449
  };
450
450
  }
451
451
 
452
+ /** A bar that failed: steps 7 to 11 do not run, the journal rolls back, the script stops. */
452
453
  private stopped(thrown: unknown): BarResult {
453
454
  const diagnostic =
454
455
  thrown instanceof ScriptError ? thrown.diagnostic : unexpected('the bar', thrown);
@@ -10,7 +10,7 @@
10
10
  * catalogue has: an array past its element ceiling is OS5002, a string past its
11
11
  * character ceiling is OS5008, one more drawing object than the host will hold
12
12
  * is OS5010, an argument outside its contract is OS4003, an index outside an
13
- * array is OS4004, a change to an object a script already deleted is OS4005,
13
+ * array is OS4004, a cell outside a table's declared shape is OS4008, a change to an object a script already deleted is OS4005,
14
14
  * and a timezone name the host's table does not hold is OS6005.
15
15
  */
16
16
  import type { Span } from '../span/index.js';
@@ -52,6 +52,17 @@ export function guardFor(budget: Budget): Guard {
52
52
  raise('OS4004', span, { index, name, size });
53
53
  },
54
54
 
55
+ /**
56
+ * A cell outside the grid its table declared, OS4008.
57
+ *
58
+ * Not OS4004, which is about an array and would name an index and a
59
+ * flattened element count: a table's shape is two numbers the declaration
60
+ * fixed, and the fix is to declare the shape the script writes.
61
+ */
62
+ badCell(span: Span, row: number, column: number, rows: number, columns: number): never {
63
+ raise('OS4008', span, { row, column, rows, columns });
64
+ },
65
+
55
66
  deleted(span: Span, kind: string, bar: number): never {
56
67
  raise('OS4005', span, { kind, bar });
57
68
  },
@@ -25,6 +25,7 @@
25
25
  * stop rather than what a script computes.
26
26
  */
27
27
  import type { HostBar } from './bars.js';
28
+ import type { BarColumns } from './bar-source.js';
28
29
  import type { PendingEffect } from './channels.js';
29
30
  import type { OrderIntent } from './ledger/index.js';
30
31
  import type { HostFacts } from './library/index.js';
@@ -154,10 +155,11 @@ export interface RequestRefusal {
154
155
  * answer between bars and then recalculates the study over its whole history,
155
156
  * which is a fresh load rather than an answer spliced into a run already past
156
157
  * the bars it would have changed. A host that wants the engine to fold the
157
- * chart's own bars instead returns nothing at all.
158
+ * chart's own bars instead returns nothing at all. The bars may be records or
159
+ * columns, as a run's may (`bar-source.ts`).
158
160
  */
159
161
  export type RequestAnswer =
160
- | { readonly bars: readonly HostBar[] }
162
+ | { readonly bars: readonly HostBar[] | BarColumns }
161
163
  | { readonly pending: true }
162
164
  | { readonly refused: RequestRefusal };
163
165
 
@@ -32,6 +32,7 @@ export type { AlertFiring } from './alerts.js';
32
32
  export type { Grid } from './grids.js';
33
33
 
34
34
  export type { BarFacts, BarState, HostBar } from './bars.js';
35
+ export type { BarColumn, BarColumns } from './bar-source.js';
35
36
  export { BAR_FIELDS, barField, isBarField } from './bars.js';
36
37
 
37
38
  export type { Clock, EngineLimits } from './budget.js';
@@ -160,6 +160,8 @@ export interface Guard {
160
160
  badArgument(span: Span, name: string, argument: string, found: string): never;
161
161
  /** Raise a run-time diagnostic against an index outside an array. */
162
162
  badIndex(span: Span, name: string, index: string, size: number): never;
163
+ /** Raise a run-time diagnostic against a cell outside a table's declared shape. */
164
+ badCell(span: Span, row: number, column: number, rows: number, columns: number): never;
163
165
  /** Raise a run-time diagnostic against an object a script already deleted. */
164
166
  deleted(span: Span, kind: string, bar: number): never;
165
167
  /** Raise a run-time diagnostic against a zone name the host does not know. */
@@ -74,6 +74,17 @@ for (const one of ENTRIES) {
74
74
  }
75
75
 
76
76
  /** The entry for one name and argument count, or nothing when there is none. */
77
+ /**
78
+ * Every name and argument count this engine holds, as `name/arity`, sorted.
79
+ *
80
+ * `scripts/check-manifests.mjs` compares it with the second engine's, so a call
81
+ * one engine holds and the other does not is found by a build rather than by the
82
+ * first case that reaches it.
83
+ */
84
+ export function manifestKeys(): readonly string[] {
85
+ return [...INDEX.keys()].sort();
86
+ }
87
+
77
88
  export function manifestEntry(name: string, arity: number): ManifestEntry | undefined {
78
89
  return INDEX.get(keyOf(name, arity));
79
90
  }
@@ -234,12 +234,7 @@ export const OBJECT_ENTRIES: readonly ManifestEntry[] = [
234
234
  const col = wholeAt(ctx, 'cell', 'col', args, 2);
235
235
  if (row === null || col === null) return null;
236
236
  if (row >= object.rows || col >= object.cols) {
237
- ctx.guard.badIndex(
238
- ctx.span,
239
- ctx.nameOf(valueAt(args, 0)),
240
- `${row}, ${col}`,
241
- object.rows * object.cols,
242
- );
237
+ ctx.guard.badCell(ctx.span, row, col, object.rows, object.cols);
243
238
  }
244
239
  const written: GridCell = {
245
240
  row,
@@ -28,6 +28,8 @@
28
28
  import type { Span } from '../span/index.js';
29
29
  import { barField, factsFor } from './bars.js';
30
30
  import type { HostBar } from './bars.js';
31
+ import { recordSource } from './bar-source.js';
32
+ import type { BarSource } from './bar-source.js';
31
33
  import { Budget, stepBound } from './budget.js';
32
34
  import type { Clock, EngineLimits } from './budget.js';
33
35
  import { Channels } from './channels.js';
@@ -59,12 +61,7 @@ export interface BodyInput {
59
61
  */
60
62
  export interface NestedRequests {
61
63
  readonly empty: boolean;
62
- fill(
63
- registers: Registers,
64
- bars: readonly HostBar[],
65
- index: number,
66
- fresh: boolean,
67
- ): void;
64
+ fill(registers: Registers, bars: BarSource, index: number, fresh: boolean): void;
68
65
  }
69
66
 
70
67
  export interface BodyParts {
@@ -120,6 +117,7 @@ export class RequestBody {
120
117
  private lastClose: number | null = null;
121
118
  /** The requested bars themselves, kept only when a read inside folds them. */
122
119
  private readonly history: HostBar[] = [];
120
+ private readonly historySource = recordSource(this.history);
123
121
  /**
124
122
  * The value the last closed requested bar handed back.
125
123
  *
@@ -251,7 +249,7 @@ export class RequestBody {
251
249
  const nested = this.parts.nested;
252
250
  if (nested !== undefined && !nested.empty) {
253
251
  this.history[index] = bar;
254
- nested.fill(this.registers, this.history, index, this.touches === 0);
252
+ nested.fill(this.registers, this.historySource, index, this.touches === 0);
255
253
  }
256
254
 
257
255
  this.machine.run();
@@ -218,9 +218,9 @@ export function undatable(plan: RequestPlan): boolean {
218
218
  *
219
219
  * OS6012's own message, because the zone is an instrument fact the host did not
220
220
  * supply and that is exactly what this read needs and cannot default. Nothing
221
- * is raised: a fact the host did not state leaves the read absent, and this is
222
- * the sentence that explains the absence rather than a second answer to the
223
- * question `host-interface.md` 4.5 leaves open.
221
+ * is raised: a fact the host did not state leaves the read absent, as
222
+ * `host-interface.md` 4.5 settles it, and this is the sentence that explains
223
+ * the absence rather than a second answer to it.
224
224
  */
225
225
  export function undatableReason(query: RequestQuery): string {
226
226
  return diagnosticFor('OS6012', NO_POSITION, { fact: 'a timezone', symbol: named(query) }).message;
@@ -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: readonly HostBar[] | undefined;
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: readonly HostBar[], index: number, fresh: boolean): void {
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: readonly HostBar[], index: number): Value {
252
+ function valueOf(read: Read, bars: BarSource, index: number): Value {
251
253
  if (!read.ready) return ABSENT;
252
- const bar = bars[index];
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[read.cursor];
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: readonly HostBar[]): void {
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[at];
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 two things the engine checks about the bars it is handed,
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 time the host
67
- * did not state is not compared: the absent value is what `barField` gives a
68
- * script for it (3.1), and the catalogue has no code for a bar dated nothing,
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. This decides nothing about what a bare
110
- * read of an instrument fact returns, which is issue 0002 and is open: a record
111
- * the host contradicts itself in is a different question from a fact it did not
112
- * state.
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,