openalgo-script 0.2.0 → 0.4.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 (276) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +65 -31
  3. package/dist/adapters/charts/surfaces.d.ts +17 -0
  4. package/dist/adapters/charts/surfaces.d.ts.map +1 -1
  5. package/dist/adapters/charts/tables.js +82 -0
  6. package/dist/adapters/charts/tables.js.map +1 -1
  7. package/dist/adapters/codemirror/commands.d.ts +14 -0
  8. package/dist/adapters/codemirror/commands.d.ts.map +1 -0
  9. package/dist/adapters/codemirror/commands.js +52 -0
  10. package/dist/adapters/codemirror/commands.js.map +1 -0
  11. package/dist/adapters/codemirror/completion.d.ts +15 -0
  12. package/dist/adapters/codemirror/completion.d.ts.map +1 -0
  13. package/dist/adapters/codemirror/completion.js +64 -0
  14. package/dist/adapters/codemirror/completion.js.map +1 -0
  15. package/dist/adapters/codemirror/contract.d.ts +156 -0
  16. package/dist/adapters/codemirror/contract.d.ts.map +1 -0
  17. package/dist/adapters/codemirror/contract.js +42 -0
  18. package/dist/adapters/codemirror/contract.js.map +1 -0
  19. package/dist/adapters/codemirror/index.d.ts +43 -0
  20. package/dist/adapters/codemirror/index.d.ts.map +1 -0
  21. package/dist/adapters/codemirror/index.js +8 -0
  22. package/dist/adapters/codemirror/index.js.map +1 -0
  23. package/dist/adapters/codemirror/lint.d.ts +17 -0
  24. package/dist/adapters/codemirror/lint.d.ts.map +1 -0
  25. package/dist/adapters/codemirror/lint.js +62 -0
  26. package/dist/adapters/codemirror/lint.js.map +1 -0
  27. package/dist/adapters/codemirror/positions.d.ts +16 -0
  28. package/dist/adapters/codemirror/positions.d.ts.map +1 -0
  29. package/dist/adapters/codemirror/positions.js +65 -0
  30. package/dist/adapters/codemirror/positions.js.map +1 -0
  31. package/dist/adapters/codemirror/stream.d.ts +23 -0
  32. package/dist/adapters/codemirror/stream.d.ts.map +1 -0
  33. package/dist/adapters/codemirror/stream.js +70 -0
  34. package/dist/adapters/codemirror/stream.js.map +1 -0
  35. package/dist/adapters/codemirror/tokens.d.ts +44 -0
  36. package/dist/adapters/codemirror/tokens.d.ts.map +1 -0
  37. package/dist/adapters/codemirror/tokens.js +34 -0
  38. package/dist/adapters/codemirror/tokens.js.map +1 -0
  39. package/dist/adapters/codemirror/tooltips.d.ts +35 -0
  40. package/dist/adapters/codemirror/tooltips.d.ts.map +1 -0
  41. package/dist/adapters/codemirror/tooltips.js +108 -0
  42. package/dist/adapters/codemirror/tooltips.js.map +1 -0
  43. package/dist/core/accounting/charges.d.ts +136 -0
  44. package/dist/core/accounting/charges.d.ts.map +1 -0
  45. package/dist/core/accounting/charges.js +362 -0
  46. package/dist/core/accounting/charges.js.map +1 -0
  47. package/dist/core/accounting/equity.d.ts +158 -0
  48. package/dist/core/accounting/equity.d.ts.map +1 -0
  49. package/dist/core/accounting/equity.js +155 -0
  50. package/dist/core/accounting/equity.js.map +1 -0
  51. package/dist/core/accounting/index.d.ts +44 -0
  52. package/dist/core/accounting/index.d.ts.map +1 -0
  53. package/dist/core/accounting/index.js +36 -0
  54. package/dist/core/accounting/index.js.map +1 -0
  55. package/dist/core/accounting/markers.d.ts +43 -0
  56. package/dist/core/accounting/markers.d.ts.map +1 -0
  57. package/dist/core/accounting/markers.js +64 -0
  58. package/dist/core/accounting/markers.js.map +1 -0
  59. package/dist/core/accounting/monthly.d.ts +52 -0
  60. package/dist/core/accounting/monthly.d.ts.map +1 -0
  61. package/dist/core/accounting/monthly.js +99 -0
  62. package/dist/core/accounting/monthly.js.map +1 -0
  63. package/dist/core/accounting/report.d.ts +38 -0
  64. package/dist/core/accounting/report.d.ts.map +1 -0
  65. package/dist/core/accounting/report.js +63 -0
  66. package/dist/core/accounting/report.js.map +1 -0
  67. package/dist/core/accounting/shapes.d.ts +70 -0
  68. package/dist/core/accounting/shapes.d.ts.map +1 -0
  69. package/dist/core/accounting/shapes.js +21 -0
  70. package/dist/core/accounting/shapes.js.map +1 -0
  71. package/dist/core/accounting/statistics.d.ts +75 -0
  72. package/dist/core/accounting/statistics.d.ts.map +1 -0
  73. package/dist/core/accounting/statistics.js +246 -0
  74. package/dist/core/accounting/statistics.js.map +1 -0
  75. package/dist/core/accounting/trades.d.ts +118 -0
  76. package/dist/core/accounting/trades.d.ts.map +1 -0
  77. package/dist/core/accounting/trades.js +186 -0
  78. package/dist/core/accounting/trades.js.map +1 -0
  79. package/dist/core/backtest/compare.d.ts +38 -0
  80. package/dist/core/backtest/compare.d.ts.map +1 -0
  81. package/dist/core/backtest/compare.js +189 -0
  82. package/dist/core/backtest/compare.js.map +1 -0
  83. package/dist/core/backtest/declaration.d.ts +52 -0
  84. package/dist/core/backtest/declaration.d.ts.map +1 -0
  85. package/dist/core/backtest/declaration.js +48 -0
  86. package/dist/core/backtest/declaration.js.map +1 -0
  87. package/dist/core/backtest/drive.d.ts +29 -0
  88. package/dist/core/backtest/drive.d.ts.map +1 -0
  89. package/dist/core/backtest/drive.js +262 -0
  90. package/dist/core/backtest/drive.js.map +1 -0
  91. package/dist/core/backtest/index.d.ts +46 -0
  92. package/dist/core/backtest/index.d.ts.map +1 -0
  93. package/dist/core/backtest/index.js +36 -0
  94. package/dist/core/backtest/index.js.map +1 -0
  95. package/dist/core/backtest/range.d.ts +84 -0
  96. package/dist/core/backtest/range.d.ts.map +1 -0
  97. package/dist/core/backtest/range.js +90 -0
  98. package/dist/core/backtest/range.js.map +1 -0
  99. package/dist/core/backtest/record.d.ts +238 -0
  100. package/dist/core/backtest/record.d.ts.map +1 -0
  101. package/dist/core/backtest/record.js +176 -0
  102. package/dist/core/backtest/record.js.map +1 -0
  103. package/dist/core/backtest/replay.d.ts +48 -0
  104. package/dist/core/backtest/replay.d.ts.map +1 -0
  105. package/dist/core/backtest/replay.js +127 -0
  106. package/dist/core/backtest/replay.js.map +1 -0
  107. package/dist/core/backtest/resting.d.ts +62 -0
  108. package/dist/core/backtest/resting.d.ts.map +1 -0
  109. package/dist/core/backtest/resting.js +59 -0
  110. package/dist/core/backtest/resting.js.map +1 -0
  111. package/dist/core/backtest/settings.d.ts +117 -0
  112. package/dist/core/backtest/settings.d.ts.map +1 -0
  113. package/dist/core/backtest/settings.js +207 -0
  114. package/dist/core/backtest/settings.js.map +1 -0
  115. package/dist/core/backtest/simulate.d.ts +146 -0
  116. package/dist/core/backtest/simulate.d.ts.map +1 -0
  117. package/dist/core/backtest/simulate.js +217 -0
  118. package/dist/core/backtest/simulate.js.map +1 -0
  119. package/dist/core/catalogue/catalogue.generated.d.ts +66 -0
  120. package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
  121. package/dist/core/catalogue/catalogue.generated.js +6 -0
  122. package/dist/core/catalogue/catalogue.generated.js.map +1 -1
  123. package/dist/core/catalogue/values.generated.d.ts +24 -0
  124. package/dist/core/catalogue/values.generated.d.ts.map +1 -1
  125. package/dist/core/check/index.d.ts +2 -1
  126. package/dist/core/check/index.d.ts.map +1 -1
  127. package/dist/core/check/index.js +1 -1
  128. package/dist/core/check/index.js.map +1 -1
  129. package/dist/core/check/library-prose.generated.d.ts +16 -0
  130. package/dist/core/check/library-prose.generated.d.ts.map +1 -0
  131. package/dist/core/check/library-prose.generated.js +353 -0
  132. package/dist/core/check/library-prose.generated.js.map +1 -0
  133. package/dist/core/check/surface.d.ts +24 -0
  134. package/dist/core/check/surface.d.ts.map +1 -1
  135. package/dist/core/check/surface.js +29 -0
  136. package/dist/core/check/surface.js.map +1 -1
  137. package/dist/core/emit/defaults.d.ts +35 -16
  138. package/dist/core/emit/defaults.d.ts.map +1 -1
  139. package/dist/core/emit/defaults.js +66 -0
  140. package/dist/core/emit/defaults.js.map +1 -1
  141. package/dist/core/emit/index.d.ts +2 -0
  142. package/dist/core/emit/index.d.ts.map +1 -1
  143. package/dist/core/emit/index.js +2 -0
  144. package/dist/core/emit/index.js.map +1 -1
  145. package/dist/core/engine/index.d.ts +1 -1
  146. package/dist/core/engine/index.d.ts.map +1 -1
  147. package/dist/core/engine/index.js +1 -1
  148. package/dist/core/engine/index.js.map +1 -1
  149. package/dist/core/engine/load.d.ts +15 -1
  150. package/dist/core/engine/load.d.ts.map +1 -1
  151. package/dist/core/engine/load.js +1 -0
  152. package/dist/core/engine/load.js.map +1 -1
  153. package/dist/core/index.d.ts +37 -3
  154. package/dist/core/index.d.ts.map +1 -1
  155. package/dist/core/index.js +17 -1
  156. package/dist/core/index.js.map +1 -1
  157. package/dist/core/version/version.generated.d.ts +1 -1
  158. package/dist/core/version/version.generated.js +1 -1
  159. package/dist/editor/complete.d.ts +40 -0
  160. package/dist/editor/complete.d.ts.map +1 -0
  161. package/dist/editor/complete.js +206 -0
  162. package/dist/editor/complete.js.map +1 -0
  163. package/dist/editor/diagnose.d.ts +18 -0
  164. package/dist/editor/diagnose.d.ts.map +1 -0
  165. package/dist/editor/diagnose.js +70 -0
  166. package/dist/editor/diagnose.js.map +1 -0
  167. package/dist/editor/format.d.ts +11 -0
  168. package/dist/editor/format.d.ts.map +1 -0
  169. package/dist/editor/format.js +50 -0
  170. package/dist/editor/format.js.map +1 -0
  171. package/dist/editor/highlight.d.ts +28 -0
  172. package/dist/editor/highlight.d.ts.map +1 -0
  173. package/dist/editor/highlight.js +65 -0
  174. package/dist/editor/highlight.js.map +1 -0
  175. package/dist/editor/hover.d.ts +40 -0
  176. package/dist/editor/hover.d.ts.map +1 -0
  177. package/dist/editor/hover.js +147 -0
  178. package/dist/editor/hover.js.map +1 -0
  179. package/dist/editor/index.d.ts +60 -0
  180. package/dist/editor/index.d.ts.map +1 -0
  181. package/dist/editor/index.js +7 -0
  182. package/dist/editor/index.js.map +1 -0
  183. package/dist/editor/kinds.d.ts +27 -0
  184. package/dist/editor/kinds.d.ts.map +1 -0
  185. package/dist/editor/kinds.js +118 -0
  186. package/dist/editor/kinds.js.map +1 -0
  187. package/dist/editor/layout.d.ts +47 -0
  188. package/dist/editor/layout.d.ts.map +1 -0
  189. package/dist/editor/layout.js +135 -0
  190. package/dist/editor/layout.js.map +1 -0
  191. package/dist/editor/manifest.d.ts +47 -0
  192. package/dist/editor/manifest.d.ts.map +1 -0
  193. package/dist/editor/manifest.js +93 -0
  194. package/dist/editor/manifest.js.map +1 -0
  195. package/dist/editor/reading.d.ts +41 -0
  196. package/dist/editor/reading.d.ts.map +1 -0
  197. package/dist/editor/reading.js +51 -0
  198. package/dist/editor/reading.js.map +1 -0
  199. package/dist/editor/scan.d.ts +26 -0
  200. package/dist/editor/scan.d.ts.map +1 -0
  201. package/dist/editor/scan.js +139 -0
  202. package/dist/editor/scan.js.map +1 -0
  203. package/dist/editor/scope.d.ts +19 -0
  204. package/dist/editor/scope.d.ts.map +1 -0
  205. package/dist/editor/scope.js +99 -0
  206. package/dist/editor/scope.js.map +1 -0
  207. package/dist/editor/signature.d.ts +38 -0
  208. package/dist/editor/signature.d.ts.map +1 -0
  209. package/dist/editor/signature.js +112 -0
  210. package/dist/editor/signature.js.map +1 -0
  211. package/dist/editor/site.d.ts +46 -0
  212. package/dist/editor/site.d.ts.map +1 -0
  213. package/dist/editor/site.js +184 -0
  214. package/dist/editor/site.js.map +1 -0
  215. package/dist/editor/spacing.d.ts +29 -0
  216. package/dist/editor/spacing.d.ts.map +1 -0
  217. package/dist/editor/spacing.js +116 -0
  218. package/dist/editor/spacing.js.map +1 -0
  219. package/package.json +30 -3
  220. package/spec/errors.json +140 -0
  221. package/src/adapters/charts/surfaces.ts +17 -0
  222. package/src/adapters/charts/tables.ts +88 -0
  223. package/src/adapters/codemirror/commands.ts +53 -0
  224. package/src/adapters/codemirror/completion.ts +74 -0
  225. package/src/adapters/codemirror/contract.ts +156 -0
  226. package/src/adapters/codemirror/index.ts +66 -0
  227. package/src/adapters/codemirror/lint.ts +66 -0
  228. package/src/adapters/codemirror/positions.ts +79 -0
  229. package/src/adapters/codemirror/stream.ts +87 -0
  230. package/src/adapters/codemirror/tokens.ts +64 -0
  231. package/src/adapters/codemirror/tooltips.ts +113 -0
  232. package/src/core/accounting/charges.ts +452 -0
  233. package/src/core/accounting/equity.ts +276 -0
  234. package/src/core/accounting/index.ts +49 -0
  235. package/src/core/accounting/markers.ts +95 -0
  236. package/src/core/accounting/monthly.ts +137 -0
  237. package/src/core/accounting/report.ts +89 -0
  238. package/src/core/accounting/shapes.ts +73 -0
  239. package/src/core/accounting/statistics.ts +350 -0
  240. package/src/core/accounting/trades.ts +313 -0
  241. package/src/core/backtest/compare.ts +244 -0
  242. package/src/core/backtest/declaration.ts +97 -0
  243. package/src/core/backtest/drive.ts +364 -0
  244. package/src/core/backtest/index.ts +52 -0
  245. package/src/core/backtest/range.ts +137 -0
  246. package/src/core/backtest/record.ts +341 -0
  247. package/src/core/backtest/replay.ts +158 -0
  248. package/src/core/backtest/resting.ts +125 -0
  249. package/src/core/backtest/settings.ts +280 -0
  250. package/src/core/backtest/simulate.ts +304 -0
  251. package/src/core/catalogue/catalogue.generated.ts +6 -0
  252. package/src/core/catalogue/values.generated.ts +6 -0
  253. package/src/core/check/index.ts +3 -0
  254. package/src/core/check/library-prose.generated.ts +367 -0
  255. package/src/core/check/surface.ts +34 -0
  256. package/src/core/emit/defaults.ts +70 -0
  257. package/src/core/emit/index.ts +2 -0
  258. package/src/core/engine/index.ts +1 -1
  259. package/src/core/engine/load.ts +16 -2
  260. package/src/core/index.ts +85 -1
  261. package/src/core/version/version.generated.ts +1 -1
  262. package/src/editor/complete.ts +266 -0
  263. package/src/editor/diagnose.ts +71 -0
  264. package/src/editor/format.ts +85 -0
  265. package/src/editor/highlight.ts +110 -0
  266. package/src/editor/hover.ts +199 -0
  267. package/src/editor/index.ts +65 -0
  268. package/src/editor/kinds.ts +157 -0
  269. package/src/editor/layout.ts +189 -0
  270. package/src/editor/manifest.ts +119 -0
  271. package/src/editor/reading.ts +69 -0
  272. package/src/editor/scan.ts +162 -0
  273. package/src/editor/scope.ts +122 -0
  274. package/src/editor/signature.ts +150 -0
  275. package/src/editor/site.ts +233 -0
  276. package/src/editor/spacing.ts +132 -0
