openalgo-script 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +1050 -0
  2. package/README.md +111 -27
  3. package/dist/adapters/charts/driving.d.ts +50 -0
  4. package/dist/adapters/charts/driving.d.ts.map +1 -0
  5. package/dist/adapters/charts/driving.js +57 -0
  6. package/dist/adapters/charts/driving.js.map +1 -0
  7. package/dist/adapters/charts/run.d.ts +20 -0
  8. package/dist/adapters/charts/run.d.ts.map +1 -1
  9. package/dist/adapters/charts/run.js +89 -16
  10. package/dist/adapters/charts/run.js.map +1 -1
  11. package/dist/adapters/charts/tables.d.ts +6 -19
  12. package/dist/adapters/charts/tables.d.ts.map +1 -1
  13. package/dist/adapters/charts/tables.js.map +1 -1
  14. package/dist/adapters/charts/undrawable.d.ts +4 -0
  15. package/dist/adapters/charts/undrawable.d.ts.map +1 -0
  16. package/dist/adapters/charts/undrawable.js +47 -0
  17. package/dist/adapters/charts/undrawable.js.map +1 -0
  18. package/dist/adapters/charts/venue.d.ts +73 -0
  19. package/dist/adapters/charts/venue.d.ts.map +1 -0
  20. package/dist/adapters/charts/venue.js +104 -0
  21. package/dist/adapters/charts/venue.js.map +1 -0
  22. package/dist/core/accounting/analysis.d.ts +111 -0
  23. package/dist/core/accounting/analysis.d.ts.map +1 -0
  24. package/dist/core/accounting/analysis.js +123 -0
  25. package/dist/core/accounting/analysis.js.map +1 -0
  26. package/dist/core/accounting/equity.d.ts +33 -0
  27. package/dist/core/accounting/equity.d.ts.map +1 -1
  28. package/dist/core/accounting/equity.js +12 -0
  29. package/dist/core/accounting/equity.js.map +1 -1
  30. package/dist/core/accounting/index.d.ts +2 -0
  31. package/dist/core/accounting/index.d.ts.map +1 -1
  32. package/dist/core/accounting/index.js +1 -0
  33. package/dist/core/accounting/index.js.map +1 -1
  34. package/dist/core/accounting/report.d.ts +3 -0
  35. package/dist/core/accounting/report.d.ts.map +1 -1
  36. package/dist/core/accounting/report.js +2 -0
  37. package/dist/core/accounting/report.js.map +1 -1
  38. package/dist/core/accounting/statistics.d.ts +12 -0
  39. package/dist/core/accounting/statistics.d.ts.map +1 -1
  40. package/dist/core/accounting/statistics.js +23 -1
  41. package/dist/core/accounting/statistics.js.map +1 -1
  42. package/dist/core/backtest/case.d.ts +60 -0
  43. package/dist/core/backtest/case.d.ts.map +1 -0
  44. package/dist/core/backtest/case.js +319 -0
  45. package/dist/core/backtest/case.js.map +1 -0
  46. package/dist/core/backtest/compare.d.ts.map +1 -1
  47. package/dist/core/backtest/compare.js +2 -0
  48. package/dist/core/backtest/compare.js.map +1 -1
  49. package/dist/core/backtest/deliver.d.ts +93 -0
  50. package/dist/core/backtest/deliver.d.ts.map +1 -0
  51. package/dist/core/backtest/deliver.js +94 -0
  52. package/dist/core/backtest/deliver.js.map +1 -0
  53. package/dist/core/backtest/drive.d.ts +101 -8
  54. package/dist/core/backtest/drive.d.ts.map +1 -1
  55. package/dist/core/backtest/drive.js +114 -158
  56. package/dist/core/backtest/drive.js.map +1 -1
  57. package/dist/core/backtest/index.d.ts +26 -12
  58. package/dist/core/backtest/index.d.ts.map +1 -1
  59. package/dist/core/backtest/index.js +21 -10
  60. package/dist/core/backtest/index.js.map +1 -1
  61. package/dist/core/backtest/record.d.ts +63 -2
  62. package/dist/core/backtest/record.d.ts.map +1 -1
  63. package/dist/core/backtest/record.js +71 -4
  64. package/dist/core/backtest/record.js.map +1 -1
  65. package/dist/core/backtest/replay.d.ts.map +1 -1
  66. package/dist/core/backtest/replay.js +11 -1
  67. package/dist/core/backtest/replay.js.map +1 -1
  68. package/dist/core/backtest/simulate.d.ts +113 -1
  69. package/dist/core/backtest/simulate.d.ts.map +1 -1
  70. package/dist/core/backtest/simulate.js +123 -5
  71. package/dist/core/backtest/simulate.js.map +1 -1
  72. package/dist/core/backtest/walk.d.ts +39 -0
  73. package/dist/core/backtest/walk.d.ts.map +1 -0
  74. package/dist/core/backtest/walk.js +125 -0
  75. package/dist/core/backtest/walk.js.map +1 -0
  76. package/dist/core/catalogue/catalogue.generated.d.ts +179 -2
  77. package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
  78. package/dist/core/catalogue/catalogue.generated.js +19 -2
  79. package/dist/core/catalogue/catalogue.generated.js.map +1 -1
  80. package/dist/core/catalogue/types.d.ts +8 -2
  81. package/dist/core/catalogue/types.d.ts.map +1 -1
  82. package/dist/core/catalogue/values.generated.d.ts +51 -0
  83. package/dist/core/catalogue/values.generated.d.ts.map +1 -1
  84. package/dist/core/check/assigned.d.ts +19 -0
  85. package/dist/core/check/assigned.d.ts.map +1 -0
  86. package/dist/core/check/assigned.js +14 -0
  87. package/dist/core/check/assigned.js.map +1 -0
  88. package/dist/core/check/call-sites.d.ts.map +1 -1
  89. package/dist/core/check/call-sites.js +9 -4
  90. package/dist/core/check/call-sites.js.map +1 -1
  91. package/dist/core/check/calls.d.ts.map +1 -1
  92. package/dist/core/check/calls.js +12 -4
  93. package/dist/core/check/calls.js.map +1 -1
  94. package/dist/core/check/check.d.ts.map +1 -1
  95. package/dist/core/check/check.js +15 -2
  96. package/dist/core/check/check.js.map +1 -1
  97. package/dist/core/check/checker.d.ts +26 -0
  98. package/dist/core/check/checker.d.ts.map +1 -1
  99. package/dist/core/check/checker.js +38 -2
  100. package/dist/core/check/checker.js.map +1 -1
  101. package/dist/core/check/constant.d.ts +16 -0
  102. package/dist/core/check/constant.d.ts.map +1 -1
  103. package/dist/core/check/constant.js +52 -0
  104. package/dist/core/check/constant.js.map +1 -1
  105. package/dist/core/check/deleted.d.ts +3 -0
  106. package/dist/core/check/deleted.d.ts.map +1 -0
  107. package/dist/core/check/deleted.js +109 -0
  108. package/dist/core/check/deleted.js.map +1 -0
  109. package/dist/core/check/expressions.js +1 -1
  110. package/dist/core/check/expressions.js.map +1 -1
  111. package/dist/core/check/library-orders.js +2 -2
  112. package/dist/core/check/library-orders.js.map +1 -1
  113. package/dist/core/check/library-output.d.ts.map +1 -1
  114. package/dist/core/check/library-output.js +1 -0
  115. package/dist/core/check/library-output.js.map +1 -1
  116. package/dist/core/check/library-prose.generated.js +12 -12
  117. package/dist/core/check/library-prose.generated.js.map +1 -1
  118. package/dist/core/check/library.d.ts +7 -0
  119. package/dist/core/check/library.d.ts.map +1 -1
  120. package/dist/core/check/library.js +1 -0
  121. package/dist/core/check/library.js.map +1 -1
  122. package/dist/core/check/surface.d.ts +9 -0
  123. package/dist/core/check/surface.d.ts.map +1 -1
  124. package/dist/core/check/surface.js +13 -0
  125. package/dist/core/check/surface.js.map +1 -1
  126. package/dist/core/emit/canonical.d.ts +22 -8
  127. package/dist/core/emit/canonical.d.ts.map +1 -1
  128. package/dist/core/emit/canonical.js +67 -6
  129. package/dist/core/emit/canonical.js.map +1 -1
  130. package/dist/core/engine/arithmetic.d.ts +6 -25
  131. package/dist/core/engine/arithmetic.d.ts.map +1 -1
  132. package/dist/core/engine/arithmetic.js +48 -3
  133. package/dist/core/engine/arithmetic.js.map +1 -1
  134. package/dist/core/engine/bar-source.d.ts +65 -0
  135. package/dist/core/engine/bar-source.d.ts.map +1 -0
  136. package/dist/core/engine/bar-source.js +58 -0
  137. package/dist/core/engine/bar-source.js.map +1 -0
  138. package/dist/core/engine/engine.d.ts +9 -11
  139. package/dist/core/engine/engine.d.ts.map +1 -1
  140. package/dist/core/engine/engine.js +24 -24
  141. package/dist/core/engine/engine.js.map +1 -1
  142. package/dist/core/engine/guard.d.ts.map +1 -1
  143. package/dist/core/engine/guard.js +10 -0
  144. package/dist/core/engine/guard.js.map +1 -1
  145. package/dist/core/engine/host.d.ts +4 -2
  146. package/dist/core/engine/host.d.ts.map +1 -1
  147. package/dist/core/engine/host.js.map +1 -1
  148. package/dist/core/engine/index.d.ts +2 -1
  149. package/dist/core/engine/index.d.ts.map +1 -1
  150. package/dist/core/engine/index.js +1 -1
  151. package/dist/core/engine/index.js.map +1 -1
  152. package/dist/core/engine/library/arrays.d.ts.map +1 -1
  153. package/dist/core/engine/library/arrays.js +8 -2
  154. package/dist/core/engine/library/arrays.js.map +1 -1
  155. package/dist/core/engine/library/binding.d.ts +2 -0
  156. package/dist/core/engine/library/binding.d.ts.map +1 -1
  157. package/dist/core/engine/library/binding.js.map +1 -1
  158. package/dist/core/engine/library/code-points.d.ts +40 -0
  159. package/dist/core/engine/library/code-points.d.ts.map +1 -0
  160. package/dist/core/engine/library/code-points.js +74 -0
  161. package/dist/core/engine/library/code-points.js.map +1 -0
  162. package/dist/core/engine/library/index.d.ts +13 -0
  163. package/dist/core/engine/library/index.d.ts.map +1 -1
  164. package/dist/core/engine/library/index.js +15 -0
  165. package/dist/core/engine/library/index.js.map +1 -1
  166. package/dist/core/engine/library/objects.d.ts.map +1 -1
  167. package/dist/core/engine/library/objects.js +1 -1
  168. package/dist/core/engine/library/objects.js.map +1 -1
  169. package/dist/core/engine/library/text.d.ts.map +1 -1
  170. package/dist/core/engine/library/text.js +51 -34
  171. package/dist/core/engine/library/text.js.map +1 -1
  172. package/dist/core/engine/load.d.ts +20 -0
  173. package/dist/core/engine/load.d.ts.map +1 -1
  174. package/dist/core/engine/load.js +62 -0
  175. package/dist/core/engine/load.js.map +1 -1
  176. package/dist/core/engine/request-body.d.ts +3 -1
  177. package/dist/core/engine/request-body.d.ts.map +1 -1
  178. package/dist/core/engine/request-body.js +3 -1
  179. package/dist/core/engine/request-body.js.map +1 -1
  180. package/dist/core/engine/request-plan.d.ts +3 -3
  181. package/dist/core/engine/request-plan.js +3 -3
  182. package/dist/core/engine/requests.d.ts +2 -2
  183. package/dist/core/engine/requests.d.ts.map +1 -1
  184. package/dist/core/engine/requests.js +5 -4
  185. package/dist/core/engine/requests.js.map +1 -1
  186. package/dist/core/engine/series.d.ts +18 -6
  187. package/dist/core/engine/series.d.ts.map +1 -1
  188. package/dist/core/engine/series.js +24 -5
  189. package/dist/core/engine/series.js.map +1 -1
  190. package/dist/core/engine/session/record.d.ts +4 -4
  191. package/dist/core/engine/session/record.js +4 -4
  192. package/dist/core/engine/verify-tables.d.ts.map +1 -1
  193. package/dist/core/engine/verify-tables.js +41 -0
  194. package/dist/core/engine/verify-tables.js.map +1 -1
  195. package/dist/core/importer/calls.d.ts +29 -0
  196. package/dist/core/importer/calls.d.ts.map +1 -0
  197. package/dist/core/importer/calls.js +267 -0
  198. package/dist/core/importer/calls.js.map +1 -0
  199. package/dist/core/importer/context.d.ts +118 -0
  200. package/dist/core/importer/context.d.ts.map +1 -0
  201. package/dist/core/importer/context.js +120 -0
  202. package/dist/core/importer/context.js.map +1 -0
  203. package/dist/core/importer/expressions.d.ts +44 -0
  204. package/dist/core/importer/expressions.d.ts.map +1 -0
  205. package/dist/core/importer/expressions.js +223 -0
  206. package/dist/core/importer/expressions.js.map +1 -0
  207. package/dist/core/importer/importer.d.ts +13 -0
  208. package/dist/core/importer/importer.d.ts.map +1 -0
  209. package/dist/core/importer/importer.js +220 -0
  210. package/dist/core/importer/importer.js.map +1 -0
  211. package/dist/core/importer/index.d.ts +20 -0
  212. package/dist/core/importer/index.d.ts.map +1 -0
  213. package/dist/core/importer/index.js +18 -0
  214. package/dist/core/importer/index.js.map +1 -0
  215. package/dist/core/importer/lexer.d.ts +68 -0
  216. package/dist/core/importer/lexer.d.ts.map +1 -0
  217. package/dist/core/importer/lexer.js +207 -0
  218. package/dist/core/importer/lexer.js.map +1 -0
  219. package/dist/core/importer/names.d.ts +41 -0
  220. package/dist/core/importer/names.d.ts.map +1 -0
  221. package/dist/core/importer/names.js +78 -0
  222. package/dist/core/importer/names.js.map +1 -0
  223. package/dist/core/importer/orders.d.ts +8 -0
  224. package/dist/core/importer/orders.d.ts.map +1 -0
  225. package/dist/core/importer/orders.js +332 -0
  226. package/dist/core/importer/orders.js.map +1 -0
  227. package/dist/core/importer/outputs.d.ts +19 -0
  228. package/dist/core/importer/outputs.d.ts.map +1 -0
  229. package/dist/core/importer/outputs.js +266 -0
  230. package/dist/core/importer/outputs.js.map +1 -0
  231. package/dist/core/importer/parser.d.ts +5 -0
  232. package/dist/core/importer/parser.d.ts.map +1 -0
  233. package/dist/core/importer/parser.js +439 -0
  234. package/dist/core/importer/parser.js.map +1 -0
  235. package/dist/core/importer/presence.d.ts +32 -0
  236. package/dist/core/importer/presence.d.ts.map +1 -0
  237. package/dist/core/importer/presence.js +146 -0
  238. package/dist/core/importer/presence.js.map +1 -0
  239. package/dist/core/importer/settle.d.ts +23 -0
  240. package/dist/core/importer/settle.d.ts.map +1 -0
  241. package/dist/core/importer/settle.js +135 -0
  242. package/dist/core/importer/settle.js.map +1 -0
  243. package/dist/core/importer/statements.d.ts +4 -0
  244. package/dist/core/importer/statements.d.ts.map +1 -0
  245. package/dist/core/importer/statements.js +339 -0
  246. package/dist/core/importer/statements.js.map +1 -0
  247. package/dist/core/importer/syntax.d.ts +182 -0
  248. package/dist/core/importer/syntax.d.ts.map +1 -0
  249. package/dist/core/importer/syntax.js +14 -0
  250. package/dist/core/importer/syntax.js.map +1 -0
  251. package/dist/core/importer/table.d.ts +52 -0
  252. package/dist/core/importer/table.d.ts.map +1 -0
  253. package/dist/core/importer/table.js +156 -0
  254. package/dist/core/importer/table.js.map +1 -0
  255. package/dist/core/importer/values.d.ts +27 -0
  256. package/dist/core/importer/values.d.ts.map +1 -0
  257. package/dist/core/importer/values.js +236 -0
  258. package/dist/core/importer/values.js.map +1 -0
  259. package/dist/core/index.d.ts +22 -5
  260. package/dist/core/index.d.ts.map +1 -1
  261. package/dist/core/index.js +19 -3
  262. package/dist/core/index.js.map +1 -1
  263. package/dist/core/stdlib/calendar/civil.d.ts.map +1 -1
  264. package/dist/core/stdlib/calendar/civil.js +5 -2
  265. package/dist/core/stdlib/calendar/civil.js.map +1 -1
  266. package/dist/core/stdlib/index.d.ts +1 -1
  267. package/dist/core/stdlib/index.d.ts.map +1 -1
  268. package/dist/core/stdlib/index.js +1 -1
  269. package/dist/core/stdlib/index.js.map +1 -1
  270. package/dist/core/stdlib/maths/index.d.ts +1 -1
  271. package/dist/core/stdlib/maths/index.d.ts.map +1 -1
  272. package/dist/core/stdlib/maths/index.js +1 -1
  273. package/dist/core/stdlib/maths/index.js.map +1 -1
  274. package/dist/core/stdlib/maths/rounding.d.ts +5 -0
  275. package/dist/core/stdlib/maths/rounding.d.ts.map +1 -1
  276. package/dist/core/stdlib/maths/rounding.js +19 -1
  277. package/dist/core/stdlib/maths/rounding.js.map +1 -1
  278. package/dist/core/version/version.generated.d.ts +1 -1
  279. package/dist/core/version/version.generated.js +1 -1
  280. package/package.json +17 -2
  281. package/spec/README.md +2 -1
  282. package/spec/errors.json +383 -23
  283. package/src/adapters/charts/driving.ts +109 -0
  284. package/src/adapters/charts/run.ts +126 -28
  285. package/src/adapters/charts/tables.ts +6 -19
  286. package/src/adapters/charts/undrawable.ts +51 -0
  287. package/src/adapters/charts/venue.ts +132 -0
  288. package/src/core/accounting/analysis.ts +188 -0
  289. package/src/core/accounting/equity.ts +38 -0
  290. package/src/core/accounting/index.ts +2 -0
  291. package/src/core/accounting/report.ts +5 -0
  292. package/src/core/accounting/statistics.ts +38 -1
  293. package/src/core/backtest/case.ts +395 -0
  294. package/src/core/backtest/compare.ts +2 -0
  295. package/src/core/backtest/deliver.ts +161 -0
  296. package/src/core/backtest/drive.ts +233 -217
  297. package/src/core/backtest/index.ts +26 -12
  298. package/src/core/backtest/record.ts +134 -5
  299. package/src/core/backtest/replay.ts +11 -1
  300. package/src/core/backtest/simulate.ts +201 -6
  301. package/src/core/backtest/walk.ts +182 -0
  302. package/src/core/catalogue/catalogue.generated.ts +19 -2
  303. package/src/core/catalogue/types.ts +8 -2
  304. package/src/core/catalogue/values.generated.ts +16 -0
  305. package/src/core/check/assigned.ts +31 -0
  306. package/src/core/check/call-sites.ts +11 -4
  307. package/src/core/check/calls.ts +13 -4
  308. package/src/core/check/check.ts +14 -2
  309. package/src/core/check/checker.ts +39 -2
  310. package/src/core/check/constant.ts +57 -0
  311. package/src/core/check/deleted.ts +135 -0
  312. package/src/core/check/expressions.ts +1 -1
  313. package/src/core/check/library-orders.ts +2 -2
  314. package/src/core/check/library-output.ts +1 -0
  315. package/src/core/check/library-prose.generated.ts +12 -12
  316. package/src/core/check/library.ts +8 -0
  317. package/src/core/check/surface.ts +16 -0
  318. package/src/core/emit/canonical.ts +67 -9
  319. package/src/core/engine/arithmetic.ts +23 -3
  320. package/src/core/engine/bar-source.ts +113 -0
  321. package/src/core/engine/engine.ts +26 -25
  322. package/src/core/engine/guard.ts +12 -1
  323. package/src/core/engine/host.ts +4 -2
  324. package/src/core/engine/index.ts +2 -1
  325. package/src/core/engine/library/arrays.ts +8 -2
  326. package/src/core/engine/library/binding.ts +2 -0
  327. package/src/core/engine/library/code-points.ts +73 -0
  328. package/src/core/engine/library/index.ts +17 -0
  329. package/src/core/engine/library/objects.ts +1 -6
  330. package/src/core/engine/library/text.ts +53 -35
  331. package/src/core/engine/load.ts +68 -0
  332. package/src/core/engine/request-body.ts +5 -7
  333. package/src/core/engine/request-plan.ts +3 -3
  334. package/src/core/engine/requests.ts +11 -8
  335. package/src/core/engine/series.ts +30 -6
  336. package/src/core/engine/session/record.ts +4 -4
  337. package/src/core/engine/verify-tables.ts +43 -0
  338. package/src/core/importer/calls.ts +274 -0
  339. package/src/core/importer/context.ts +197 -0
  340. package/src/core/importer/expressions.ts +247 -0
  341. package/src/core/importer/importer.ts +226 -0
  342. package/src/core/importer/index.ts +19 -0
  343. package/src/core/importer/lexer.ts +285 -0
  344. package/src/core/importer/names.ts +104 -0
  345. package/src/core/importer/orders.ts +307 -0
  346. package/src/core/importer/outputs.ts +245 -0
  347. package/src/core/importer/parser.ts +436 -0
  348. package/src/core/importer/presence.ts +164 -0
  349. package/src/core/importer/settle.ts +141 -0
  350. package/src/core/importer/statements.ts +331 -0
  351. package/src/core/importer/syntax.ts +144 -0
  352. package/src/core/importer/table.ts +191 -0
  353. package/src/core/importer/values.ts +241 -0
  354. package/src/core/index.ts +29 -2
  355. package/src/core/stdlib/calendar/civil.ts +5 -2
  356. package/src/core/stdlib/index.ts +1 -0
  357. package/src/core/stdlib/maths/index.ts +1 -0
  358. package/src/core/stdlib/maths/rounding.ts +23 -1
  359. 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
 
