openalgo-script 0.2.0 → 0.5.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 (357) hide show
  1. package/CHANGELOG.md +1198 -0
  2. package/README.md +127 -40
  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 +83 -16
  10. package/dist/adapters/charts/run.js.map +1 -1
  11. package/dist/adapters/charts/surfaces.d.ts +17 -0
  12. package/dist/adapters/charts/surfaces.d.ts.map +1 -1
  13. package/dist/adapters/charts/tables.js +82 -0
  14. package/dist/adapters/charts/tables.js.map +1 -1
  15. package/dist/adapters/charts/venue.d.ts +73 -0
  16. package/dist/adapters/charts/venue.d.ts.map +1 -0
  17. package/dist/adapters/charts/venue.js +104 -0
  18. package/dist/adapters/charts/venue.js.map +1 -0
  19. package/dist/adapters/codemirror/commands.d.ts +14 -0
  20. package/dist/adapters/codemirror/commands.d.ts.map +1 -0
  21. package/dist/adapters/codemirror/commands.js +52 -0
  22. package/dist/adapters/codemirror/commands.js.map +1 -0
  23. package/dist/adapters/codemirror/completion.d.ts +15 -0
  24. package/dist/adapters/codemirror/completion.d.ts.map +1 -0
  25. package/dist/adapters/codemirror/completion.js +64 -0
  26. package/dist/adapters/codemirror/completion.js.map +1 -0
  27. package/dist/adapters/codemirror/contract.d.ts +156 -0
  28. package/dist/adapters/codemirror/contract.d.ts.map +1 -0
  29. package/dist/adapters/codemirror/contract.js +42 -0
  30. package/dist/adapters/codemirror/contract.js.map +1 -0
  31. package/dist/adapters/codemirror/index.d.ts +43 -0
  32. package/dist/adapters/codemirror/index.d.ts.map +1 -0
  33. package/dist/adapters/codemirror/index.js +8 -0
  34. package/dist/adapters/codemirror/index.js.map +1 -0
  35. package/dist/adapters/codemirror/lint.d.ts +17 -0
  36. package/dist/adapters/codemirror/lint.d.ts.map +1 -0
  37. package/dist/adapters/codemirror/lint.js +62 -0
  38. package/dist/adapters/codemirror/lint.js.map +1 -0
  39. package/dist/adapters/codemirror/positions.d.ts +16 -0
  40. package/dist/adapters/codemirror/positions.d.ts.map +1 -0
  41. package/dist/adapters/codemirror/positions.js +65 -0
  42. package/dist/adapters/codemirror/positions.js.map +1 -0
  43. package/dist/adapters/codemirror/stream.d.ts +23 -0
  44. package/dist/adapters/codemirror/stream.d.ts.map +1 -0
  45. package/dist/adapters/codemirror/stream.js +70 -0
  46. package/dist/adapters/codemirror/stream.js.map +1 -0
  47. package/dist/adapters/codemirror/tokens.d.ts +44 -0
  48. package/dist/adapters/codemirror/tokens.d.ts.map +1 -0
  49. package/dist/adapters/codemirror/tokens.js +34 -0
  50. package/dist/adapters/codemirror/tokens.js.map +1 -0
  51. package/dist/adapters/codemirror/tooltips.d.ts +35 -0
  52. package/dist/adapters/codemirror/tooltips.d.ts.map +1 -0
  53. package/dist/adapters/codemirror/tooltips.js +108 -0
  54. package/dist/adapters/codemirror/tooltips.js.map +1 -0
  55. package/dist/core/accounting/analysis.d.ts +111 -0
  56. package/dist/core/accounting/analysis.d.ts.map +1 -0
  57. package/dist/core/accounting/analysis.js +123 -0
  58. package/dist/core/accounting/analysis.js.map +1 -0
  59. package/dist/core/accounting/charges.d.ts +136 -0
  60. package/dist/core/accounting/charges.d.ts.map +1 -0
  61. package/dist/core/accounting/charges.js +362 -0
  62. package/dist/core/accounting/charges.js.map +1 -0
  63. package/dist/core/accounting/equity.d.ts +191 -0
  64. package/dist/core/accounting/equity.d.ts.map +1 -0
  65. package/dist/core/accounting/equity.js +167 -0
  66. package/dist/core/accounting/equity.js.map +1 -0
  67. package/dist/core/accounting/index.d.ts +46 -0
  68. package/dist/core/accounting/index.d.ts.map +1 -0
  69. package/dist/core/accounting/index.js +37 -0
  70. package/dist/core/accounting/index.js.map +1 -0
  71. package/dist/core/accounting/markers.d.ts +43 -0
  72. package/dist/core/accounting/markers.d.ts.map +1 -0
  73. package/dist/core/accounting/markers.js +64 -0
  74. package/dist/core/accounting/markers.js.map +1 -0
  75. package/dist/core/accounting/monthly.d.ts +52 -0
  76. package/dist/core/accounting/monthly.d.ts.map +1 -0
  77. package/dist/core/accounting/monthly.js +99 -0
  78. package/dist/core/accounting/monthly.js.map +1 -0
  79. package/dist/core/accounting/report.d.ts +41 -0
  80. package/dist/core/accounting/report.d.ts.map +1 -0
  81. package/dist/core/accounting/report.js +65 -0
  82. package/dist/core/accounting/report.js.map +1 -0
  83. package/dist/core/accounting/shapes.d.ts +70 -0
  84. package/dist/core/accounting/shapes.d.ts.map +1 -0
  85. package/dist/core/accounting/shapes.js +21 -0
  86. package/dist/core/accounting/shapes.js.map +1 -0
  87. package/dist/core/accounting/statistics.d.ts +87 -0
  88. package/dist/core/accounting/statistics.d.ts.map +1 -0
  89. package/dist/core/accounting/statistics.js +268 -0
  90. package/dist/core/accounting/statistics.js.map +1 -0
  91. package/dist/core/accounting/trades.d.ts +118 -0
  92. package/dist/core/accounting/trades.d.ts.map +1 -0
  93. package/dist/core/accounting/trades.js +186 -0
  94. package/dist/core/accounting/trades.js.map +1 -0
  95. package/dist/core/backtest/case.d.ts +60 -0
  96. package/dist/core/backtest/case.d.ts.map +1 -0
  97. package/dist/core/backtest/case.js +319 -0
  98. package/dist/core/backtest/case.js.map +1 -0
  99. package/dist/core/backtest/compare.d.ts +38 -0
  100. package/dist/core/backtest/compare.d.ts.map +1 -0
  101. package/dist/core/backtest/compare.js +191 -0
  102. package/dist/core/backtest/compare.js.map +1 -0
  103. package/dist/core/backtest/declaration.d.ts +52 -0
  104. package/dist/core/backtest/declaration.d.ts.map +1 -0
  105. package/dist/core/backtest/declaration.js +48 -0
  106. package/dist/core/backtest/declaration.js.map +1 -0
  107. package/dist/core/backtest/deliver.d.ts +93 -0
  108. package/dist/core/backtest/deliver.d.ts.map +1 -0
  109. package/dist/core/backtest/deliver.js +94 -0
  110. package/dist/core/backtest/deliver.js.map +1 -0
  111. package/dist/core/backtest/drive.d.ts +87 -0
  112. package/dist/core/backtest/drive.d.ts.map +1 -0
  113. package/dist/core/backtest/drive.js +312 -0
  114. package/dist/core/backtest/drive.js.map +1 -0
  115. package/dist/core/backtest/index.d.ts +58 -0
  116. package/dist/core/backtest/index.d.ts.map +1 -0
  117. package/dist/core/backtest/index.js +47 -0
  118. package/dist/core/backtest/index.js.map +1 -0
  119. package/dist/core/backtest/range.d.ts +84 -0
  120. package/dist/core/backtest/range.d.ts.map +1 -0
  121. package/dist/core/backtest/range.js +90 -0
  122. package/dist/core/backtest/range.js.map +1 -0
  123. package/dist/core/backtest/record.d.ts +299 -0
  124. package/dist/core/backtest/record.d.ts.map +1 -0
  125. package/dist/core/backtest/record.js +243 -0
  126. package/dist/core/backtest/record.js.map +1 -0
  127. package/dist/core/backtest/replay.d.ts +48 -0
  128. package/dist/core/backtest/replay.d.ts.map +1 -0
  129. package/dist/core/backtest/replay.js +137 -0
  130. package/dist/core/backtest/replay.js.map +1 -0
  131. package/dist/core/backtest/resting.d.ts +62 -0
  132. package/dist/core/backtest/resting.d.ts.map +1 -0
  133. package/dist/core/backtest/resting.js +59 -0
  134. package/dist/core/backtest/resting.js.map +1 -0
  135. package/dist/core/backtest/settings.d.ts +117 -0
  136. package/dist/core/backtest/settings.d.ts.map +1 -0
  137. package/dist/core/backtest/settings.js +207 -0
  138. package/dist/core/backtest/settings.js.map +1 -0
  139. package/dist/core/backtest/simulate.d.ts +258 -0
  140. package/dist/core/backtest/simulate.d.ts.map +1 -0
  141. package/dist/core/backtest/simulate.js +335 -0
  142. package/dist/core/backtest/simulate.js.map +1 -0
  143. package/dist/core/catalogue/catalogue.generated.d.ts +66 -0
  144. package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
  145. package/dist/core/catalogue/catalogue.generated.js +6 -0
  146. package/dist/core/catalogue/catalogue.generated.js.map +1 -1
  147. package/dist/core/catalogue/values.generated.d.ts +24 -0
  148. package/dist/core/catalogue/values.generated.d.ts.map +1 -1
  149. package/dist/core/check/index.d.ts +2 -1
  150. package/dist/core/check/index.d.ts.map +1 -1
  151. package/dist/core/check/index.js +1 -1
  152. package/dist/core/check/index.js.map +1 -1
  153. package/dist/core/check/library-orders.js +2 -2
  154. package/dist/core/check/library-orders.js.map +1 -1
  155. package/dist/core/check/library-prose.generated.d.ts +16 -0
  156. package/dist/core/check/library-prose.generated.d.ts.map +1 -0
  157. package/dist/core/check/library-prose.generated.js +353 -0
  158. package/dist/core/check/library-prose.generated.js.map +1 -0
  159. package/dist/core/check/surface.d.ts +24 -0
  160. package/dist/core/check/surface.d.ts.map +1 -1
  161. package/dist/core/check/surface.js +29 -0
  162. package/dist/core/check/surface.js.map +1 -1
  163. package/dist/core/emit/canonical.d.ts +22 -8
  164. package/dist/core/emit/canonical.d.ts.map +1 -1
  165. package/dist/core/emit/canonical.js +67 -6
  166. package/dist/core/emit/canonical.js.map +1 -1
  167. package/dist/core/emit/defaults.d.ts +35 -16
  168. package/dist/core/emit/defaults.d.ts.map +1 -1
  169. package/dist/core/emit/defaults.js +66 -0
  170. package/dist/core/emit/defaults.js.map +1 -1
  171. package/dist/core/emit/index.d.ts +2 -0
  172. package/dist/core/emit/index.d.ts.map +1 -1
  173. package/dist/core/emit/index.js +2 -0
  174. package/dist/core/emit/index.js.map +1 -1
  175. package/dist/core/engine/arithmetic.d.ts +6 -25
  176. package/dist/core/engine/arithmetic.d.ts.map +1 -1
  177. package/dist/core/engine/arithmetic.js +48 -3
  178. package/dist/core/engine/arithmetic.js.map +1 -1
  179. package/dist/core/engine/index.d.ts +2 -2
  180. package/dist/core/engine/index.d.ts.map +1 -1
  181. package/dist/core/engine/index.js +2 -2
  182. package/dist/core/engine/index.js.map +1 -1
  183. package/dist/core/engine/library/arrays.d.ts.map +1 -1
  184. package/dist/core/engine/library/arrays.js +8 -2
  185. package/dist/core/engine/library/arrays.js.map +1 -1
  186. package/dist/core/engine/library/code-points.d.ts +40 -0
  187. package/dist/core/engine/library/code-points.d.ts.map +1 -0
  188. package/dist/core/engine/library/code-points.js +74 -0
  189. package/dist/core/engine/library/code-points.js.map +1 -0
  190. package/dist/core/engine/library/index.d.ts +5 -0
  191. package/dist/core/engine/library/index.d.ts.map +1 -1
  192. package/dist/core/engine/library/index.js +5 -0
  193. package/dist/core/engine/library/index.js.map +1 -1
  194. package/dist/core/engine/library/text.d.ts.map +1 -1
  195. package/dist/core/engine/library/text.js +51 -34
  196. package/dist/core/engine/library/text.js.map +1 -1
  197. package/dist/core/engine/load.d.ts +35 -1
  198. package/dist/core/engine/load.d.ts.map +1 -1
  199. package/dist/core/engine/load.js +63 -0
  200. package/dist/core/engine/load.js.map +1 -1
  201. package/dist/core/engine/verify-tables.d.ts.map +1 -1
  202. package/dist/core/engine/verify-tables.js +41 -0
  203. package/dist/core/engine/verify-tables.js.map +1 -1
  204. package/dist/core/index.d.ts +43 -4
  205. package/dist/core/index.d.ts.map +1 -1
  206. package/dist/core/index.js +23 -2
  207. package/dist/core/index.js.map +1 -1
  208. package/dist/core/stdlib/index.d.ts +1 -1
  209. package/dist/core/stdlib/index.d.ts.map +1 -1
  210. package/dist/core/stdlib/index.js +1 -1
  211. package/dist/core/stdlib/index.js.map +1 -1
  212. package/dist/core/stdlib/maths/index.d.ts +1 -1
  213. package/dist/core/stdlib/maths/index.d.ts.map +1 -1
  214. package/dist/core/stdlib/maths/index.js +1 -1
  215. package/dist/core/stdlib/maths/index.js.map +1 -1
  216. package/dist/core/stdlib/maths/rounding.d.ts +5 -0
  217. package/dist/core/stdlib/maths/rounding.d.ts.map +1 -1
  218. package/dist/core/stdlib/maths/rounding.js +19 -1
  219. package/dist/core/stdlib/maths/rounding.js.map +1 -1
  220. package/dist/core/version/version.generated.d.ts +1 -1
  221. package/dist/core/version/version.generated.js +1 -1
  222. package/dist/editor/complete.d.ts +40 -0
  223. package/dist/editor/complete.d.ts.map +1 -0
  224. package/dist/editor/complete.js +206 -0
  225. package/dist/editor/complete.js.map +1 -0
  226. package/dist/editor/diagnose.d.ts +18 -0
  227. package/dist/editor/diagnose.d.ts.map +1 -0
  228. package/dist/editor/diagnose.js +70 -0
  229. package/dist/editor/diagnose.js.map +1 -0
  230. package/dist/editor/format.d.ts +11 -0
  231. package/dist/editor/format.d.ts.map +1 -0
  232. package/dist/editor/format.js +50 -0
  233. package/dist/editor/format.js.map +1 -0
  234. package/dist/editor/highlight.d.ts +28 -0
  235. package/dist/editor/highlight.d.ts.map +1 -0
  236. package/dist/editor/highlight.js +65 -0
  237. package/dist/editor/highlight.js.map +1 -0
  238. package/dist/editor/hover.d.ts +40 -0
  239. package/dist/editor/hover.d.ts.map +1 -0
  240. package/dist/editor/hover.js +147 -0
  241. package/dist/editor/hover.js.map +1 -0
  242. package/dist/editor/index.d.ts +60 -0
  243. package/dist/editor/index.d.ts.map +1 -0
  244. package/dist/editor/index.js +7 -0
  245. package/dist/editor/index.js.map +1 -0
  246. package/dist/editor/kinds.d.ts +27 -0
  247. package/dist/editor/kinds.d.ts.map +1 -0
  248. package/dist/editor/kinds.js +118 -0
  249. package/dist/editor/kinds.js.map +1 -0
  250. package/dist/editor/layout.d.ts +47 -0
  251. package/dist/editor/layout.d.ts.map +1 -0
  252. package/dist/editor/layout.js +135 -0
  253. package/dist/editor/layout.js.map +1 -0
  254. package/dist/editor/manifest.d.ts +47 -0
  255. package/dist/editor/manifest.d.ts.map +1 -0
  256. package/dist/editor/manifest.js +93 -0
  257. package/dist/editor/manifest.js.map +1 -0
  258. package/dist/editor/reading.d.ts +41 -0
  259. package/dist/editor/reading.d.ts.map +1 -0
  260. package/dist/editor/reading.js +51 -0
  261. package/dist/editor/reading.js.map +1 -0
  262. package/dist/editor/scan.d.ts +26 -0
  263. package/dist/editor/scan.d.ts.map +1 -0
  264. package/dist/editor/scan.js +139 -0
  265. package/dist/editor/scan.js.map +1 -0
  266. package/dist/editor/scope.d.ts +19 -0
  267. package/dist/editor/scope.d.ts.map +1 -0
  268. package/dist/editor/scope.js +99 -0
  269. package/dist/editor/scope.js.map +1 -0
  270. package/dist/editor/signature.d.ts +38 -0
  271. package/dist/editor/signature.d.ts.map +1 -0
  272. package/dist/editor/signature.js +112 -0
  273. package/dist/editor/signature.js.map +1 -0
  274. package/dist/editor/site.d.ts +46 -0
  275. package/dist/editor/site.d.ts.map +1 -0
  276. package/dist/editor/site.js +184 -0
  277. package/dist/editor/site.js.map +1 -0
  278. package/dist/editor/spacing.d.ts +29 -0
  279. package/dist/editor/spacing.d.ts.map +1 -0
  280. package/dist/editor/spacing.js +116 -0
  281. package/dist/editor/spacing.js.map +1 -0
  282. package/package.json +42 -3
  283. package/spec/README.md +2 -1
  284. package/spec/errors.json +141 -1
  285. package/src/adapters/charts/driving.ts +109 -0
  286. package/src/adapters/charts/run.ts +120 -28
  287. package/src/adapters/charts/surfaces.ts +17 -0
  288. package/src/adapters/charts/tables.ts +88 -0
  289. package/src/adapters/charts/venue.ts +132 -0
  290. package/src/adapters/codemirror/commands.ts +53 -0
  291. package/src/adapters/codemirror/completion.ts +74 -0
  292. package/src/adapters/codemirror/contract.ts +156 -0
  293. package/src/adapters/codemirror/index.ts +66 -0
  294. package/src/adapters/codemirror/lint.ts +66 -0
  295. package/src/adapters/codemirror/positions.ts +79 -0
  296. package/src/adapters/codemirror/stream.ts +87 -0
  297. package/src/adapters/codemirror/tokens.ts +64 -0
  298. package/src/adapters/codemirror/tooltips.ts +113 -0
  299. package/src/core/accounting/analysis.ts +188 -0
  300. package/src/core/accounting/charges.ts +452 -0
  301. package/src/core/accounting/equity.ts +314 -0
  302. package/src/core/accounting/index.ts +51 -0
  303. package/src/core/accounting/markers.ts +95 -0
  304. package/src/core/accounting/monthly.ts +137 -0
  305. package/src/core/accounting/report.ts +94 -0
  306. package/src/core/accounting/shapes.ts +73 -0
  307. package/src/core/accounting/statistics.ts +387 -0
  308. package/src/core/accounting/trades.ts +313 -0
  309. package/src/core/backtest/case.ts +395 -0
  310. package/src/core/backtest/compare.ts +246 -0
  311. package/src/core/backtest/declaration.ts +97 -0
  312. package/src/core/backtest/deliver.ts +161 -0
  313. package/src/core/backtest/drive.ts +468 -0
  314. package/src/core/backtest/index.ts +64 -0
  315. package/src/core/backtest/range.ts +137 -0
  316. package/src/core/backtest/record.ts +470 -0
  317. package/src/core/backtest/replay.ts +168 -0
  318. package/src/core/backtest/resting.ts +125 -0
  319. package/src/core/backtest/settings.ts +280 -0
  320. package/src/core/backtest/simulate.ts +499 -0
  321. package/src/core/catalogue/catalogue.generated.ts +6 -0
  322. package/src/core/catalogue/values.generated.ts +6 -0
  323. package/src/core/check/index.ts +3 -0
  324. package/src/core/check/library-orders.ts +2 -2
  325. package/src/core/check/library-prose.generated.ts +367 -0
  326. package/src/core/check/surface.ts +34 -0
  327. package/src/core/emit/canonical.ts +67 -9
  328. package/src/core/emit/defaults.ts +70 -0
  329. package/src/core/emit/index.ts +2 -0
  330. package/src/core/engine/arithmetic.ts +23 -3
  331. package/src/core/engine/index.ts +2 -2
  332. package/src/core/engine/library/arrays.ts +8 -2
  333. package/src/core/engine/library/code-points.ts +73 -0
  334. package/src/core/engine/library/index.ts +6 -0
  335. package/src/core/engine/library/text.ts +53 -35
  336. package/src/core/engine/load.ts +84 -2
  337. package/src/core/engine/verify-tables.ts +43 -0
  338. package/src/core/index.ts +100 -2
  339. package/src/core/stdlib/index.ts +1 -0
  340. package/src/core/stdlib/maths/index.ts +1 -0
  341. package/src/core/stdlib/maths/rounding.ts +23 -1
  342. package/src/core/version/version.generated.ts +1 -1
  343. package/src/editor/complete.ts +266 -0
  344. package/src/editor/diagnose.ts +71 -0
  345. package/src/editor/format.ts +85 -0
  346. package/src/editor/highlight.ts +110 -0
  347. package/src/editor/hover.ts +199 -0
  348. package/src/editor/index.ts +65 -0
  349. package/src/editor/kinds.ts +157 -0
  350. package/src/editor/layout.ts +189 -0
  351. package/src/editor/manifest.ts +119 -0
  352. package/src/editor/reading.ts +69 -0
  353. package/src/editor/scan.ts +162 -0
  354. package/src/editor/scope.ts +122 -0
  355. package/src/editor/signature.ts +150 -0
  356. package/src/editor/site.ts +233 -0
  357. package/src/editor/spacing.ts +132 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The destination a strategy runs against when a chart draws one.
