openscript 0.4.0__py3-none-any.whl

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 (97) hide show
  1. openscript/__init__.py +40 -0
  2. openscript/__main__.py +62 -0
  3. openscript/accounting/__init__.py +74 -0
  4. openscript/accounting/analysis.py +174 -0
  5. openscript/accounting/charges.py +397 -0
  6. openscript/accounting/equity.py +234 -0
  7. openscript/accounting/report.py +82 -0
  8. openscript/accounting/shapes.py +74 -0
  9. openscript/accounting/statistics.py +300 -0
  10. openscript/accounting/trades.py +294 -0
  11. openscript/adapter/__init__.py +32 -0
  12. openscript/adapter/answers.py +215 -0
  13. openscript/adapter/channels.py +137 -0
  14. openscript/adapter/expectations.py +67 -0
  15. openscript/adapter/facts.py +127 -0
  16. openscript/adapter/matching.py +257 -0
  17. openscript/adapter/ordering.py +187 -0
  18. openscript/adapter/page.py +130 -0
  19. openscript/adapter/reading.py +357 -0
  20. openscript/adapter/reporting.py +244 -0
  21. openscript/adapter/running.py +449 -0
  22. openscript/adapter/serving.py +229 -0
  23. openscript/adapter/sessions.py +168 -0
  24. openscript/adapter/spellings.py +184 -0
  25. openscript/bars.py +157 -0
  26. openscript/budget.py +342 -0
  27. openscript/canonical.py +192 -0
  28. openscript/civil.py +196 -0
  29. openscript/contracts.py +165 -0
  30. openscript/dates.py +302 -0
  31. openscript/diagnostics.py +104 -0
  32. openscript/hours.py +165 -0
  33. openscript/inputs.py +239 -0
  34. openscript/intervals.py +60 -0
  35. openscript/library/__init__.py +76 -0
  36. openscript/library/arithmetic.py +128 -0
  37. openscript/library/averages.py +133 -0
  38. openscript/library/bars.py +60 -0
  39. openscript/library/bookkeeping.py +166 -0
  40. openscript/library/code_points.py +85 -0
  41. openscript/library/colour.py +202 -0
  42. openscript/library/composites.py +208 -0
  43. openscript/library/counting.py +218 -0
  44. openscript/library/deviation.py +155 -0
  45. openscript/library/elementary.py +206 -0
  46. openscript/library/extremes.py +122 -0
  47. openscript/library/flows.py +220 -0
  48. openscript/library/momentum.py +203 -0
  49. openscript/library/number_text.py +223 -0
  50. openscript/library/prices.py +36 -0
  51. openscript/library/ranges.py +105 -0
  52. openscript/library/rounding.py +123 -0
  53. openscript/library/series.py +213 -0
  54. openscript/library/stateful.py +442 -0
  55. openscript/library/stateless.py +261 -0
  56. openscript/library/strength.py +180 -0
  57. openscript/library/strings.py +228 -0
  58. openscript/library/trend.py +260 -0
  59. openscript/library/values.py +91 -0
  60. openscript/logbook.py +119 -0
  61. openscript/machine.py +499 -0
  62. openscript/memory.py +204 -0
  63. openscript/opcodes.py +166 -0
  64. openscript/program.py +146 -0
  65. openscript/run.py +368 -0
  66. openscript/strategy/__init__.py +78 -0
  67. openscript/strategy/calls.py +201 -0
  68. openscript/strategy/closable.py +182 -0
  69. openscript/strategy/fills.py +131 -0
  70. openscript/strategy/holdings.py +277 -0
  71. openscript/strategy/intents.py +162 -0
  72. openscript/strategy/ledger.py +270 -0
  73. openscript/strategy/placing.py +206 -0
  74. openscript/strategy/positions.py +124 -0
  75. openscript/strategy/refusals.py +293 -0
  76. openscript/strategy/rows.py +219 -0
  77. openscript/strategy/sizing.py +229 -0
  78. openscript/strategy/statuses.py +65 -0
  79. openscript/surface/__init__.py +115 -0
  80. openscript/surface/bands.py +103 -0
  81. openscript/surface/levels.py +44 -0
  82. openscript/surface/marks.py +52 -0
  83. openscript/surface/paints.py +58 -0
  84. openscript/surface/plots.py +44 -0
  85. openscript/surface/published.py +119 -0
  86. openscript/values.py +210 -0
  87. openscript/verify.py +301 -0
  88. openscript/verify_code.py +290 -0
  89. openscript/verify_requests.py +271 -0
  90. openscript/verify_shape.py +162 -0
  91. openscript/verify_tables.py +256 -0
  92. openscript/version.py +39 -0
  93. openscript/zones.py +118 -0
  94. openscript-0.4.0.dist-info/METADATA +82 -0
  95. openscript-0.4.0.dist-info/RECORD +97 -0
  96. openscript-0.4.0.dist-info/WHEEL +5 -0
  97. openscript-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,442 @@