@@ -24,7 +24,7 @@
24
24
  export { Engine } from './engine.js';
25
25
  export type { BarResult, RunResult } from './results.js';
26
26
 
27
- export { load } from './load.js';
27
+ export { load, loadText } from './load.js';
28
28
  export type { LoadOptions, LoadResult } from './load.js';
29
29
 
30
30
  export type { AlertFiring } from './alerts.js';
@@ -32,6 +32,7 @@ export type { AlertFiring } from './alerts.js';
32
32
  export type { Grid } from './grids.js';
33
33
 
34
34
  export type { BarFacts, BarState, HostBar } from './bars.js';
35
+ export type { BarColumn, BarColumns } from './bar-source.js';
35
36
  export { BAR_FIELDS, barField, isBarField } from './bars.js';
36
37
 
37
38
  export type { Clock, EngineLimits } from './budget.js';
@@ -22,6 +22,7 @@ import type { Value } from '../values/index.js';
22
22
  import { isNumber, reference, valuesEqual } from '../values/index.js';
23
23
  import { entry, refAt, stringAt, valueAt, wholeAt } from './binding.js';
24
24
  import type { CallContext, ManifestEntry } from './binding.js';
25
+ import { compareStrings } from './code-points.js';
25
26
  import { result } from '../../stdlib/index.js';