3
+ *
4
+ * **Why a chart needs one at all.** A strategy asks a destination to do things
5
+ * and folds its position from what comes back. Handed no destination it is
6
+ * refused at load with OS6006, and handed one that answers nothing it runs but
7
+ * never learns it is in a position: every `close()` closes nothing, every entry
8
+ * is allowed again on the next signal, and the study draws a strategy that is
9
+ * wrong about itself while looking entirely normal. Measured on a stop and
10
+ * reverse script, that shape produced five buys and no sells.
11
+ *
12
+ * **It is the backtest's own venue, not a second one.** `Simulator` is what
13
+ * `backtest()` runs against, so a chart running a strategy through it fills the
14
+ * same orders at the same prices as the report the trader ran a moment ago. A
15
+ * venue written here to be simpler would be a second answer to "what would this
16
+ * have filled at", and the two would disagree the first time either changed:
17
+ * the chart would draw one set of trades and the report would list another.
18
+ *
19
+ * **What a chart supplies and what it invents.** The bars, the instrument and
20
+ * the declaration are the chart's own and are passed through. Money is not: a
21
+ * chart draws a strategy, it does not report one, so the currency, the point
22
+ * value and the rounding are stated here as the neutral values that make the
23
+ * accounting a no-op. Nothing a chart draws reads them, and a chart that
24
+ * guessed a point value would put a wrong profit in front of somebody.
25
+ *
26
+ * **Nothing here decides when a fill happens.** The declaration does, through
27
+ * `fillOn`, and the venue reads it. A market order priced at the next bar's
28
+ * open is known at that open; one that rested and traded inside a bar is known
29
+ * once the bar is complete. Both are what a venue could have told anybody at
30
+ * the time, and that is the whole reason the frames arrive between bars rather
31
+ * than inside the execution that sent them.
32
+ */
33
+
34
+ import { DEFAULT_FILL, Simulator, declarationOf } from '../../core/backtest/index.js';
35
+ import type { RecordedBar } from '../../core/backtest/index.js';
36
+ import type { CompiledProgram } from '../../core/emit/index.js';
37
+ import type { Instrument, OrderFrame, ResolvedInput, RoutedEffect } from '../../core/engine/index.js';
38
+ import type { ChartBar } from './contract.js';
39
+
40
+ /**
41
+ * Whether this program needs a destination before it can run at all.
42
+ *
43
+ * Read from the program's own declared capabilities rather than from whether
44
+ * its source said `strategy()`, because `requires` is the same fact the engine
45
+ * tests at load. A strategy that places no orders needs none, and anything that
46
+ * does need one is refused without it.
47
+ */
48
+ export function needsVenue(program: CompiledProgram): boolean {
49
+ return program.requires.includes('orders');
50
+ }
51
+
52
+ /** One chart bar as the venue reads it. */
53
+ function recorded(bar: ChartBar): RecordedBar {
54
+ return {
55
+ time: bar.time,
56
+ open: bar.open,
57
+ high: bar.high,
58
+ low: bar.low,
59
+ close: bar.close,
60
+ volume: bar.volume ?? null,
61
+ oi: bar.oi ?? null,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * The venue for one chart run, or nothing where the program does not need one.
67
+ *
68
+ * `inputs` are the ones the engine resolved at load, because a declaration
69
+ * field may be written as an `input()`: a script whose `fillOn` comes from a
70
+ * setting would otherwise be filled the way its source happened to be written
71
+ * rather than the way the trader is running it.
72
+ */
73
+ export function venueFor(
74
+ program: CompiledProgram,
75
+ bars: readonly ChartBar[],
76
+ inputs: readonly ResolvedInput[],
77
+ instrument: Instrument | undefined,
78
+ ): Simulator | null {
79
+ if (!needsVenue(program)) return null;
80
+
81
+ const declared = declarationOf(program, inputs);
82
+
83
+ return new Simulator({
84
+ bars: bars.map(recorded),
85
+ contract: {
86
+ symbol: instrument?.symbol ?? null,
87
+ exchange: instrument?.exchange ?? null,
88
+ // Neutral money. A chart draws a strategy and does not report one, so
89
+ // nothing it puts on screen reads these; stating a point value a host
90
+ // never gave would be inventing the size of somebody's position.
91
+ currency: declared.currency,
92
+ tickSize: instrument?.tickSize ?? null,
93
+ lotSize: null,
94
+ pointValue: 1,
95
+ digits: 2,
96
+ },
97
+ fill: DEFAULT_FILL,
98
+ // The declaration's own, so slippage is applied the way the backtest
99
+ // applies it. Ticks, against the instrument's tick size.
100
+ slippageTicks: declared.slippage,
101
+ fillOn: declared.fillOn,
102
+ qtyType: declared.qtyType,
103
+ });
104
+ }
105
+
106
+ /**
107
+ * A venue that can be asked for frames, held across a chart's recomputes.
108
+ *
109
+ * The bars a `Simulator` prices against are the ones it was built with, so a
110
+ * chart that has grown a bar needs a venue that knows about it. `extend` is how
111
+ * the tail path says so without rebuilding the orders it is already holding.
112
+ */
113
+ export interface HeldVenue {
114
+ readonly venue: Simulator;
115
+ /** Frames the venue answered for a bar, waiting to be delivered before the next. */
116
+ pending: readonly OrderFrame[];
117
+ }
118
+
119
+ /** What a venue has to say after one bar, which the next bar's engine reads. */
120
+ export function answersFor(held: HeldVenue, barIndex: number): void {
121
+ held.pending = held.venue.framesFor(barIndex);
122
+ }
123
+
124
+ /** The route an engine is loaded with, pointing at a venue built after it. */
125
+ export function routeInto(holder: { current: Simulator | null }): (effect: RoutedEffect, bar: number) => void {
126
+ // Late bound on purpose. The route is handed to `load`, and the venue cannot
127
+ // be built until `load` has resolved the inputs the declaration reads. The
128
+ // route is only ever called from inside an execution, which is after both.
129
+ return (effect: RoutedEffect, bar: number) => {
130
+ holder.current?.route(effect, bar);
131
+ };
132
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Laying a script out again, as an editor command.
3
+ *
4
+ * `format` is the tier's, and the guarantee it carries is the one that makes a
5
+ * format button safe to put in front of somebody holding a position: laying a
6
+ * script out again cannot change what it computes. Every example and every gate
7
+ * script in the repository is formatted, both texts compiled, and the compiled
8
+ * programs compared field for field; and every call checks itself against the
9
+ * lexer, so a rule that is wrong returns the source untouched rather than a
10
+ * changed program.
11
+ *
12
+ * The whole document is replaced rather than a minimal edit computed. It is the
13
+ * honest version: a diff would have to be trusted to be equivalent to the
14
+ * replacement, and a formatter that moved a line somewhere unintended would do
15
+ * it silently. The cursor is kept where it was in the new text, clamped, because
16
+ * a format that sends the caret to the top of the file is a format nobody
17
+ * presses twice.
18
+ *
19
+ * **A document with two character line endings comes back with one character
20
+ * ones**, because that is what `language.md` 3.1 normalises a file to before
21
+ * anything reads it and what every offset in this tier indexes. It is the one
22
+ * change to a file's bytes that formatting makes without being asked, and it is
23
+ * recorded in `spec/editor-narrowings.json`.
24
+ */
25
+ import { format } from '../../editor/index.js';
26
+ import type { EditorView } from './contract.js';
27
+ import { documentOf, normalisedOffset } from './positions.js';
28
+
29
+ /**
30
+ * Lays the document out again, and says whether anything changed.
31
+ *
32
+ * ```ts
33
+ * keymap.of([{ key: "Shift-Alt-f", run: formatDocument }])
34
+ * ```
35
+ *
36
+ * False where the document is already laid out, which is what an editor command
37
+ * returns when it has nothing to do: the key press then falls through to
38
+ * whatever else is bound to it rather than being swallowed.
39
+ */
40
+ export function formatDocument(view: EditorView): boolean {
41
+ const raw = view.state.doc.toString();
42
+ const held = documentOf(raw);
43
+ const laid = format(held.text);
44
+ if (laid === raw) return false;
45
+
46
+ const at = normalisedOffset(held, view.state.selection.main.head);
47
+ view.dispatch({
48
+ changes: { from: 0, to: view.state.doc.length, insert: laid },
49
+ selection: { anchor: Math.min(at, laid.length) },
50
+ scrollIntoView: true,
51
+ });
52
+ return true;
53
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Completions, as the editor's completion source.
3
+ *
4
+ * The whole of the work is done by `complete`, which reads the standard library
5
+ * manifest, the checker's bindings and the error catalogue. What is here is the
6
+ * translation: the editor wants a range to replace, a list of rows, and a note
7
+ * about whether the list is already filtered.
8
+ *
9
+ * **The list is already filtered**, by the word the writer has typed, compared
10
+ * exactly, because the language's names are case sensitive. So `filter` is false
11
+ * and the editor is told not to filter it again: its own matcher is fuzzy and
12
+ * case insensitive, and running it over a list that has already answered the
13
+ * question would put `EMA` in front of somebody as though it compiled.
14
+ *
15
+ * **A planned call is shown and marked.** `complete` carries the catalogue's own
16
+ * OS2020 sentence on a planned row, so that is what goes in the row's expanded
17
+ * note, and the row is pushed to the bottom of the list with the editor's own
18
+ * boost. A host that would rather not show them at all drops them with one
19
+ * field, which is why they are marked rather than filtered here.
20
+ */
21
+ import { complete } from '../../editor/index.js';
22
+ import type { Completion } from '../../editor/index.js';
23
+ import type {
24
+ EditorCompletion,
25
+ EditorCompletionContext,
26
+ EditorCompletionResult,
27
+ } from './contract.js';
28
+ import { COMPLETION_TYPES } from './tokens.js';
29
+
30
+ /** As far down the list as the editor will move a row. */
31
+ const PLANNED_BOOST = -99;
32
+
33
+ function rowOf(one: Completion): EditorCompletion {
34
+ const info = one.refusal ?? one.summary;
35
+ const type = COMPLETION_TYPES[one.kind];
36
+ return {
37
+ label: one.label,
38
+ ...(type === undefined ? {} : { type }),
39
+ detail: one.detail,
40
+ ...(info === undefined ? {} : { info }),
41
+ ...(one.insert === one.label ? {} : { apply: one.insert }),
42
+ ...(one.planned ? { boost: PLANNED_BOOST } : {}),
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Every completion for the position the editor is asking about.
48
+ *
49
+ * ```ts
50
+ * autocompletion({ override: [openscriptCompletion] })
51
+ * ```
52
+ *
53
+ * Nothing comes back for an empty list rather than an empty list, because the
54
+ * editor reads the two differently: an empty result closes the panel and null
55
+ * lets another source answer, and a host with a snippet source of its own should
56
+ * keep it.
57
+ */
58
+ export function openscriptCompletion(
59
+ context: EditorCompletionContext,
60
+ ): EditorCompletionResult | null {
61
+ const rows = complete(context.state.doc.toString(), context.pos);
62
+ // The first row carries the range every row replaces, so an empty list and a
63
+ // list whose first row is missing are one answer: nothing. Two guards for that
64
+ // would be one guard and a line no test can reach.
65
+ const first = rows[0];
66
+ if (first === undefined) return null;
67
+
68
+ return {
69
+ from: first.replace.offset,
70
+ to: first.replace.offset + first.replace.length,
71
+ options: rows.map(rowOf),
72
+ filter: false,
73
+ };
74
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The editor component's own shapes, as this adapter targets them.
3
+ *
4
+ * **Why they are declared here rather than imported.** The editor component is a
5
+ * peer dependency: a host that wants a text area installs it, and a host that
6
+ * wants only the language must not be made to. This repository therefore does
7
+ * not install it, cannot type check against it, and would fail its own layering
8
+ * check the moment a file here imported it. It is the same decision the chart
9
+ * adapter took, for the same reason, and `src/adapters/charts/contract.ts` says
10
+ * it at length.
11
+ *
12
+ * So the shapes this produces values for are declared below and the host's own
13
+ * build is where the two meet. Each one is a one line check that costs a host
14
+ * nothing and fails to compile if this file has drifted:
15
+ *
16
+ * ```ts
17
+ * import type { StreamParser } from "<the editor's language package>";
18
+ * import type { CompletionSource } from "<the editor's completion package>";
19
+ * import type { Diagnostic } from "<the editor's lint package>";
20
+ *
21
+ * const parser: StreamParser<unknown> = openscriptStream;
22
+ * const source: CompletionSource = openscriptCompletion;
23
+ * const linter: (view: EditorView) => Diagnostic[] = openscriptLint;
24
+ * ```
25
+ *
26
+ * **That line catches a value of the wrong shape and not an argument of the
27
+ * wrong shape.** A method's parameters are compared in both directions, so a
28
+ * hook declared here as taking something the editor never passes still assigns.
29
+ * That is the failure the chart adapter has actually made, so every member below
30
+ * takes the editor's own argument, spelled as the editor spells it, and every
31
+ * test drives each one with the argument the editor passes rather than with one
32
+ * of its own.
33
+ *
34
+ * **A style name is not checked by any of those lines.** The names this adapter
35
+ * returns from `token` are looked up by the editor in its own table of
36
+ * highlighting tags at run time, and an unknown one is a warning on a console
37
+ * rather than a compile error. That is why the whole mapping is one exported
38
+ * record a host can replace in a line, and why it is recorded in
39
+ * `spec/editor-narrowings.json` as the one thing here a type cannot hold.
40
+ */
41
+
42
+ /**
43
+ * One line of the document, as the editor's tokenizer sees it.
44
+ *
45
+ * The editor hands a tokenizer a line at a time and expects `pos` to have moved
46
+ * forward when `token` returns. Only the members this adapter reads are
47
+ * declared: a narrower shape is still assignable from the editor's wider one.
48
+ */
49
+ export interface EditorStringStream {
50
+ /** The line's text, without its line ending. */
51
+ readonly string: string;
52
+ /** Where the tokenizer has got to, which this adapter moves. */
53
+ pos: number;
54
+ /** Where the current token began. */
55
+ readonly start: number;
56
+ /** Whether `pos` is at the end of the line. */
57
+ eol(): boolean;
58
+ /** Whether `pos` is at the start of the line. */
59
+ sol(): boolean;
60
+ }
61
+
62
+ /**
63
+ * A tokenizer the editor drives one line at a time.
64
+ *
65
+ * `token` returns a style name, or null for text with no style of its own. The
66
+ * state is the tokenizer's to define, and the editor copies it at line
67
+ * boundaries so that it can restart mid document, which is why `copyState` is
68
+ * here.
69
+ */
70
+ export interface EditorStreamParser<State> {
71
+ readonly name?: string;
72
+ startState?(indentUnit: number): State;
73
+ token(stream: EditorStringStream, state: State): string | null;
74
+ copyState?(state: State): State;
75
+ blankLine?(state: State, indentUnit: number): void;
76
+ readonly languageData?: Readonly<Record<string, unknown>>;
77
+ }
78
+
79
+ /** The document, which the editor holds as a rope rather than as a string. */
80
+ export interface EditorText {
81
+ readonly length: number;
82
+ toString(): string;
83
+ }
84
+
85
+ /** The part of the editor's state this adapter reads. */
86
+ export interface EditorState {
87
+ readonly doc: EditorText;
88
+ readonly selection: {
89
+ readonly main: { readonly head: number; readonly from: number; readonly to: number };
90
+ };
91
+ }
92
+
93
+ /** The part of the editor's view this adapter reads and dispatches to. */
94
+ export interface EditorView {
95
+ readonly state: EditorState;
96
+ dispatch(transaction: {
97
+ readonly changes: { readonly from: number; readonly to: number; readonly insert: string };
98
+ readonly selection?: { readonly anchor: number };
99
+ readonly scrollIntoView?: boolean;
100
+ }): void;
101
+ }
102
+
103
+ /** What the editor hands a completion source. */
104
+ export interface EditorCompletionContext {
105
+ readonly state: EditorState;
106
+ readonly pos: number;
107
+ /** Whether the writer asked for completions rather than merely typing. */
108
+ readonly explicit: boolean;
109
+ }
110
+
111
+ /** One row of the editor's completion list. */
112
+ export interface EditorCompletion {
113
+ readonly label: string;
114
+ /** The editor's own name for the icon beside a row. */
115
+ readonly type?: string;
116
+ readonly detail?: string;
117
+ readonly info?: string;
118
+ /** The text to insert, where it differs from the label. */
119
+ readonly apply?: string;
120
+ /** Moves a row up or down the list, from -99 to 99. */
121
+ readonly boost?: number;
122
+ }
123
+
124
+ export interface EditorCompletionResult {
125
+ readonly from: number;
126
+ readonly to?: number;
127
+ readonly options: readonly EditorCompletion[];
128
+ /** False when the list is already filtered, which this adapter's is. */
129
+ readonly filter?: boolean;
130
+ }
131
+
132
+ /** One squiggle in the editor's lint panel and gutter. */
133
+ export interface EditorDiagnostic {
134
+ readonly from: number;
135
+ readonly to: number;
136
+ readonly severity: 'hint' | 'info' | 'warning' | 'error';
137
+ readonly message: string;
138
+ /** Where the diagnostic came from, shown beside the message. */
139
+ readonly source?: string;
140
+ readonly actions?: readonly {
141
+ readonly name: string;
142
+ apply(view: EditorView, from: number, to: number): void;
143
+ }[];
144
+ }
145
+
146
+ /** What the editor shows over the text, and the element it shows. */
147
+ export interface EditorTooltipView {
148
+ readonly dom: unknown;
149
+ }
150
+
151
+ export interface EditorTooltip {
152
+ readonly pos: number;
153
+ readonly end?: number;
154
+ readonly above?: boolean;
155
+ create(view: EditorView): EditorTooltipView;
156
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The editor adapter: the language intelligence, wired into a text component.
3
+ *
4
+ * This and the chart adapter are the only modules in the repository allowed to
5
+ * know two worlds at once, and it is the piece a platform with its own editor
6
+ * replaces rather than the piece it patches. Everything under `src/editor` stays
7
+ * ignorant of any component, and the layering check is what keeps it that way.
8
+ *
9
+ * **The editor component is a peer dependency, and nothing here imports it.** A
10
+ * package that pulled a text component into everyone's install would defeat the
11
+ * point of a language a platform can take on its own, so the shapes this
12
+ * produces are declared in `contract.ts` and the host's own build is where the
13
+ * two are compared, in one line per piece. See that file for the lines. The
14
+ * entry point resolves and every function here runs in an install that holds no
15
+ * editor at all, which `scripts/check-entry-points.mjs` proves on every build.
16
+ *
17
+ * Six functions in, five pieces out:
18
+ *
19
+ * openscriptStream highlighting, a line at a time
20
+ * openscriptCompletion the completion list
21
+ * openscriptLint errors as you type, with their fixes
22
+ * openscriptHoverTooltip what the thing under the pointer is
23
+ * openscriptSignatureTooltip the call being written
24
+ * formatDocument lay the script out again
25
+ *
26
+ * The two tooltips take the markup as a parameter and have no default for it, so
27
+ * nothing in this package names a browser global at any tier. A host that wants
28
+ * none of this calls the six functions itself and draws its own. That is not the fallback, it is the supported path: what a host gives up
29
+ * by taking these instead is recorded in `spec/editor-narrowings.json`, which is
30
+ * three lines long and worth reading before choosing.
31
+ */
32
+ export type {
33
+ EditorCompletion,
34
+ EditorCompletionContext,
35
+ EditorCompletionResult,
36
+ EditorDiagnostic,
37
+ EditorState,
38
+ EditorStreamParser,
39
+ EditorStringStream,
40
+ EditorText,
41
+ EditorTooltip,
42
+ EditorTooltipView,
43
+ EditorView,
44
+ } from './contract.js';
45
+
46
+ export { COMPLETION_TYPES, HIGHLIGHT_TOKENS } from './tokens.js';
47
+
48
+ export type { StreamState } from './stream.js';
49
+ export { openscriptStream } from './stream.js';
50
+
51
+ export { openscriptCompletion } from './completion.js';
52
+
53
+ export { diagnosticsFor, openscriptLint } from './lint.js';
54
+
55
+ export type { Renderer } from './tooltips.js';
56
+ export {
57
+ hoverLines,
58
+ openscriptHoverTooltip,
59
+ openscriptSignatureTooltip,
60
+ signatureLines,
61
+ } from './tooltips.js';
62
+
63
+ export { formatDocument } from './commands.js';
64
+
65
+ export type { Document } from './positions.js';
66
+ export { documentOf, normalisedOffset } from './positions.js';
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Errors as you type, as the editor's linter.
3
+ *
4
+ * The diagnostics are the compiler's own, whole: `diagnose` runs the lexer, the
5
+ * parser, the checker and the emitter, so what a writer sees beside their line
6
+ * is exactly what a compile produces and nothing new can appear when they press
7
+ * apply. Every message and every fix came from the error catalogue that the
8
+ * documentation site is generated from.
9
+ *
10
+ * Three decisions worth stating, because each one is visible to a trader.
11
+ *
12
+ * **The fix is shown.** A `Diagnostic` carries a message saying what is wrong and
13
+ * a fix saying what to do, and the fix is the half that is worth reading. The
14
+ * editor's panel shows one string, so the two are joined with a line break
15
+ * rather than the fix being dropped.
16
+ *
17
+ * **The code is the source.** The editor shows a diagnostic's source beside its
18
+ * message, and a code is the one part of a diagnostic this project promises never
19
+ * to change, so it is what a trader quotes and what a support page is indexed by.
20
+ *
21
+ * **A zero width span is widened by one character.** The compiler points at a
22
+ * position for a mistake that is an absence, a missing bracket among them, and a
23
+ * squiggle of no width is a squiggle nobody sees.
24
+ */
25
+ import { endOffset } from '../../core/index.js';
26
+ import type { Diagnostic } from '../../core/index.js';
27
+ import { diagnose } from '../../editor/index.js';
28
+ import type { EditorDiagnostic, EditorView } from './contract.js';
29
+ import { documentOf } from './positions.js';
30
+
31
+ /** What the editor prints beside a message, which is the code and nothing else. */
32
+ function sourceOf(one: Diagnostic): string {
33
+ return one.code;
34
+ }
35
+
36
+ /** Every diagnostic of a document, in the editor's own shape. */
37
+ export function diagnosticsFor(raw: string): readonly EditorDiagnostic[] {
38
+ const held = documentOf(raw);
39
+ return diagnose(held.text).map((one) => {
40
+ const from = held.at(one.span.offset);
41
+ const to = held.at(endOffset(one.span));
42
+ return {
43
+ from,
44
+ to: to > from ? to : from + 1,
45
+ severity: one.severity,
46
+ message: `${one.message}\n${one.fix}`,
47
+ source: sourceOf(one),
48
+ };
49
+ });
50
+ }
51
+
52
+ /**
53
+ * The linter a host wires in one line.
54
+ *
55
+ * ```ts
56
+ * linter(openscriptLint)
57
+ * ```
58
+ *
59
+ * It is synchronous because it is fast enough to be: a finished ninety line
60
+ * study compiles in a third of a millisecond and the same file with a bracket
61
+ * left open in about a millisecond, both with a budget in the benchmark suite.
62
+ * The editor's own linter already debounces, so nothing here does it twice.
63
+ */
64
+ export function openscriptLint(view: EditorView): readonly EditorDiagnostic[] {
65
+ return diagnosticsFor(view.state.doc.toString());
66
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Offsets, translated between the document the editor holds and the text the
3
+ * compiler read.
4
+ *
5
+ * `language.md` 3.1 normalises a file before anything reads it: a byte order
6
+ * mark is dropped and every CRLF becomes LF. Every span the compiler produces
7
+ * therefore indexes the normalised text, which `docs/integrating/the-editor-half.md`
8
+ * tells a host plainly and expects it to handle.
9
+ *
10
+ * A drop-in cannot expect it. A document written on a machine that ends its
11
+ * lines with two characters is an ordinary document, the editor holds it exactly
12
+ * as it is, and an adapter that handed the editor a span from the normalised
13
+ * text would put every squiggle on a file's fiftieth line fifty characters to
14
+ * the left. That is the kind of defect that is reported as "the underline is in
15
+ * the wrong place" and survives for months.
16
+ *
17
+ * So the two are mapped. The map is built only when the document is not already
18
+ * normalised, which is the common case and costs a comparison; when it is built
19
+ * it is one walk of the text.
20
+ */
21
+ import { normaliseSource } from '../../core/index.js';
22
+
23
+ const BYTE_ORDER_MARK = 0xfeff;
24
+ const CARRIAGE_RETURN = 13;
25
+ const LINE_FEED = 10;
26
+
27
+ /** The document as the compiler reads it, and where each of its offsets sits. */
28
+ export interface Document {
29
+ /** The normalised text: what `diagnose`, `complete` and the rest are given. */
30
+ readonly text: string;
31
+ /**
32
+ * The offset in the editor's own document that a normalised offset names.
33
+ *
34
+ * The identity where the document is already normalised, which is what a
35
+ * document written on a machine that ends its lines with one character is.
36
+ */
37
+ at(offset: number): number;
38
+ }
39
+
40
+ export function documentOf(raw: string): Document {
41
+ const text = normaliseSource(raw);
42
+ if (text === raw) return { text, at: (offset) => offset };
43
+
44
+ // One entry per normalised offset, plus the one past its end, so that the end
45
+ // of a span at the end of the file maps as readily as its start.
46
+ const map = new Array<number>(text.length + 1);
47
+ let normalised = 0;
48
+ let held = raw.charCodeAt(0) === BYTE_ORDER_MARK ? 1 : 0;
49
+
50
+ while (normalised <= text.length) {
51
+ map[normalised] = held;
52
+ if (normalised === text.length) break;
53
+ if (raw.charCodeAt(held) === CARRIAGE_RETURN && raw.charCodeAt(held + 1) === LINE_FEED) {
54
+ held += 1;
55
+ }
56
+ held += 1;
57
+ normalised += 1;
58
+ }
59
+
60
+ return {
61
+ text,
62
+ at: (offset) => map[Math.min(Math.max(offset, 0), text.length)] ?? offset,
63
+ };
64
+ }
65
+
66
+ /** The normalised offset an editor offset names, which is the other direction. */
67
+ export function normalisedOffset(held: Document, raw: number): number {
68
+ // Walked rather than inverted, because the inverse of the map above is only
69
+ // needed at one position per call and building a second array to answer one
70
+ // question would cost a file's length for a comparison's worth of work.
71
+ let low = 0;
72
+ let high = held.text.length;
73
+ while (low < high) {
74
+ const middle = (low + high + 1) >> 1;
75
+ if (held.at(middle) <= raw) low = middle;
76
+ else high = middle - 1;
77
+ }
78
+ return low;
79
+ }