package/src/core/index.ts CHANGED
@@ -170,6 +170,7 @@ export type {
170
170
  InputKind,
171
171
  LibraryEntry,
172
172
  LibraryParameter,
173
+ LibraryProse,
173
174
  ObjectKind,
174
175
  RequestMode,
175
176
  Storage,
@@ -192,6 +193,7 @@ export {
192
193
  atLeastBar,
193
194
  check,
194
195
  delayed,
196
+ describedNames,
195
197
  earlier,
196
198
  elementOf,
197
199
  isLibraryName,
@@ -205,6 +207,7 @@ export {
205
207
  membersOf,
206
208
  sameType,
207
209
  seriesOf,
210
+ proseFor,
208
211
  typeText,
209
212
  } from './check/index.js';
210
213
  export { REQUEST_NAMES } from './check/index.js';
@@ -223,9 +226,21 @@ export { COMPILED_FORMAT_VERSION, VERSION } from './version/index.js';
223
226
  */
224
227
  export { emit } from './emit/index.js';
225
228
  export type { EmitOptions, EmitResult } from './emit/index.js';
226
- export type { CompiledProgram } from './emit/index.js';
229
+ export type { Colour, CompiledProgram } from './emit/index.js';
227
230
  export { canonicalise, programHash, sourceHash } from './emit/index.js';
228
231
 
232
+ /**
233
+ * Two answers the emitter holds that a tool built on the language needs.
234
+ *
235
+ * A declaration call's defaults and the channels a colour name denotes are both
236
+ * facts the compiler applies and neither is in the library manifest, so a tier
237
+ * above this one that read the manifest alone would show a writer nothing where
238
+ * the compiler has a value. They are here rather than reached for behind the
239
+ * emitter's door, and `scripts/check-defaults.mjs` holds the first of them to
240
+ * what `stdlib.md` prints.
241
+ */
242
+ export { DECLARATION_CALLS, declarationDefaultText, namedColour } from './emit/index.js';
243
+
229
244
  /**
230
245
  * The engine: a program in, one bar at a time, values out.
231
246
  *
@@ -267,3 +282,72 @@ export { isAbsent } from './engine/index.js';
267
282
  export type { Value } from './engine/index.js';
268
283
  export { LANGUAGE_VERSIONS, capabilitiesFor, verify } from './engine/index.js';
269
284
  export type { VerifyOptions, VerifyResult } from './engine/index.js';
285
+
286
+ /**
287
+ * The money, and the run it is folded from (`stdlib.md` 17.1, 17.7).
288
+ *
289
+ * Two modules rather than one, and the line between them is the design. The
290
+ * accounting shapes are arithmetic over portable data and know nothing about an
291
+ * engine, so a stored record can be reported again with no engine present and
292
+ * the engine can one day call the same arithmetic with no second implementation
293
+ * to disagree with. The backtest shapes are what one run was carried out under
294
+ * and what it produced, and a record of one is a conformance case a second
295
+ * engine can be handed whole.
296
+ *
297
+ * They are here rather than behind an entry point of their own because they are
298
+ * the same layer: a host that compiles and runs a program is the host that
299
+ * reports what it did.
300
+ */
301
+ export type {
302
+ BarMark,
303
+ ChargeBase,
304
+ ChargeBreakdown,
305
+ ChargeLine,
306
+ ChargeSchedule,
307
+ ChargeSide,
308
+ Contract,
309
+ EquityPoint,
310
+ Money,
311
+ MonthlyReturn,
312
+ RecordedFill,
313
+ Report,
314
+ Summary,
315
+ Trade,
316
+ TradeMarker,
317
+ } from './accounting/index.js';
318
+ export { chargeFor, scheduleFromDeclaration, scheduleProblem } from './accounting/index.js';
319
+ export { markersOf, monthlyOver, reportOf } from './accounting/index.js';
320
+ export { tradesOf } from './accounting/index.js';
321
+ export { equityOver, summaryOf } from './accounting/index.js';
322
+ export {
323
+ DEFAULT_FILL,
324
+ EXACT,
325
+ WHOLE_RANGE,
326
+ RECORD_VERSION,
327
+ backtest,
328
+ barsHash,
329
+ checkSettings,
330
+ compareRuns,
331
+ recordFromJson,
332
+ recordToJson,
333
+ recordOf,
334
+ replay,
335
+ rerun,
336
+ runBytes,
337
+ settingsFor,
338
+ windowFor,
339
+ } from './backtest/index.js';
340
+ export type { BacktestResult, ReplayResult, ReportWindow } from './backtest/index.js';
341
+ export type {
342
+ BacktestSettings,
343
+ BarsInRecord,
344
+ DateRange,
345
+ FillPolicy,
346
+ RecordedBar,
347
+ RecordedDiagnostic,
348
+ RecordedFrame,
349
+ RecordedOrder,
350
+ RunComparison,
351
+ RunRecord,
352
+ Tolerance,
353
+ } from './backtest/index.js';
@@ -4,7 +4,7 @@
4
4
  // spec/compiled-program.md, so neither can drift from the thing it names.
5
5
 
6
6
  /** This build's package version. The release refuses to publish unless the tag, the manifest and this agree. */
7
- export const VERSION = "0.2.0";
7
+ export const VERSION = "0.4.0";
8
8
 
9
9
  /**
10
10
  * The compiled program format this compiler emits.
@@ -0,0 +1,266 @@
1
+ /**
2
+ * `complete`: what may be written at a position.
3
+ *
4
+ * Four kinds of answer, and every one of them is read out of something the
5
+ * compiler already holds:
6
+ *
7
+ * the members of a namespace `membersOf`, after `draw.`
8
+ * the named arguments of the the manifest's parameters for the call the
9
+ * call being written brackets say the cursor is inside
10
+ * the file's own names the checker's bindings, filtered by the two
11
+ * scope rules of `scope.ts`
12
+ * the library's names `libraryNames`, the same index the checker
13
+ * resolves against and the example check reads
14
+ * its globals from
15
+ *
16
+ * **A hand written list of function names is the exact failure this project
17
+ * keeps removing.** It would be right on the day it was written and wrong at the
18
+ * next release, and nobody would report it, because a missing completion is
19
+ * indistinguishable from a completion that has not loaded yet. Nothing below
20
+ * names a single function of the language.
21
+ *
22
+ * ## A planned call is offered, and is marked as one
23
+ *
24
+ * `stdlib.md` lists calls that are named and not implemented, so that the gap in
25
+ * the surface is visible rather than looking like an oversight. Writing one is
26
+ * OS2020.
27
+ *
28
+ * Three answers were possible: leave them out, offer them as though they worked,
29
+ * or offer them marked. Leaving them out is the worst of the three, because the
30
+ * writer then types the name from the documentation, gets no completion, and
31
+ * learns nothing about why; offering them unmarked walks somebody into OS2020
32
+ * with a plot that will not compile.
33
+ *
34
+ * So they are offered, they sort after everything a script may write today, and
35
+ * each one carries `refusal`, which is **the catalogue's own OS2020 message
36
+ * filled with that name**. A host greys the row, shows the sentence, or drops
37
+ * the row with one field, and the sentence it shows is the same sentence the
38
+ * compiler would produce, because it came from the same catalogue.
39
+ */
40
+ import {
41
+ NAMESPACES,
42
+ entryFor,
43
+ fillTemplate,
44
+ isNamespace,
45
+ libraryEntries,
46
+ libraryNames,
47
+ membersOf,
48
+ proseFor,
49
+ typeText,
50
+ } from '../core/index.js';
51
+ import type { Binding, LibraryEntry, Span } from '../core/index.js';
52
+ import { entryAt, parametersOf, signatureTextOf } from './manifest.js';
53
+ import { readChecked } from './reading.js';
54
+ import { inScopeAt } from './scope.js';
55
+ import { siteAt } from './site.js';
56
+
57
+ /**
58
+ * What a completion is, which decides the icon a host puts beside it.
59
+ *
60
+ * `value` is a library name read without brackets, `close` and `aqua` among
61
+ * them; `function` is one that is called. The two are the manifest's own
62
+ * `callable`, not a guess from the spelling.
63
+ */
64
+ export type CompletionKind =
65
+ | 'function'
66
+ | 'value'
67
+ | 'namespace'
68
+ | 'member'
69
+ | 'variable'
70
+ | 'argument';
71
+
72
+ export interface Completion {
73
+ /** What the row reads as: the name, or the parameter for a named argument. */
74
+ readonly label: string;
75
+ /**
76
+ * What a host puts into the text, which is not always the label.
77
+ *
78
+ * A named argument is written `len = `, so accepting the row leaves the caret
79
+ * where the value goes rather than beside a label with no equals sign.
80
+ */
81
+ readonly insert: string;
82
+ readonly kind: CompletionKind;
83
+ /** The signature or the type, spelled by the manifest and the checker. */
84
+ readonly detail: string;
85
+ /** The line the specification gives it, where it gives one. */
86
+ readonly summary: string | undefined;
87
+ /** Named in the library and not implemented in this version: OS2020. */
88
+ readonly planned: boolean;
89
+ /** The catalogue's OS2020 message for this name, on a planned row and no other. */
90
+ readonly refusal: string | undefined;
91
+ /** The text the row replaces, which is the word being typed or nothing. */
92
+ readonly replace: Span;
93
+ }
94
+
95
+ /** The order a host shows them in, decided here so two hosts agree. */
96
+ const ORDER: Readonly<Record<CompletionKind, number>> = {
97
+ argument: 0,
98
+ variable: 1,
99
+ function: 2,
100
+ member: 2,
101
+ value: 3,
102
+ namespace: 4,
103
+ };
104
+
105
+ /**
106
+ * The order, and the two things that decide it.
107
+ *
108
+ * Named arguments keep the order the signature states them in, because that is
109
+ * the order they are written in and an alphabetical list of them would put
110
+ * `len` before `src` in a call whose first argument is the source. Everything
111
+ * else is sorted: a planned call after every call a script may write today,
112
+ * then the kinds above, then the name.
113
+ *
114
+ * The name comparison has no locale in it. `compiled-program.md` 8.4 forbids
115
+ * one, and it is the same rule here for the same reason: two machines set
116
+ * differently would order a list differently, and the machine set differently is
117
+ * the one nobody is looking at.
118
+ */
119
+ function ordered(completions: readonly Completion[]): readonly Completion[] {
120
+ const written = completions.filter((one) => one.kind === 'argument');
121
+ const rest = completions.filter((one) => one.kind !== 'argument');
122
+ rest.sort((a, b) => {
123
+ if (a.planned !== b.planned) return a.planned ? 1 : -1;
124
+ const kind = (ORDER[a.kind] ?? 0) - (ORDER[b.kind] ?? 0);
125
+ if (kind !== 0) return kind;
126
+ if (a.label === b.label) return 0;
127
+ return a.label < b.label ? -1 : 1;
128
+ });
129
+ return [...written, ...rest];
130
+ }
131
+
132
+ /** The OS2020 sentence, filled with the name, for a call that is not here yet. */
133
+ function refusalFor(name: string): string {
134
+ return fillTemplate(entryFor('OS2020').message, { name });
135
+ }
136
+
137
+ function fromEntry(
138
+ entry: LibraryEntry,
139
+ label: string,
140
+ kind: CompletionKind,
141
+ replace: Span,
142
+ ): Completion {
143
+ return {
144
+ label,
145
+ insert: label,
146
+ kind,
147
+ detail: signatureTextOf(entry),
148
+ summary: proseFor(entry.name)?.summary,
149
+ planned: entry.planned,
150
+ refusal: entry.planned ? refusalFor(entry.name) : undefined,
151
+ replace,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * One row per library name, taking the first signature for the detail.
157
+ *
158
+ * A name with several signatures is one row and not several: `clear` is one
159
+ * thing to write and two things to write it about, and a list that showed it
160
+ * twice would be a list about the manifest rather than about the language.
161
+ * `signature` is what tells a writer which one they are in once they have
162
+ * opened the bracket.
163
+ */
164
+ function libraryRow(name: string, replace: Span): Completion | undefined {
165
+ const entries = libraryEntries(name);
166
+ const entry = entries[0];
167
+ if (entry === undefined) return undefined;
168
+ return fromEntry(entry, name, entry.callable ? 'function' : 'value', replace);
169
+ }
170
+
171
+ /** A name the file declared, with the type and the storage the checker gave it. */
172
+ function declaredRow(binding: Binding, replace: Span): Completion {
173
+ const kind: CompletionKind = binding.kind === 'function' ? 'function' : 'variable';
174
+ const persistence = binding.persistence === 'none' ? '' : `${binding.persistence} `;
175
+ return {
176
+ label: binding.name,
177
+ insert: binding.name,
178
+ kind,
179
+ detail: `${persistence}${binding.name}: ${typeText(binding.type)}`,
180
+ summary: undefined,
181
+ planned: false,
182
+ refusal: undefined,
183
+ replace,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Everything that may be written at an offset, most useful first.
189
+ *
190
+ * Rows are filtered by the word already typed, which is compared exactly:
191
+ * `language.md` makes names case sensitive, so `EM` is not a start of `ema` and
192
+ * offering it would be offering a name that does not compile.
193
+ */
194
+ export function complete(source: string, offset: number): readonly Completion[] {
195
+ const { file, tokens, checked } = readChecked(source);
196
+ const site = siteAt(file, tokens, offset);
197
+ const at = site.replace.offset;
198
+
199
+ const rows: Completion[] = [];
200
+
201
+ // After a dot, the only thing that may be written is a member of the
202
+ // namespace, and only where the name before the dot is one. A dotted read of
203
+ // anything else is not a member read at all in version 1 (`language.md` 15.2),
204
+ // so there is nothing to offer rather than the whole library offered wrongly.
205
+ if (site.afterDot) {
206
+ const namespace = site.namespace;
207
+ if (namespace !== undefined && isNamespace(namespace)) {
208
+ for (const member of membersOf(namespace)) {
209
+ const entry = libraryEntries(`${namespace}.${member}`)[0];
210
+ if (entry !== undefined) rows.push(fromEntry(entry, member, 'member', site.replace));
211
+ }
212
+ }
213
+ return ordered(rows.filter((row) => row.label.startsWith(site.word)));
214
+ }
215
+
216
+ // The named arguments of the call being written, where one is being written
217
+ // and the label of this argument has not been decided yet.
218
+ const call = site.call;
219
+ if (call !== undefined && call.label === undefined) {
220
+ const entry = entryAt(call.name, call.argument + 1);
221
+ if (entry !== undefined) {
222
+ for (const parameter of parametersOf(entry)) {
223
+ if (call.labels.includes(parameter.name)) continue;
224
+ rows.push({
225
+ label: parameter.name,
226
+ insert: `${parameter.name} = `,
227
+ kind: 'argument',
228
+ detail:
229
+ `${parameter.name}: ${parameter.type}` +
230
+ (parameter.defaultText === undefined ? '' : ` = ${parameter.defaultText}`),
231
+ summary: undefined,
232
+ planned: entry.planned,
233
+ refusal: entry.planned ? refusalFor(entry.name) : undefined,
234
+ replace: site.replace,
235
+ });
236
+ }
237
+ }
238
+ }
239
+
240
+ for (const binding of inScopeAt(checked, at)) {
241
+ rows.push(declaredRow(binding, site.replace));
242
+ }
243
+
244
+ for (const name of libraryNames()) {
245
+ // A dotted name is reached through its namespace and not written whole from
246
+ // nothing, so the namespace is the row and `membersOf` is the rest.
247
+ if (name.includes('.')) continue;
248
+ const row = libraryRow(name, site.replace);
249
+ if (row !== undefined) rows.push(row);
250
+ }
251
+
252
+ for (const namespace of NAMESPACES) {
253
+ rows.push({
254
+ label: namespace,
255
+ insert: `${namespace}.`,
256
+ kind: 'namespace',
257
+ detail: `${namespace}.`,
258
+ summary: undefined,
259
+ planned: false,
260
+ refusal: undefined,
261
+ replace: site.replace,
262
+ });
263
+ }
264
+
265
+ return ordered(rows.filter((row) => row.label.startsWith(site.word)));
266
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `diagnose`: source text in, everything the compiler has to say about it out.
3
+ *
4
+ * This is the one a trader sees most. It runs on a debounce while somebody
5
+ * types, so two things about it are decisions rather than details: what it
6
+ * costs, and what it does with a file that does not parse.
7
+ *
8
+ * ## It is the whole compile, on purpose
9
+ *
10
+ * Lexer, parser, checker, emitter. Not a subset, and not a faster partial
11
+ * compiler written for the editor, because a second implementation of the
12
+ * language is the thing this project refuses to have: the editor is the compiler
13
+ * wearing a different hat.
14
+ *
15
+ * It matters concretely as well as in principle. There are codes a script can
16
+ * carry that the emitter raises and nothing before it does, so an editor that
17
+ * stopped after the checker would show a clean file and then refuse it the
18
+ * moment somebody pressed apply. What a host sees here is exactly what a compile
19
+ * produces, so nothing new can appear at apply, and a test holds that by taking
20
+ * a file whose only fault the emitter is the one to find.
21
+ *
22
+ * ## What it costs
23
+ *
24
+ * A full compile of a ninety line study is a third of a millisecond, and the
25
+ * same file with a bracket left open mid way through, which is what a file looks
26
+ * like the moment somebody types one, is about a millisecond. Both are in the
27
+ * benchmark suite with a budget, as `diagnose-heavy` and `diagnose-typing`, so a
28
+ * change that makes the editor cost ten times what it costs today fails the
29
+ * build rather than being felt by a trader.
30
+ *
31
+ * That is why there is no partial compiler here. At this price, one is not worth
32
+ * the second implementation it would cost; if a script ever arrives that makes
33
+ * it worth one, the number to beat is in the benchmark table rather than in
34
+ * somebody's impression.
35
+ *
36
+ * ## A file that does not parse
37
+ *
38
+ * That is the normal state of a file being typed into, so it is the case this
39
+ * has to be good at rather than the case it survives. Every stage of this
40
+ * compiler recovers and reports instead of throwing: a lexical error costs its
41
+ * own character, a statement that will not parse costs its own line, a name that
42
+ * does not resolve becomes `unknown` and the lines around it are still checked.
43
+ * So a half typed file produces the diagnostics its finished lines have earned,
44
+ * plus the one about the line being typed, and nothing here needs a try block to
45
+ * make that true. `tests/editor/diagnose.test.ts` puts a corpus of malformed
46
+ * files through it, including text that is not the language at all.
47
+ */
48
+ import { check, emit } from '../core/index.js';
49
+ import type { Diagnostic } from '../core/index.js';
50
+ import { readTree } from './reading.js';
51
+
52
+ /**
53
+ * Every diagnostic a compile of this source produces, in the order a reader
54
+ * walks the file.
55
+ *
56
+ * Each one carries its code, its span, its message and its fix, because that is
57
+ * what a `Diagnostic` is: the stage that raised it supplied the code and the
58
+ * span, and everything a person reads came from the error catalogue. There is no
59
+ * editor-shaped diagnostic type here for the same reason there is no editor
60
+ * compiler. A host that renders one in a panel and a terminal that renders the
61
+ * same one under a caret are looking at the same record.
62
+ *
63
+ * The order is the bag's own: by offset, then by the span's length, then by code
64
+ * point of the code. It depends on nothing outside the file, so two machines
65
+ * list them identically.
66
+ */
67
+ export function diagnose(source: string): readonly Diagnostic[] {
68
+ const { file, bag, script } = readTree(source);
69
+ emit(file, check(file, script, bag), bag, {});
70
+ return bag.ordered();
71
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `format`: source text in, the same program in its canonical layout out.
3
+ *
4
+ * ## The rule that makes a format button safe to press
5
+ *
6
+ * **Formatting a source must not change what it means.** A trader presses this
7
+ * on a script that is holding a position. If a reprint can change a number, the
8
+ * button is a hazard whatever else it does for the diff.
9
+ *
10
+ * That is not asserted here, it is proved twice over:
11
+ *
12
+ * - **Per call, before this function returns.** The output is lexed again and
13
+ * compared with the tokens that went in, token for token. If anything moved,
14
+ * the source is returned untouched. The parser reads nothing but the token
15
+ * stream, so an identical stream is an identical tree, and an identical tree
16
+ * is an identical program. A formatter that is wrong about a rule therefore
17
+ * fails by doing nothing, which is the only acceptable way for it to fail.
18
+ * - **Over a corpus, in the suite.** `tests/editor/format.test.ts` formats every
19
+ * example in the repository and every script the phase gates run, compiles
20
+ * both texts, and asserts the compiled programs are identical. That is the
21
+ * test that says the formatter works; the check above is what says it is safe
22
+ * when it does not.
23
+ *
24
+ * ## A source that does not parse
25
+ *
26
+ * It is returned unchanged. Not reformatted as best it can be, and not partly
27
+ * reformatted: unchanged, exactly, including its line endings.
28
+ *
29
+ * The reason is what a diagnostic from either stage means. The lexer reports a
30
+ * character it could not read and emits no token for it, so a reprint from the
31
+ * token stream would delete it, and a reader would watch a character disappear
32
+ * from their file. The parser reports a line it could not read and recovers, so
33
+ * the tree describes a program nobody wrote and the three questions the spacing
34
+ * rules ask of it have no trustworthy answers. Neither is a file to lay out. A
35
+ * host formats on demand rather than on every keystroke, and a file mid-edit is
36
+ * simply left alone until it reads.
37
+ */
38
+ import type { Token } from '../core/index.js';
39
+ import { layOut } from './layout.js';
40
+ import { readTree } from './reading.js';
41
+ import { scan } from './scan.js';
42
+ import { shapesOf } from './spacing.js';
43
+
44
+ /**
45
+ * Whether two token streams are the same stream.
46
+ *
47
+ * Kind and text for everything, except the indent token, whose text is the
48
+ * leading whitespace of the line it opens and is exactly what a formatter is
49
+ * allowed to change. It is the only token that carries layout as text: a
50
+ * newline, a dedent and the end of the file have none.
51
+ *
52
+ * Spans are not compared, and must not be: every one of them moves when a line
53
+ * is re-indented, which is the whole point of the exercise.
54
+ */
55
+ function sameStream(before: readonly Token[], after: readonly Token[]): boolean {
56
+ if (before.length !== after.length) return false;
57
+ for (const [at, token] of before.entries()) {
58
+ const other = after[at];
59
+ if (other === undefined || other.kind !== token.kind) return false;
60
+ if (token.kind !== 'indent' && other.text !== token.text) return false;
61
+ }
62
+ return true;
63
+ }
64
+
65
+ /**
66
+ * The canonical layout of a source, or the source itself where it cannot be
67
+ * given one safely.
68
+ *
69
+ * The output always ends in a single line ending and uses LF, because a file is
70
+ * normalised before anything reads it (`language.md` 3.1). A host holding CRLF
71
+ * text gets LF back, which is the same file to the compiler and to every other
72
+ * part of this project.
73
+ */
74
+ export function format(source: string): string {
75
+ const held = readTree(source);
76
+ if (!held.bag.isEmpty) return source;
77
+
78
+ const shapes = shapesOf(held.script, held.tokens);
79
+ const out = layOut(held.file, held.tokens, scan(held.file, held.tokens), shapes);
80
+
81
+ const again = readTree(out);
82
+ if (!again.bag.isEmpty) return source;
83
+ if (!sameStream(held.tokens, again.tokens)) return source;
84
+ return out;
85
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `highlight`: source text in, painted spans out.
3
+ *
4
+ * It is the real lexer and nothing else. A word added to the language is
5
+ * coloured on the day it lexes, because the only thing between the lexer and a
6
+ * colour here is a table lookup in `kinds.ts` that asks the language's own
7
+ * tables which sort of thing a token is.
8
+ *
9
+ * ## Two shapes, and why both
10
+ *
11
+ * A host paints a document one of two ways and there is no third: it draws over
12
+ * the whole text, or it renders a line at a time. So both shapes are here, and
13
+ * the second is derived from the first rather than scanned again.
14
+ *
15
+ * `highlight` is the flat one: every piece of the file in order, covering it
16
+ * exactly once. It is the primitive because the covering property is a property
17
+ * of a flat list, and because a host holding one can produce anything else.
18
+ *
19
+ * `highlightLines` is the per-line one, and it exists because the split is where
20
+ * the mistake is made. A host slicing the flat list at line boundaries has to
21
+ * decide what to do with a piece of whitespace that runs from the end of one
22
+ * line to the start of the next, and getting it wrong costs a line its
23
+ * indentation or gives it somebody else's. Done once here, each line's pieces
24
+ * concatenate to exactly that line's text, newline excluded, which is what a
25
+ * line renderer wants and what the test asserts.
26
+ *
27
+ * ## Offsets are into the normalised text
28
+ *
29
+ * A byte order mark is dropped and CRLF becomes LF before anything else reads a
30
+ * file (`language.md` 3.1), so a span's offset counts into the text after those
31
+ * two changes, exactly as a diagnostic's does. A host that keeps its buffer
32
+ * exactly as the file was written normalises it once on the way in, or draws
33
+ * from each piece's own `text` and never indexes its buffer at all.
34
+ */
35
+ import type { SourceFile, Span } from '../core/index.js';
36
+ import { read } from './reading.js';
37
+ import { scan } from './scan.js';
38
+ import type { Highlight } from './scan.js';
39
+
40
+ /** One line's pieces, with the line they are on so a caller counts nothing. */
41
+ export interface HighlightedLine {
42
+ /** One-based, the same line a diagnostic's span carries. */
43
+ readonly line: number;
44
+ readonly pieces: readonly Highlight[];
45
+ }
46
+
47
+ /**
48
+ * The pieces of a file, in order, covering every character exactly once.
49
+ *
50
+ * Nothing here throws and nothing here reports. A file being typed into is
51
+ * malformed most of the time, and a highlighter that stopped at the first
52
+ * character the language does not have would leave the rest of a trader's
53
+ * script grey while they finished the word. `diagnose` is where the compiler
54
+ * says what is wrong with it.
55
+ */
56
+ export function highlight(source: string): readonly Highlight[] {
57
+ const held = read(source);
58
+ return scan(held.file, held.tokens);
59
+ }
60
+
61
+ /**
62
+ * The same pieces, grouped by line, with every line of the file present.
63
+ *
64
+ * A piece that runs across a line ending is cut at it, and the line ending
65
+ * itself belongs to no line: a renderer draws the text of a line and puts the
66
+ * break there itself. A blank line is an entry with no pieces rather than a
67
+ * missing entry, so a caller can walk lines one to `lineCount` and never index
68
+ * past the end of a shorter list.
69
+ */
70
+ export function highlightLines(source: string): readonly HighlightedLine[] {
71
+ const { file, tokens } = read(source);
72
+ const pieces = scan(file, tokens);
73
+ const lines: Highlight[][] = [];
74
+ for (let line = 0; line < file.lineCount; line += 1) lines.push([]);
75
+
76
+ for (const piece of pieces) {
77
+ for (const part of split(file, piece)) {
78
+ lines[part.span.line - 1]?.push(part);
79
+ }
80
+ }
81
+
82
+ return lines.map((held, index) => ({ line: index + 1, pieces: held }));
83
+ }
84
+
85
+ /**
86
+ * One piece as the pieces of the lines it covers, the line endings dropped.
87
+ *
88
+ * Only whitespace can reach here holding a line ending, since no token and no
89
+ * comment crosses one, but the split is written for any piece rather than for
90
+ * that one case: a piece shape that grows a new kind should not need this
91
+ * function to be revisited to stay correct.
92
+ */
93
+ function split(file: SourceFile, piece: Highlight): readonly Highlight[] {
94
+ if (!piece.text.includes('\n')) return [piece];
95
+
96
+ const out: Highlight[] = [];
97
+ let from = 0;
98
+ for (let i = 0; i <= piece.text.length; i += 1) {
99
+ const ending = i === piece.text.length || piece.text.charCodeAt(i) === 10;
100
+ if (!ending) continue;
101
+ if (i > from) out.push(partOf(file, piece, from, i));
102
+ from = i + 1;
103
+ }
104
+ return out;
105
+ }
106
+
107
+ function partOf(file: SourceFile, piece: Highlight, from: number, to: number): Highlight {
108
+ const span: Span = file.spanAt(piece.span.offset + from, to - from);
109
+ return { kind: piece.kind, span, text: piece.text.slice(from, to) };
110
+ }