26
27
 
27
28
  /** The array a handle names, or nothing when the handle is absent or stale. */
@@ -68,12 +69,17 @@ function within(
68
69
  return index;
69
70
  }
70
71
 
71
- /** The order `sort` uses, written down so two engines cannot differ. */
72
+ /**
73
+ * The order `sort` uses, written down so two engines cannot differ.
74
+ *
75
+ * Two strings order by code point (`language.md` 9.3), which is not the order
76
+ * the host's `<` gives once a string holds a symbol outside the basic plane.
77
+ */
72
78
  function rank(a: Value, b: Value): number {
73
79
  if (a === null) return b === null ? 0 : 1;
74
80
  if (b === null) return -1;
75
81
  if (typeof a === 'number' && typeof b === 'number') return a < b ? -1 : a > b ? 1 : 0;
76
- if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0;
82
+ if (typeof a === 'string' && typeof b === 'string') return compareStrings(a, b);
77
83
  if (typeof a === 'boolean' && typeof b === 'boolean') return a === b ? 0 : a ? 1 : -1;
78
84
  return 0;
79
85
  }
@@ -160,6 +160,8 @@ export interface Guard {
160
160
  badArgument(span: Span, name: string, argument: string, found: string): never;
161
161
  /** Raise a run-time diagnostic against an index outside an array. */
162
162
  badIndex(span: Span, name: string, index: string, size: number): never;
163
+ /** Raise a run-time diagnostic against a cell outside a table's declared shape. */
164
+ badCell(span: Span, row: number, column: number, rows: number, columns: number): never;
163
165
  /** Raise a run-time diagnostic against an object a script already deleted. */
164
166
  deleted(span: Span, kind: string, bar: number): never;
165
167
  /** Raise a run-time diagnostic against a zone name the host does not know. */
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The two string rules two engines have to share, written from the page.
3
+ *
4
+ * **A string is a sequence of code points** (`compiled-program.md` 3.1), and the
5
+ * host this engine is written in stores one as sixteen bit units. Every rule
6
+ * here counts code points and not units, because the two disagree the moment a
7
+ * string holds a symbol outside the basic plane, and a rule written on the
8
+ * host's units would be a rule a second engine could not follow.
9
+ *
10
+ * **Ordering.** Two strings are ordered by comparing code points from the
11
+ * front, the first difference deciding, and a string that runs out first
12
+ * ordering first (`language.md` 9.3, `stdlib.md` section 10). The host's own
13
+ * `<` orders by unit, which puts every symbol outside the basic plane, stored
14
+ * as a surrogate pair from U+D800, below the code points from U+E000 to U+FFFF
15
+ * that come after it, so it is not used.
16
+ *
17
+ * **Whitespace.** `str.trim` removes, and `toNumber` ignores, exactly the code
18
+ * points `stdlib.md` section 10 lists, which are the ones with the Unicode
19
+ * White_Space property. The host's own `trim` removes a set that is nearly this
20
+ * one: it takes the byte order mark U+FEFF as well, and a second engine's host
21
+ * takes the four separators U+001C to U+001F and leaves the byte order mark, so
22
+ * neither host's default is the set, and the set is implemented from the list.
23
+ * `tests/engine/strings.test.ts` walks every code point of the basic plane
24
+ * against the list read out of the page.
25
+ */
26
+
27
+ /** The code points `str.trim` removes, as `stdlib.md` section 10 lists them. */
28
+ const WHITESPACE: ReadonlySet<number> = new Set([
29
+ 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x0020, 0x0085, 0x00a0, 0x1680,
30
+ 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
31
+ 0x2028, 0x2029, 0x202f, 0x205f, 0x3000,
32
+ ]);
33
+
34
+ /** Code points, which is what every index over a string counts. */
35
+ export function points(text: string): string[] {
36
+ return [...text];
37
+ }
38
+
39
+ /** Whether one code point is in the trimmed set. */
40
+ export function isWhitespace(point: number): boolean {
41
+ return WHITESPACE.has(point);
42
+ }
43
+
44
+ /** `text` with the whitespace of the written set taken off both ends. */
45
+ export function trimmed(text: string): string {
46
+ const all = points(text);
47
+ let from = 0;
48
+ let to = all.length;
49
+ while (from < to && isWhitespace(all[from]?.codePointAt(0) ?? -1)) from += 1;
50
+ while (to > from && isWhitespace(all[to - 1]?.codePointAt(0) ?? -1)) to -= 1;
51
+ return from === 0 && to === all.length ? text : all.slice(from, to).join('');
52
+ }
53
+
54
+ /**
55
+ * The order of two strings, negative when `a` comes first, by code point.
56
+ *
57
+ * Walked with the string iterator so a surrogate pair is one code point, and
58
+ * compared as numbers so U+10000 comes after U+FFFF, which is the order the
59
+ * page states and the one an engine with four byte strings gets for free.
60
+ */
61
+ export function compareStrings(a: string, b: string): number {
62
+ const left = a[Symbol.iterator]();
63
+ const right = b[Symbol.iterator]();
64
+ for (;;) {
65
+ const one = left.next();
66
+ const other = right.next();
67
+ if (one.done) return other.done ? 0 : -1;
68
+ if (other.done) return 1;
69
+ const x = one.value.codePointAt(0) ?? 0;
70
+ const y = other.value.codePointAt(0) ?? 0;
71
+ if (x !== y) return x < y ? -1 : 1;
72
+ }
73
+ }
@@ -37,6 +37,12 @@ import { TREND_ENTRIES } from './trend.js';
37
37
  import { VOLUME_ENTRIES } from './volume.js';
38
38
  import type { ManifestEntry } from './binding.js';
39
39
 
40
+ /**
41
+ * The two string rules, for the operators outside this module and the tests:
42
+ * `code-points.ts` says why neither is the host's own.
43
+ */
44
+ export { compareStrings, isWhitespace, trimmed } from './code-points.js';
45
+
40
46
  const ENTRIES: readonly ManifestEntry[] = [
41
47
  ...COLOUR_ENTRIES,
42
48
  ...MATHS_ENTRIES,
@@ -68,6 +74,17 @@ for (const one of ENTRIES) {
68
74
  }
69
75
 
70
76
  /** The entry for one name and argument count, or nothing when there is none. */
77
+ /**
78
+ * Every name and argument count this engine holds, as `name/arity`, sorted.
79
+ *
80
+ * `scripts/check-manifests.mjs` compares it with the second engine's, so a call
81
+ * one engine holds and the other does not is found by a build rather than by the
82
+ * first case that reaches it.
83
+ */
84
+ export function manifestKeys(): readonly string[] {
85
+ return [...INDEX.keys()].sort();
86
+ }
87
+
71
88
  export function manifestEntry(name: string, arity: number): ManifestEntry | undefined {
72
89
  return INDEX.get(keyOf(name, arity));
73
90
  }
@@ -234,12 +234,7 @@ export const OBJECT_ENTRIES: readonly ManifestEntry[] = [
234
234
  const col = wholeAt(ctx, 'cell', 'col', args, 2);
235
235
  if (row === null || col === null) return null;
236
236
  if (row >= object.rows || col >= object.cols) {
237
- ctx.guard.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,
@@ -8,34 +8,50 @@
8
8
  * one element, or two engines will disagree about the length of a string
9
9
  * holding a symbol outside the basic plane and about every substring taken
10
10
  * after one. That is what the spread into an array is doing everywhere below:
11
- * it costs a pass over the string and buys agreement.
11
+ * it costs a pass over the string and buys agreement. The trimmed set and the
12
+ * order of two strings are in `code-points.ts`, written from the page.
12
13
  *
13
14
  * **Number to string is specified, not the host's default.** `text(x)` is the
14
- * shortest decimal string that reads back as the same binary64 value, which is
15
- * what this language's own conversion already produces. `text(x, d)` rounds to
16
- * `d` decimals with halves away from zero and always emits exactly `d` digits
17
- * after the point, because this is a display conversion and half up is what a
18
- * reader of a price expects.
15
+ * rule of `language.md` 5.5 and goes through the one writer that implements
16
+ * it, `canonicalNumber`, as does every digit this file writes: a host's own
17
+ * conversion is never asked. `text(x, d)` rounds to `d` decimals with halves
18
+ * away from zero and always emits exactly `d` digits after the point, because
19
+ * this is a display conversion and half up is what a reader of a price expects.
19
20
  */
20
- import { roundHalfAway } from '../../stdlib/index.js';
21
+ import { canonicalNumber } from '../../emit/index.js';
22
+ import { roundHalfAway, scaleOf } from '../../stdlib/index.js';
21
23
  import type { Value } from '../values/index.js';
22
24
  import { isColour, reference } from '../values/index.js';
23
25
  import { entry, numberAt, refAt, stringAt, valueAt, wholeAt } from './binding.js';
24
26
  import type { CallContext, ManifestEntry } from './binding.js';
27
+ import { points, trimmed } from './code-points.js';
25
28
 
26
29
  /** A value as `text(x)` spells it. */
27
30
  export function spell(ctx: CallContext, value: Value): string {
28
31
  if (value === null) return 'none';
29
- if (typeof value === 'number') return String(value);
30
- if (typeof value === 'boolean') return String(value);
32
+ if (typeof value === 'number') return canonicalNumber(value);
33
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
31
34
  if (typeof value === 'string') return value;
32
35
  if (isColour(value)) {
33
- const byte = (x: number): string => x.toString(16).padStart(2, '0');
34
- return `#${byte(value.r)}${byte(value.g)}${byte(value.b)}${byte(roundHalfAway(value.a * 255))}`;
36
+ const alpha = roundHalfAway(value.a * 255);
37
+ return `#${hexByte(value.r)}${hexByte(value.g)}${hexByte(value.b)}${hexByte(alpha)}`;
35
38
  }
36
39
  return ctx.heap.get(value.id)?.kind ?? 'none';
37
40
  }
38
41
 
42
+ /**
43
+ * A whole number from 0 to 255 as two hex digits, from a table.
44
+ *
45
+ * A colour's channels are whole numbers (`compiled-program.md` 3.1), so this
46
+ * is not the decimal rendering rule and asks no host conversion: each nibble
47
+ * indexes a string of sixteen characters.
48
+ */
49
+ const HEX_DIGITS = '0123456789abcdef';
50
+
51
+ function hexByte(channel: number): string {
52
+ return `${HEX_DIGITS[channel >> 4] ?? '0'}${HEX_DIGITS[channel & 15] ?? '0'}`;
53
+ }
54
+
39
55
  /** A magnitude as decimal digits, and how many of them fall before the point. */
40
56
  interface Spread {
41
57
  readonly digits: string;
@@ -108,24 +124,31 @@ function written(of: Spread): string {
108
124
  * least one digit, and exactly `d` digits after the point. `spread` is what
109
125
  * makes that true of every magnitude rather than of the ones below a threshold.
110
126
  *
111
- * **Past the scaling range the digits are the shortest form's, zero filled.**
112
- * Scaling by `10 ** d` leaves binary64 altogether for a large enough magnitude
113
- * or a large enough `d`, and there is nothing to round out there: a binary64 at
114
- * or above 2 ** 53 is a whole number already, and a decimal place that far from
115
- * the leading digit is past every digit the value carries. Two engines write
116
- * the same digits, because the shortest decimal form is what each one's own
117
- * conversion produces. Inside the range the scaling is what it was, so no value
118
- * that had an answer has a different one.
127
+ * **The scale is `round(x, d)`'s**, `scaleOf` from the numeric library, so
128
+ * the display conversion and the rounding call multiply by one value
129
+ * (`stdlib.md` 20.7) and cannot part by an ulp at the one count where the
130
+ * host's power does.
131
+ *
132
+ * **The digits are the shortest form's, zero filled, at every magnitude.**
133
+ * Below 2 ** 53 a rounded whole number has no digits but its own, so nothing
134
+ * ordinary changes; at or above it the shortest form is what both engines
135
+ * write, and it is what this uses inside the scaling range as well as past it,
136
+ * where scaling by ten to the `d` leaves binary64 altogether and there is
137
+ * nothing to round: a binary64 that large is a whole number already, and a
138
+ * decimal place that far from the leading digit is past every digit the value
139
+ * carries. One rule for the digits at every magnitude, rather than the exact
140
+ * binary expansion below a threshold and the shortest form above it.
119
141
  */
120
142
  function fixed(x: number, decimals: number): string {
121
- const scaled = roundHalfAway(x * Math.pow(10, decimals));
143
+ const scaled = roundHalfAway(x * scaleOf(decimals));
122
144
  const usable = Number.isFinite(scaled);
123
145
  const sign = (usable ? scaled < 0 : x < 0) ? '-' : '';
124
- // Within the scaling range the whole number's own digits are asked for and
125
- // not its shortest form, which drops the low digits of a large one.
146
+ // The digits are the shortest form's at every magnitude, through the one
147
+ // writer: a rounded whole below 2 ** 53 has no other digits, and above it
148
+ // the shortest form is the one rule both engines write (language.md 5.5).
126
149
  const magnitude = usable
127
- ? spread(Math.abs(scaled).toFixed(0), 0)
128
- : spread(Math.abs(x).toString(), decimals);
150
+ ? spread(canonicalNumber(Math.abs(scaled)), 0)
151
+ : spread(canonicalNumber(Math.abs(x)), decimals);
129
152
  const digits = written(magnitude).padStart(decimals + 1, '0');
130
153
  if (decimals === 0) return sign + digits;
131
154
  const whole = digits.slice(0, digits.length - decimals);
@@ -143,7 +166,7 @@ function fixed(x: number, decimals: number): string {
143
166
  * have. `str.repeat` measures first for the same reason.
144
167
  */
145
168
  function lengthOf(x: number, decimals: number): number {
146
- const before = Math.max(1, spread(Math.abs(x).toString(), 0).point);
169
+ const before = Math.max(1, spread(canonicalNumber(Math.abs(x)), 0).point);
147
170
  return before + decimals + (decimals > 0 ? 1 : 0);
148
171
  }
149
172
 
@@ -154,22 +177,17 @@ function lengthOf(x: number, decimals: number): number {
154
177
  * which accepts hexadecimal, infinities and a bare leading point in some
155
178
  * languages and not others. Absence rather than zero for text that is not a
156
179
  * number, so a script can tell text that is not a number apart from the number
157
- * zero.
180
+ * zero. The whitespace ignored at either end is `str.trim`'s set and no other.
158
181
  */
159
182
  const NUMERIC = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
160
183
 
161
184
  function parseNumber(text: string): Value {
162
- const trimmed = text.trim();
163
- if (!NUMERIC.test(trimmed)) return null;
164
- const parsed = Number(trimmed);
185
+ const bare = trimmed(text);
186
+ if (!NUMERIC.test(bare)) return null;
187
+ const parsed = Number(bare);
165
188
  return Number.isFinite(parsed) ? (parsed === 0 ? 0 : parsed) : null;
166
189
  }
167
190
 
168
- /** Code points, which is what every index in this file counts. */
169
- function points(text: string): string[] {
170
- return [...text];
171
- }
172
-
173
191
  function makeArray(ctx: CallContext, items: Value[]): Value {
174
192
  ctx.guard.array(ctx.span, 'the array', items.length);
175
193
  return reference(ctx.heap.allocate({ kind: 'array', items }));
@@ -198,7 +216,7 @@ export const TEXT_ENTRIES: readonly ManifestEntry[] = [
198
216
 
199
217
  entry('str.upper', 's', (ctx, args) => map(ctx, args, (s) => s.toUpperCase())),
200
218
  entry('str.lower', 's', (ctx, args) => map(ctx, args, (s) => s.toLowerCase())),
201
- entry('str.trim', 's', (ctx, args) => map(ctx, args, (s) => s.trim())),
219
+ entry('str.trim', 's', (ctx, args) => map(ctx, args, trimmed)),
202
220
 
203
221
  entry('str.contains', 's part', (_ctx, args) => pair(args, (s, part) => s.includes(part))),
204
222
  entry('str.startsWith', 's part', (_ctx, args) => pair(args, (s, part) => s.startsWith(part))),
@@ -10,6 +10,7 @@
10
10
  * bar four thousand with half a study on the screen.
11
11
  */
12
12
  import type { Diagnostic } from '../diagnostics/index.js';
13
+ import { canonicalise } from '../emit/index.js';
13
14
  import type { SourceFile } from '../source/index.js';
14
15
  import { BAR_FIELDS } from './bars.js';
15
16
  import { limitsWith } from './budget.js';
@@ -116,3 +117,70 @@ export function load(program: unknown, options: LoadOptions = {}): LoadResult {
116
117
  inputs: resolved.inputs,
117
118
  };
118
119
  }
120
+
121
+ /**
122
+ * Loads a compiled program from text, which is where canonicity is required.
123
+ *
124
+ * `load` takes a parsed object because a host that compiled in this process
125
+ * never serialised, and there is nothing for such an object to be canonical
126
+ * about. Text is the other boundary, `compiled-program.md` 9.4 step 1 and
127
+ * section 13 (decision 57): a program an engine reads from outside its process
128
+ * arrives as the canonical encoding of 2.14, and text that parses to a program
129
+ * but is not that encoding is refused, because the hash a host recorded was
130
+ * taken over canonical bytes and text in any other spelling is text that hash
131
+ * does not name. So the text is parsed, written out again by the one canonical
132
+ * writer, and the two are compared character for character. Either failure is
133
+ * OS6018 naming where the text stops being readable or stops being canonical,
134
+ * counted in characters of the text, which is where an editor's cursor lands.
135
+ *
136
+ * What passes here is the object `load` verifies from step 2 on, so every
137
+ * refusal that applies to an object applies to text as well and in the same
138
+ * order.
139
+ */
140
+ export function loadText(text: string, options: LoadOptions = {}): LoadResult {
141
+ let parsed: unknown;
142
+ try {
143
+ parsed = JSON.parse(text);
144
+ } catch (thrown) {
145
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
146
+ const at = /position (\d+)/.exec(message);
147
+ return {
148
+ ok: false,
149
+ diagnostic: malformed(
150
+ at === null ? 'the text' : `character ${at[1]}`,
151
+ 'the text stops being the object notation of section 2.14 there, so it is not a program',
152
+ ),
153
+ };
154
+ }
155
+
156
+ let canonical: string;
157
+ try {
158
+ canonical = canonicalise(parsed);
159
+ } catch (thrown) {
160
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
161
+ return {
162
+ ok: false,
163
+ diagnostic: malformed('the text', `it parses but cannot be written in the canonical form: ${message}`),
164
+ };
165
+ }
166
+ if (canonical !== text) {
167
+ return {
168
+ ok: false,
169
+ diagnostic: malformed(
170
+ `character ${firstDifference(text, canonical)}`,
171
+ 'the text is not the canonical encoding of the program it parses to, and a program ' +
172
+ 'read from outside the process has to be, because that is what its hash was taken over',
173
+ ),
174
+ };
175
+ }
176
+ return load(parsed, options);
177
+ }
178
+
179
+ /** Where two texts part, as an index into either, or the shorter one's length. */
180
+ function firstDifference(a: string, b: string): number {
181
+ const shorter = Math.min(a.length, b.length);
182
+ for (let i = 0; i < shorter; i += 1) {
183
+ if (a[i] !== b[i]) return i;
184
+ }
185
+ return shorter;
186
+ }
@@ -28,6 +28,8 @@
28
28
  import type { Span } from '../span/index.js';
29
29
  import { barField, factsFor } from './bars.js';
30
30
  import type { HostBar } from './bars.js';
31
+ import { recordSource } from './bar-source.js';
32
+ import type { BarSource } from './bar-source.js';
31
33
  import { Budget, stepBound } from './budget.js';
32
34
  import type { Clock, EngineLimits } from './budget.js';
33
35
  import { Channels } from './channels.js';
@@ -59,12 +61,7 @@ export interface BodyInput {
59
61
  */
60
62
  export interface NestedRequests {
61
63
  readonly empty: boolean;
62
- fill(
63
- registers: Registers,
64
- bars: readonly HostBar[],
65
- index: number,
66
- fresh: boolean,
67
- ): void;
64
+ fill(registers: Registers, bars: BarSource, index: number, fresh: boolean): void;
68
65
  }
69
66
 
70
67
  export interface BodyParts {
@@ -120,6 +117,7 @@ export class RequestBody {
120
117
  private lastClose: number | null = null;
121
118
  /** The requested bars themselves, kept only when a read inside folds them. */
122
119
  private readonly history: HostBar[] = [];
120
+ private readonly historySource = recordSource(this.history);
123
121
  /**
124
122
  * The value the last closed requested bar handed back.
125
123
  *
@@ -251,7 +249,7 @@ export class RequestBody {
251
249
  const nested = this.parts.nested;
252
250
  if (nested !== undefined && !nested.empty) {
253
251
  this.history[index] = bar;
254
- nested.fill(this.registers, this.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;