1
+ """The stateful half of the library manifest: what a name and an arity resolve to.
2
+
3
+ The other half of the table ``stateless.py`` holds, on the same terms.
4
+ `compiled-program.md` section 2.5 has the engine check every entry of a program's
5
+ library table against its manifest at load, by name and by arity, and ``state``
6
+ and ``effect`` have to agree there as well, so a row is wrong if it says a
7
+ function holds nothing when it holds a window.
8
+
9
+ **Every entry here is ``state`` true and ``effect`` none.** A region is created
10
+ per call site by the engine and handed back to the same call site on every bar,
11
+ and section 6's rollback copies it without knowing whose it is. That is why a
12
+ call here takes three things rather than two: the context, the arguments, and
13
+ the region it is the only reader of.
14
+
15
+ **A kernel takes the context only where the vector file says it read the bar.**
16
+ ``atr`` reads the high, the low and the previous close; ``sma`` reads nothing but
17
+ its arguments. Passing the context to everything would make it impossible to
18
+ tell, from this file, which functions an engine has to state bar facts for.
19
+ """
20
+
21
+ from typing import Any, Callable, List, NamedTuple, Optional, Sequence
22
+
23
+ from . import (
24
+ averages,
25
+ bookkeeping,
26
+ composites,
27
+ counting,
28
+ deviation,
29
+ extremes,
30
+ flows,
31
+ momentum,
32
+ ranges,
33
+ strength,
34
+ trend,
35
+ )
36
+ from .series import Region
37
+ from .stateless import Context
38
+ from .values import Value, whole
39
+
40
+ #: One library call at a bar: the context, this bar's arguments, and the region.
41
+ Call = Callable[[Context, Sequence[Value], Region], Value]
42
+
43
+
44
+ class Entry(NamedTuple):
45
+ """One manifest row, the four fields of section 2.5 and the call itself."""
46
+
47
+ name: str
48
+ arity: int
49
+ call: Call
50
+ state: bool = True
51
+ effect: str = "none"
52
+
53
+
54
+ def at(args: Sequence[Value], index: int) -> Value:
55
+ """An argument, or absence where the caller passed fewer than the arity.
56
+
57
+ A call arrives with its defaults already filled (`compiled-program.md`
58
+ section 4.10), so a short argument list is a program the load-time check
59
+ should have refused. Absence rather than a raise, for the reason
60
+ ``values.py`` gives: nothing in this package raises.
61
+ """
62
+ return args[index] if index < len(args) else None
63
+
64
+
65
+ def length_of(value: Value) -> Optional[int]:
66
+ """A length argument as the whole number of bars it has to be.
67
+
68
+ `stdlib.md` section 2.5 refuses a fractional or negative length with a
69
+ diagnostic before the call arrives, so what is left here is the backstop:
70
+ anything that is not a whole count is absence, and a window of absence is
71
+ absent rather than guessed at.
72
+ """
73
+ return whole(value)
74
+
75
+
76
+ def _measured(name: str, of: Callable[[Region, Value, Optional[int]], Value]) -> Entry:
77
+ """A row of the shape ``(src, len)``, which most of the library is."""
78
+ return Entry(name, 2, lambda ctx, args, state: of(state, at(args, 0), length_of(at(args, 1))))
79
+
80
+
81
+ def _windowed(name: str, of: Callable[[Region, Any, Optional[int]], Value]) -> Entry:
82
+ """A row of the shape ``(len)`` whose kernel reads the bar."""
83
+ return Entry(name, 1, lambda ctx, args, state: of(state, ctx, length_of(at(args, 0))))
84
+
85
+
86
+ def _pivot(name: str, of) -> Entry:
87
+ """A row of the shape ``(src, left, right)``."""
88
+ return Entry(
89
+ name,
90
+ 3,
91
+ lambda ctx, args, state: of(
92
+ state, at(args, 0), length_of(at(args, 1)), length_of(at(args, 2))
93
+ ),
94
+ )
95
+
96
+
97
+ def _paired(name: str, of) -> Entry:
98
+ """A row of the shape ``(a, b, len)``, two series over one window."""
99
+ return Entry(
100
+ name,
101
+ 3,
102
+ lambda ctx, args, state: of(state, at(args, 0), at(args, 1), length_of(at(args, 2))),
103
+ )
104
+
105
+
106
+ def _crossing(name: str, of) -> Entry:
107
+ """A row of the shape ``(a, b)``, which is every crossing test."""
108
+ return Entry(name, 2, lambda ctx, args, state: of(state, at(args, 0), at(args, 1)))
109
+
110
+
111
+ def _array(items: List[Value], ctx: Context) -> Value:
112
+ """A multi-output call's answer: an array that is never absent, section 2.3."""
113
+ return ctx.make_array(items)
114
+
115
+
116
+ # `stdlib.md` section 20.3, the means.
117
+ _AVERAGES: tuple[Entry, ...] = (
118
+ _measured("sma", averages.simple),
119
+ _measured("ema", averages.exponential),
120
+ _measured("rma", averages.smoothed),
121
+ _measured("wma", averages.linear),
122
+ Entry("swma", 1, lambda ctx, args, state: averages.symmetric(state, at(args, 0))),
123
+ Entry(
124
+ "vwma",
125
+ 2,
126
+ lambda ctx, args, state: averages.volume_weighted(
127
+ state, at(args, 0), ctx.bar("volume"), length_of(at(args, 1))
128
+ ),
129
+ ),
130
+ _measured("hma", composites.hull),
131
+ _measured("dema", composites.doubled),
132
+ _measured("tema", composites.tripled),
133
+ Entry(
134
+ "alma",
135
+ 4,
136
+ lambda ctx, args, state: composites.gaussian(
137
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2), at(args, 3)
138
+ ),
139
+ ),
140
+ Entry(
141
+ "linreg",
142
+ 3,
143
+ lambda ctx, args, state: composites.fitted(
144
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
145
+ ),
146
+ ),
147
+ Entry(
148
+ "ma",
149
+ 3,
150
+ lambda ctx, args, state: composites.named(
151
+ state, ctx, at(args, 0), length_of(at(args, 1)), at(args, 2)
152
+ ),
153
+ ),
154
+ )
155
+
156
+
157
+ # `stdlib.md` section 20.8, the totals and the ranks, and section 20.10's scans.
158
+ _HELPERS: tuple[Entry, ...] = (
159
+ _measured("highest", extremes.highest),
160
+ _measured("lowest", extremes.lowest),
161
+ _measured("highestBars", extremes.highest_bars),
162
+ _measured("lowestBars", extremes.lowest_bars),
163
+ _pivot("pivotHigh", extremes.pivot_high),
164
+ _pivot("pivotLow", extremes.pivot_low),
165
+ _measured("sum", counting.windowed_sum),
166
+ _measured("count", counting.counted),
167
+ _measured("sumSkip", counting.skipping_sum),
168
+ _measured("avgSkip", counting.skipping_mean),
169
+ _measured("countPresent", counting.present_count),
170
+ _measured("median", counting.middle),
171
+ _measured("percentRank", counting.rank_of),
172
+ Entry("cum", 1, lambda ctx, args, state: counting.accumulated(state, at(args, 0))),
173
+ Entry(
174
+ "history",
175
+ 2,
176
+ lambda ctx, args, state: counting.history_at(state, at(args, 0), length_of(at(args, 1))),
177
+ ),
178
+ Entry(
179
+ "percentile",
180
+ 3,
181
+ lambda ctx, args, state: counting.ranked(
182
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
183
+ ),
184
+ ),
185
+ _paired("covariance", counting.covariance),
186
+ _paired("correlation", counting.correlation),
187
+ )
188
+
189
+ # `stdlib.md` section 9, the comparisons and the two that wait for a condition.
190
+ _BOOKKEEPING: tuple[Entry, ...] = (
191
+ Entry("change", 1, lambda ctx, args, state: bookkeeping.change(state, at(args, 0), 1)),
192
+ _measured("change", bookkeeping.change),
193
+ _measured("rising", bookkeeping.rising),
194
+ _measured("falling", bookkeeping.falling),
195
+ _crossing("crossUp", bookkeeping.crossed_up),
196
+ _crossing("crossDown", bookkeeping.crossed_down),
197
+ _crossing("cross", bookkeeping.crossed),
198
+ Entry("barsSince", 1, lambda ctx, args, state: bookkeeping.bars_since(state, at(args, 0))),
199
+ Entry(
200
+ "valueWhen",
201
+ 3,
202
+ lambda ctx, args, state: bookkeeping.value_when(
203
+ state, at(args, 0), at(args, 1), length_of(at(args, 2))
204
+ ),
205
+ ),
206
+ )
207
+
208
+
209
+ # `stdlib.md` section 20.5, the ranges and the bands.
210
+ _RANGES: tuple[Entry, ...] = (
211
+ _windowed("atr", ranges.average_range),
212
+ _windowed("natr", ranges.normalised_range),
213
+ _windowed("chop", ranges.choppiness),
214
+ Entry(
215
+ "donchian",
216
+ 1,
217
+ lambda ctx, args, state: _array(ranges.channel(state, ctx, length_of(at(args, 0))), ctx),
218
+ ),
219
+ Entry(
220
+ "variance",
221
+ 3,
222
+ lambda ctx, args, state: deviation.variance(
223
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
224
+ ),
225
+ ),
226
+ Entry(
227
+ "stdev",
228
+ 3,
229
+ lambda ctx, args, state: deviation.deviation(
230
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
231
+ ),
232
+ ),
233
+ Entry(
234
+ "bollinger",
235
+ 3,
236
+ lambda ctx, args, state: _array(
237
+ deviation.bands(state, at(args, 0), length_of(at(args, 1)), at(args, 2)), ctx
238
+ ),
239
+ ),
240
+ Entry(
241
+ "bbWidth",
242
+ 3,
243
+ lambda ctx, args, state: deviation.band_width(
244
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
245
+ ),
246
+ ),
247
+ Entry(
248
+ "bbPercent",
249
+ 3,
250
+ lambda ctx, args, state: deviation.band_position(
251
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
252
+ ),
253
+ ),
254
+ Entry(
255
+ "keltner",
256
+ 4,
257
+ lambda ctx, args, state: _array(
258
+ deviation.range_bands(
259
+ state,
260
+ ctx,
261
+ length_of(at(args, 0)),
262
+ at(args, 1),
263
+ length_of(at(args, 2)),
264
+ at(args, 3),
265
+ ),
266
+ ctx,
267
+ ),
268
+ ),
269
+ Entry(
270
+ "hv",
271
+ 3,
272
+ lambda ctx, args, state: deviation.historical(
273
+ state, at(args, 0), length_of(at(args, 1)), at(args, 2)
274
+ ),
275
+ ),
276
+ )
277
+
278
+
279
+ # `stdlib.md` section 20.3, the trend studies that carry a decision from bar to bar.
280
+ _TREND: tuple[Entry, ...] = (
281
+ Entry(
282
+ "supertrend",
283
+ 2,
284
+ lambda ctx, args, state: _array(
285
+ trend.trailing_band(state, ctx, at(args, 0), length_of(at(args, 1))), ctx
286
+ ),
287
+ ),
288
+ Entry(
289
+ "psar",
290
+ 3,
291
+ lambda ctx, args, state: _array(
292
+ trend.accelerating_stop(state, ctx, at(args, 0), at(args, 1), at(args, 2)), ctx
293
+ ),
294
+ ),
295
+ Entry(
296
+ "adx",
297
+ 2,
298
+ lambda ctx, args, state: _array(
299
+ trend.directional(state, ctx, length_of(at(args, 0)), length_of(at(args, 1))), ctx
300
+ ),
301
+ ),
302
+ Entry(
303
+ "aroon",
304
+ 1,
305
+ lambda ctx, args, state: _array(trend.ages(state, ctx, length_of(at(args, 0))), ctx),
306
+ ),
307
+ Entry(
308
+ "ichimoku",
309
+ 3,
310
+ lambda ctx, args, state: _array(
311
+ trend.cloud(
312
+ state, ctx, length_of(at(args, 0)), length_of(at(args, 1)), length_of(at(args, 2))
313
+ ),
314
+ ctx,
315
+ ),
316
+ ),
317
+ )
318
+
319
+
320
+ # `stdlib.md` section 20.4, the momentum and position readings.
321
+ _OSCILLATORS: tuple[Entry, ...] = (
322
+ _measured("rsi", strength.strength),
323
+ _measured("cmo", strength.change_ratio),
324
+ _measured("mom", momentum.distance),
325
+ _measured("roc", momentum.rate),
326
+ _measured("trix", momentum.smoothed_rate),
327
+ _measured("dpo", momentum.detrended),
328
+ _windowed("cci", strength.deviation_reading),
329
+ _windowed("williamsR", strength.range_position),
330
+ Entry(
331
+ "stoch",
332
+ 3,
333
+ lambda ctx, args, state: _array(
334
+ strength.position(
335
+ state, ctx, length_of(at(args, 0)), length_of(at(args, 1)), length_of(at(args, 2))
336
+ ),
337
+ ctx,
338
+ ),
339
+ ),
340
+ Entry(
341
+ "stochRsi",
342
+ 5,
343
+ lambda ctx, args, state: _array(
344
+ strength.strength_position(
345
+ state,
346
+ at(args, 0),
347
+ length_of(at(args, 1)),
348
+ length_of(at(args, 2)),
349
+ length_of(at(args, 3)),
350
+ length_of(at(args, 4)),
351
+ ),
352
+ ctx,
353
+ ),
354
+ ),
355
+ Entry(
356
+ "macd",
357
+ 4,
358
+ lambda ctx, args, state: _array(
359
+ momentum.gap(
360
+ state,
361
+ at(args, 0),
362
+ length_of(at(args, 1)),
363
+ length_of(at(args, 2)),
364
+ length_of(at(args, 3)),
365
+ False,
366
+ ),
367
+ ctx,
368
+ ),
369
+ ),
370
+ Entry(
371
+ "ppo",
372
+ 4,
373
+ lambda ctx, args, state: _array(
374
+ momentum.gap(
375
+ state,
376
+ at(args, 0),
377
+ length_of(at(args, 1)),
378
+ length_of(at(args, 2)),
379
+ length_of(at(args, 3)),
380
+ True,
381
+ ),
382
+ ctx,
383
+ ),
384
+ ),
385
+ Entry(
386
+ "awesomeOsc",
387
+ 2,
388
+ lambda ctx, args, state: momentum.midpoint_gap(
389
+ state, ctx, length_of(at(args, 0)), length_of(at(args, 1))
390
+ ),
391
+ ),
392
+ Entry(
393
+ "tsi",
394
+ 3,
395
+ lambda ctx, args, state: momentum.double_smoothed(
396
+ state, at(args, 0), length_of(at(args, 1)), length_of(at(args, 2))
397
+ ),
398
+ ),
399
+ Entry(
400
+ "ultimateOsc",
401
+ 3,
402
+ lambda ctx, args, state: momentum.blended(
403
+ state, ctx, length_of(at(args, 0)), length_of(at(args, 1)), length_of(at(args, 2))
404
+ ),
405
+ ),
406
+ )
407
+
408
+
409
+ # `stdlib.md` section 20.6, the volume readings.
410
+ _VOLUME: tuple[Entry, ...] = (
411
+ Entry("obv", 0, lambda ctx, args, state: flows.balance(state, ctx)),
412
+ Entry("ad", 0, lambda ctx, args, state: flows.accumulation(state, ctx)),
413
+ Entry("pvt", 0, lambda ctx, args, state: flows.price_trend(state, ctx)),
414
+ Entry("vwap", 1, lambda ctx, args, state: flows.session_average(state, ctx, at(args, 0))),
415
+ Entry(
416
+ "vwapAnchor",
417
+ 2,
418
+ lambda ctx, args, state: flows.anchored_average(state, ctx, at(args, 0), at(args, 1)),
419
+ ),
420
+ _windowed("cmf", flows.flow_fraction),
421
+ _windowed("mfi", flows.money_flow),
422
+ _windowed("eom", flows.movement),
423
+ _windowed("forceIndex", flows.force),
424
+ _windowed("relativeVolume", flows.relative_volume),
425
+ Entry(
426
+ "adOsc",
427
+ 2,
428
+ lambda ctx, args, state: flows.accumulation_gap(
429
+ state, ctx, length_of(at(args, 0)), length_of(at(args, 1))
430
+ ),
431
+ ),
432
+ )
433
+
434
+
435
+ ENTRIES: tuple[Entry, ...] = (
436
+ _AVERAGES + _HELPERS + _BOOKKEEPING + _RANGES + _TREND + _OSCILLATORS + _VOLUME
437
+ )
438
+
439
+
440
+ def table() -> dict[tuple[str, int], Entry]:
441
+ """The entries by name and arity, which is how a manifest is looked up."""
442
+ return {(entry.name, entry.arity): entry for entry in ENTRIES}
@@ -0,0 +1,261 @@
1
+ """The stateless half of the library manifest: what a name and an arity resolve to.
2
+
3
+ ``CALL_LIB`` names a function by its index in a program's own table, and
4
+ `compiled-program.md` section 2.5 has the engine check every entry of that table
5
+ against its manifest at load: the name must exist, the arity must match, and
6
+ ``state`` and ``effect`` must agree. This module is the half of that manifest
7
+ whose functions hold nothing across bars, keyed by name and arity, because the
8
+ library keys a function that way: ``change(src)`` and ``change(src, len)`` are two
9
+ functions and not one with a default.
10
+
11
+ **Every entry here is ``state`` false and ``effect`` none.** A function that holds
12
+ a state region across bars, and a function with an effect on the chart, the
13
+ ledger or the log, is in the stateful half. That is not a division of taste: a
14
+ stateless entry needs no state region, no checkpoint and no rollback
15
+ (`compiled-program.md` section 6), so an engine can call one of these from
16
+ anywhere without asking whose call site it is.
17
+
18
+ **What a call is given.** Three of these read something that is not an argument:
19
+ ``roundToTick`` asks the host for the instrument's tick, ``trueRange`` reads the
20
+ bar and whether it is the oldest one, and the calls that make or read an array
21
+ reach the heap. None of those is this package's to own, so a call takes a context
22
+ and asks it. The protocol below is the whole of what the stateless half needs
23
+ from an interpreter, and it is written as six members rather than as an engine
24
+ object so that the interpreter stage can satisfy it with whatever it already has.
25
+ """
26
+
27
+ from typing import Callable, Dict, NamedTuple, Optional, Protocol, Sequence, Tuple
28
+
29
+ from . import arithmetic, colour, elementary, rounding, strings
30
+ from .bars import true_range
31
+ from .number_text import spell, text_length, text_of, to_number
32
+ from .values import Value
33
+
34
+
35
+ class Context(Protocol):
36
+ """What a stateless call may ask its caller for, and nothing more."""
37
+
38
+ def bar(self, fact: str) -> Value:
39
+ """One fact of the bar being computed: ``high``, ``low``, ``previousClose``."""
40
+
41
+ def host(self, fact: str) -> Value:
42
+ """One fact the host states about the instrument, such as ``tickSize``."""
43
+
44
+ def first_bar(self) -> bool:
45
+ """Whether this is the oldest bar of the dataset.
46
+
47
+ ``trueRange`` turns on it, and no argument can answer it: `stdlib.md`
48
+ section 6 grants that bar the library's one exception to absence
49
+ propagation, and an absent previous close in the middle of a run is a
50
+ hole rather than the start of history.
51
+ """
52
+
53
+ def kind_of(self, reference: Value) -> str:
54
+ """The kind of a heap object, which is how ``text`` spells a reference."""
55
+
56
+ def items_of(self, reference: Value) -> list[Value] | None:
57
+ """The elements of an array, or absence when the value is not one."""
58
+
59
+ def make_array(self, items: list[Value]) -> Value:
60
+ """A new array on the heap, holding these elements in this order."""
61
+
62
+
63
+ Call = Callable[[Context, Sequence[Value]], Value]
64
+
65
+
66
+ class Entry(NamedTuple):
67
+ """One manifest row: what a program's table is checked against at load."""
68
+
69
+ name: str
70
+ arity: int
71
+ call: Call
72
+ state: bool = False
73
+ effect: str = "none"
74
+
75
+
76
+ def _at(args: Sequence[Value], index: int) -> Value:
77
+ """An argument, or absence where the caller passed fewer than the arity.
78
+
79
+ A call arrives with its defaults already filled (`compiled-program.md`
80
+ section 4.10), so a short argument list is a program the load-time check
81
+ should have refused. Absence rather than a raise, for the reason
82
+ ``values.py`` gives.
83
+ """
84
+ return args[index] if index < len(args) else None
85
+
86
+
87
+ def _one(name: str, of: Callable[[Value], Value]) -> Entry:
88
+ return Entry(name, 1, lambda _ctx, args: of(_at(args, 0)))
89
+
90
+
91
+ def _two(name: str, of: Callable[[Value, Value], Value]) -> Entry:
92
+ return Entry(name, 2, lambda _ctx, args: of(_at(args, 0), _at(args, 1)))
93
+
94
+
95
+ def _three(name: str, of: Callable[[Value, Value, Value], Value]) -> Entry:
96
+ return Entry(name, 3, lambda _ctx, args: of(_at(args, 0), _at(args, 1), _at(args, 2)))
97
+
98
+
99
+ def _constant(name: str, value: float) -> Entry:
100
+ """A constant read bare, which the format still compiles to a call of none."""
101
+ return Entry(name, 0, lambda _ctx, _args: value)
102
+
103
+
104
+ def _named_colour(name: str) -> Entry:
105
+ """One of the nineteen bare colour names, which is also a call of no arguments."""
106
+ return Entry(name, 0, lambda _ctx, _args: colour.named(name))
107
+
108
+
109
+ def _text(ctx: Context, args: Sequence[Value]) -> Value:
110
+ return spell(_at(args, 0), ctx.kind_of)
111
+
112
+
113
+ def _split(ctx: Context, args: Sequence[Value]) -> Value:
114
+ parts = strings.split(_at(args, 0), _at(args, 1))
115
+ return None if parts is None else ctx.make_array(list(parts))
116
+
117
+
118
+ def _join(ctx: Context, args: Sequence[Value]) -> Value:
119
+ items = ctx.items_of(_at(args, 0))
120
+ return None if items is None else strings.join(items, _at(args, 1))
121
+
122
+
123
+ def _round_to_tick(ctx: Context, args: Sequence[Value]) -> Value:
124
+ return rounding.round_to_tick(_at(args, 0), ctx.host("tickSize"))
125
+
126
+
127
+ def _true_range(ctx: Context, _args: Sequence[Value]) -> Value:
128
+ return true_range(
129
+ ctx.bar("high"), ctx.bar("low"), ctx.bar("previousClose"), ctx.first_bar()
130
+ )
131
+
132
+
133
+ # `stdlib.md` section 8.1, the bare arithmetic.
134
+ _MATHS: tuple[Entry, ...] = (
135
+ _one("abs", arithmetic.abs_of),
136
+ _one("sign", arithmetic.sign),
137
+ _two("min", arithmetic.minimum),
138
+ _two("max", arithmetic.maximum),
139
+ _three("clamp", arithmetic.clamp),
140
+ _one("floor", rounding.floor),
141
+ _one("ceil", rounding.ceil),
142
+ _one("round", rounding.round_to_whole),
143
+ _two("round", rounding.round_to),
144
+ _one("trunc", rounding.trunc),
145
+ _two("roundToStep", rounding.round_to_step),
146
+ Entry("roundToTick", 1, _round_to_tick),
147
+ _one("sqrt", elementary.sqrt),
148
+ _two("pow", elementary.power),
149
+ _one("exp", elementary.exp),
150
+ _one("log", elementary.log),
151
+ _one("log10", elementary.log10),
152
+ _two("mod", arithmetic.mod),
153
+ _one("isNone", arithmetic.is_none),
154
+ _two("orElse", arithmetic.or_else),
155
+ _one("toBool", arithmetic.to_bool),
156
+ )
157
+
158
+ # `stdlib.md` section 8.2, the `math` namespace.
159
+ _MATH_NAMESPACE: tuple[Entry, ...] = (
160
+ _constant("math.pi", elementary.PI),
161
+ _constant("math.e", elementary.E),
162
+ _one("math.log2", elementary.log2),
163
+ _two("math.hypot", elementary.hypot),
164
+ _one("math.toDegrees", elementary.to_degrees),
165
+ _one("math.toRadians", elementary.to_radians),
166
+ _one("math.sin", elementary.sin),
167
+ _one("math.cos", elementary.cos),
168
+ _one("math.tan", elementary.tan),
169
+ _one("math.asin", elementary.asin),
170
+ _one("math.acos", elementary.acos),
171
+ _one("math.atan", elementary.atan),
172
+ _two("math.atan2", elementary.atan2),
173
+ )
174
+
175
+ # `stdlib.md` section 10, strings and formatting.
176
+ _TEXT: tuple[Entry, ...] = (
177
+ Entry("text", 1, _text),
178
+ _two("text", text_of),
179
+ _one("toNumber", to_number),
180
+ _one("str.length", strings.length),
181
+ _one("str.upper", strings.upper),
182
+ _one("str.lower", strings.lower),
183
+ _one("str.trim", strings.trim),
184
+ _two("str.contains", strings.contains),
185
+ _two("str.startsWith", strings.starts_with),
186
+ _two("str.endsWith", strings.ends_with),
187
+ _two("str.indexOf", strings.index_of),
188
+ _three("str.substring", strings.substring),
189
+ _three("str.replace", strings.replace),
190
+ _three("str.replaceAll", strings.replace_all),
191
+ Entry("str.split", 2, _split),
192
+ Entry("str.join", 2, _join),
193
+ _three("str.padLeft", strings.pad_left),
194
+ _three("str.padRight", strings.pad_right),
195
+ _two("str.repeat", strings.repeat),
196
+ )
197
+
198
+ #: The two calls whose string can be measured before it is built, and the
199
+ #: function that measures each.
200
+ #:
201
+ #: The string ceiling is the interpreter's to spend and nothing in this package
202
+ #: raises, so this is how the two meet: the interpreter asks how long the string
203
+ #: will be and refuses the call rather than the result. Every other call that
204
+ #: builds a string is measured after it is built, because its length is not known
205
+ #: until the work is done, and that costs nothing: the length of a replaced or a
206
+ #: padded string is within a constant factor of what it was handed. These two are
207
+ #: not, and a count a script computed can ask either of them for a string no
208
+ #: engine could hold.
209
+ #: The calls that BUILD a string, and so are held to the code point ceiling.
210
+ #:
211
+ #: Named rather than inferred, because the answer's type cannot tell a string a
212
+ #: call made from one it passed through. `orElse(chart.symbol, "x")` answers a
213
+ #: string, and it is the host's symbol: refusing it for being long would refuse
214
+ #: the host its own instrument name, which is a difference the first engine does
215
+ #: not make. That engine guards these twelve call sites and no others
216
+ #: (`src/core/engine/library/text.ts` and `dates.ts`), so this is that set.
217
+ BUILDS_A_STRING: frozenset = frozenset(
218
+ {
219
+ "text",
220
+ "str.upper",
221
+ "str.lower",
222
+ "str.trim",
223
+ "str.substring",
224
+ "str.replace",
225
+ "str.replaceAll",
226
+ "str.join",
227
+ "str.padLeft",
228
+ "str.padRight",
229
+ "str.repeat",
230
+ "date.format",
231
+ }
232
+ )
233
+
234
+ MEASURED: Dict[Tuple[str, int], Callable[..., Optional[int]]] = {
235
+ ("text", 2): text_length,
236
+ ("str.repeat", 2): strings.repeat_length,
237
+ }
238
+
239
+ # `stdlib.md` section 11, the nineteen names and the calls that compute a colour.
240
+ _COLOUR: tuple[Entry, ...] = tuple(_named_colour(name) for name in colour.NAMES) + (
241
+ _three("rgb", colour.rgb),
242
+ Entry(
243
+ "rgba",
244
+ 4,
245
+ lambda _ctx, args: colour.rgba(_at(args, 0), _at(args, 1), _at(args, 2), _at(args, 3)),
246
+ ),
247
+ _two("fade", colour.fade),
248
+ _three("mix", colour.mix),
249
+ _one("alpha", colour.alpha),
250
+ _two("withAlpha", colour.with_alpha),
251
+ )
252
+
253
+ # `stdlib.md` section 6, the one reading of a range that holds nothing.
254
+ _RANGES: tuple[Entry, ...] = (Entry("trueRange", 0, _true_range),)
255
+
256
+ ENTRIES: tuple[Entry, ...] = _MATHS + _MATH_NAMESPACE + _TEXT + _COLOUR + _RANGES
257
+
258
+
259
+ def table() -> dict[tuple[str, int], Entry]:
260
+ """The entries by name and arity, which is how a manifest is looked up."""
261
+ return {(entry.name, entry.arity): entry for entry in ENTRIES}