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.
- package/CHANGELOG.md +269 -0
- package/README.md +65 -31
- package/dist/adapters/charts/surfaces.d.ts +17 -0
- package/dist/adapters/charts/surfaces.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js +82 -0
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/codemirror/commands.d.ts +14 -0
- package/dist/adapters/codemirror/commands.d.ts.map +1 -0
- package/dist/adapters/codemirror/commands.js +52 -0
- package/dist/adapters/codemirror/commands.js.map +1 -0
- package/dist/adapters/codemirror/completion.d.ts +15 -0
- package/dist/adapters/codemirror/completion.d.ts.map +1 -0
- package/dist/adapters/codemirror/completion.js +64 -0
- package/dist/adapters/codemirror/completion.js.map +1 -0
- package/dist/adapters/codemirror/contract.d.ts +156 -0
- package/dist/adapters/codemirror/contract.d.ts.map +1 -0
- package/dist/adapters/codemirror/contract.js +42 -0
- package/dist/adapters/codemirror/contract.js.map +1 -0
- package/dist/adapters/codemirror/index.d.ts +43 -0
- package/dist/adapters/codemirror/index.d.ts.map +1 -0
- package/dist/adapters/codemirror/index.js +8 -0
- package/dist/adapters/codemirror/index.js.map +1 -0
- package/dist/adapters/codemirror/lint.d.ts +17 -0
- package/dist/adapters/codemirror/lint.d.ts.map +1 -0
- package/dist/adapters/codemirror/lint.js +62 -0
- package/dist/adapters/codemirror/lint.js.map +1 -0
- package/dist/adapters/codemirror/positions.d.ts +16 -0
- package/dist/adapters/codemirror/positions.d.ts.map +1 -0
- package/dist/adapters/codemirror/positions.js +65 -0
- package/dist/adapters/codemirror/positions.js.map +1 -0
- package/dist/adapters/codemirror/stream.d.ts +23 -0
- package/dist/adapters/codemirror/stream.d.ts.map +1 -0
- package/dist/adapters/codemirror/stream.js +70 -0
- package/dist/adapters/codemirror/stream.js.map +1 -0
- package/dist/adapters/codemirror/tokens.d.ts +44 -0
- package/dist/adapters/codemirror/tokens.d.ts.map +1 -0
- package/dist/adapters/codemirror/tokens.js +34 -0
- package/dist/adapters/codemirror/tokens.js.map +1 -0
- package/dist/adapters/codemirror/tooltips.d.ts +35 -0
- package/dist/adapters/codemirror/tooltips.d.ts.map +1 -0
- package/dist/adapters/codemirror/tooltips.js +108 -0
- package/dist/adapters/codemirror/tooltips.js.map +1 -0
- package/dist/core/accounting/charges.d.ts +136 -0
- package/dist/core/accounting/charges.d.ts.map +1 -0
- package/dist/core/accounting/charges.js +362 -0
- package/dist/core/accounting/charges.js.map +1 -0
- package/dist/core/accounting/equity.d.ts +158 -0
- package/dist/core/accounting/equity.d.ts.map +1 -0
- package/dist/core/accounting/equity.js +155 -0
- package/dist/core/accounting/equity.js.map +1 -0
- package/dist/core/accounting/index.d.ts +44 -0
- package/dist/core/accounting/index.d.ts.map +1 -0
- package/dist/core/accounting/index.js +36 -0
- package/dist/core/accounting/index.js.map +1 -0
- package/dist/core/accounting/markers.d.ts +43 -0
- package/dist/core/accounting/markers.d.ts.map +1 -0
- package/dist/core/accounting/markers.js +64 -0
- package/dist/core/accounting/markers.js.map +1 -0
- package/dist/core/accounting/monthly.d.ts +52 -0
- package/dist/core/accounting/monthly.d.ts.map +1 -0
- package/dist/core/accounting/monthly.js +99 -0
- package/dist/core/accounting/monthly.js.map +1 -0
- package/dist/core/accounting/report.d.ts +38 -0
- package/dist/core/accounting/report.d.ts.map +1 -0
- package/dist/core/accounting/report.js +63 -0
- package/dist/core/accounting/report.js.map +1 -0
- package/dist/core/accounting/shapes.d.ts +70 -0
- package/dist/core/accounting/shapes.d.ts.map +1 -0
- package/dist/core/accounting/shapes.js +21 -0
- package/dist/core/accounting/shapes.js.map +1 -0
- package/dist/core/accounting/statistics.d.ts +75 -0
- package/dist/core/accounting/statistics.d.ts.map +1 -0
- package/dist/core/accounting/statistics.js +246 -0
- package/dist/core/accounting/statistics.js.map +1 -0
- package/dist/core/accounting/trades.d.ts +118 -0
- package/dist/core/accounting/trades.d.ts.map +1 -0
- package/dist/core/accounting/trades.js +186 -0
- package/dist/core/accounting/trades.js.map +1 -0
- package/dist/core/backtest/compare.d.ts +38 -0
- package/dist/core/backtest/compare.d.ts.map +1 -0
- package/dist/core/backtest/compare.js +189 -0
- package/dist/core/backtest/compare.js.map +1 -0
- package/dist/core/backtest/declaration.d.ts +52 -0
- package/dist/core/backtest/declaration.d.ts.map +1 -0
- package/dist/core/backtest/declaration.js +48 -0
- package/dist/core/backtest/declaration.js.map +1 -0
- package/dist/core/backtest/drive.d.ts +29 -0
- package/dist/core/backtest/drive.d.ts.map +1 -0
- package/dist/core/backtest/drive.js +262 -0
- package/dist/core/backtest/drive.js.map +1 -0
- package/dist/core/backtest/index.d.ts +46 -0
- package/dist/core/backtest/index.d.ts.map +1 -0
- package/dist/core/backtest/index.js +36 -0
- package/dist/core/backtest/index.js.map +1 -0
- package/dist/core/backtest/range.d.ts +84 -0
- package/dist/core/backtest/range.d.ts.map +1 -0
- package/dist/core/backtest/range.js +90 -0
- package/dist/core/backtest/range.js.map +1 -0
- package/dist/core/backtest/record.d.ts +238 -0
- package/dist/core/backtest/record.d.ts.map +1 -0
- package/dist/core/backtest/record.js +176 -0
- package/dist/core/backtest/record.js.map +1 -0
- package/dist/core/backtest/replay.d.ts +48 -0
- package/dist/core/backtest/replay.d.ts.map +1 -0
- package/dist/core/backtest/replay.js +127 -0
- package/dist/core/backtest/replay.js.map +1 -0
- package/dist/core/backtest/resting.d.ts +62 -0
- package/dist/core/backtest/resting.d.ts.map +1 -0
- package/dist/core/backtest/resting.js +59 -0
- package/dist/core/backtest/resting.js.map +1 -0
- package/dist/core/backtest/settings.d.ts +117 -0
- package/dist/core/backtest/settings.d.ts.map +1 -0
- package/dist/core/backtest/settings.js +207 -0
- package/dist/core/backtest/settings.js.map +1 -0
- package/dist/core/backtest/simulate.d.ts +146 -0
- package/dist/core/backtest/simulate.d.ts.map +1 -0
- package/dist/core/backtest/simulate.js +217 -0
- package/dist/core/backtest/simulate.js.map +1 -0
- package/dist/core/catalogue/catalogue.generated.d.ts +66 -0
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +6 -0
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/catalogue/values.generated.d.ts +24 -0
- package/dist/core/catalogue/values.generated.d.ts.map +1 -1
- package/dist/core/check/index.d.ts +2 -1
- package/dist/core/check/index.d.ts.map +1 -1
- package/dist/core/check/index.js +1 -1
- package/dist/core/check/index.js.map +1 -1
- package/dist/core/check/library-prose.generated.d.ts +16 -0
- package/dist/core/check/library-prose.generated.d.ts.map +1 -0
- package/dist/core/check/library-prose.generated.js +353 -0
- package/dist/core/check/library-prose.generated.js.map +1 -0
- package/dist/core/check/surface.d.ts +24 -0
- package/dist/core/check/surface.d.ts.map +1 -1
- package/dist/core/check/surface.js +29 -0
- package/dist/core/check/surface.js.map +1 -1
- package/dist/core/emit/defaults.d.ts +35 -16
- package/dist/core/emit/defaults.d.ts.map +1 -1
- package/dist/core/emit/defaults.js +66 -0
- package/dist/core/emit/defaults.js.map +1 -1
- package/dist/core/emit/index.d.ts +2 -0
- package/dist/core/emit/index.d.ts.map +1 -1
- package/dist/core/emit/index.js +2 -0
- package/dist/core/emit/index.js.map +1 -1
- package/dist/core/engine/index.d.ts +1 -1
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/index.js.map +1 -1
- package/dist/core/engine/load.d.ts +15 -1
- package/dist/core/engine/load.d.ts.map +1 -1
- package/dist/core/engine/load.js +1 -0
- package/dist/core/engine/load.js.map +1 -1
- package/dist/core/index.d.ts +37 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +17 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/dist/editor/complete.d.ts +40 -0
- package/dist/editor/complete.d.ts.map +1 -0
- package/dist/editor/complete.js +206 -0
- package/dist/editor/complete.js.map +1 -0
- package/dist/editor/diagnose.d.ts +18 -0
- package/dist/editor/diagnose.d.ts.map +1 -0
- package/dist/editor/diagnose.js +70 -0
- package/dist/editor/diagnose.js.map +1 -0
- package/dist/editor/format.d.ts +11 -0
- package/dist/editor/format.d.ts.map +1 -0
- package/dist/editor/format.js +50 -0
- package/dist/editor/format.js.map +1 -0
- package/dist/editor/highlight.d.ts +28 -0
- package/dist/editor/highlight.d.ts.map +1 -0
- package/dist/editor/highlight.js +65 -0
- package/dist/editor/highlight.js.map +1 -0
- package/dist/editor/hover.d.ts +40 -0
- package/dist/editor/hover.d.ts.map +1 -0
- package/dist/editor/hover.js +147 -0
- package/dist/editor/hover.js.map +1 -0
- package/dist/editor/index.d.ts +60 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +7 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/kinds.d.ts +27 -0
- package/dist/editor/kinds.d.ts.map +1 -0
- package/dist/editor/kinds.js +118 -0
- package/dist/editor/kinds.js.map +1 -0
- package/dist/editor/layout.d.ts +47 -0
- package/dist/editor/layout.d.ts.map +1 -0
- package/dist/editor/layout.js +135 -0
- package/dist/editor/layout.js.map +1 -0
- package/dist/editor/manifest.d.ts +47 -0
- package/dist/editor/manifest.d.ts.map +1 -0
- package/dist/editor/manifest.js +93 -0
- package/dist/editor/manifest.js.map +1 -0
- package/dist/editor/reading.d.ts +41 -0
- package/dist/editor/reading.d.ts.map +1 -0
- package/dist/editor/reading.js +51 -0
- package/dist/editor/reading.js.map +1 -0
- package/dist/editor/scan.d.ts +26 -0
- package/dist/editor/scan.d.ts.map +1 -0
- package/dist/editor/scan.js +139 -0
- package/dist/editor/scan.js.map +1 -0
- package/dist/editor/scope.d.ts +19 -0
- package/dist/editor/scope.d.ts.map +1 -0
- package/dist/editor/scope.js +99 -0
- package/dist/editor/scope.js.map +1 -0
- package/dist/editor/signature.d.ts +38 -0
- package/dist/editor/signature.d.ts.map +1 -0
- package/dist/editor/signature.js +112 -0
- package/dist/editor/signature.js.map +1 -0
- package/dist/editor/site.d.ts +46 -0
- package/dist/editor/site.d.ts.map +1 -0
- package/dist/editor/site.js +184 -0
- package/dist/editor/site.js.map +1 -0
- package/dist/editor/spacing.d.ts +29 -0
- package/dist/editor/spacing.d.ts.map +1 -0
- package/dist/editor/spacing.js +116 -0
- package/dist/editor/spacing.js.map +1 -0
- package/package.json +30 -3
- package/spec/errors.json +140 -0
- package/src/adapters/charts/surfaces.ts +17 -0
- package/src/adapters/charts/tables.ts +88 -0
- package/src/adapters/codemirror/commands.ts +53 -0
- package/src/adapters/codemirror/completion.ts +74 -0
- package/src/adapters/codemirror/contract.ts +156 -0
- package/src/adapters/codemirror/index.ts +66 -0
- package/src/adapters/codemirror/lint.ts +66 -0
- package/src/adapters/codemirror/positions.ts +79 -0
- package/src/adapters/codemirror/stream.ts +87 -0
- package/src/adapters/codemirror/tokens.ts +64 -0
- package/src/adapters/codemirror/tooltips.ts +113 -0
- package/src/core/accounting/charges.ts +452 -0
- package/src/core/accounting/equity.ts +276 -0
- package/src/core/accounting/index.ts +49 -0
- package/src/core/accounting/markers.ts +95 -0
- package/src/core/accounting/monthly.ts +137 -0
- package/src/core/accounting/report.ts +89 -0
- package/src/core/accounting/shapes.ts +73 -0
- package/src/core/accounting/statistics.ts +350 -0
- package/src/core/accounting/trades.ts +313 -0
- package/src/core/backtest/compare.ts +244 -0
- package/src/core/backtest/declaration.ts +97 -0
- package/src/core/backtest/drive.ts +364 -0
- package/src/core/backtest/index.ts +52 -0
- package/src/core/backtest/range.ts +137 -0
- package/src/core/backtest/record.ts +341 -0
- package/src/core/backtest/replay.ts +158 -0
- package/src/core/backtest/resting.ts +125 -0
- package/src/core/backtest/settings.ts +280 -0
- package/src/core/backtest/simulate.ts +304 -0
- package/src/core/catalogue/catalogue.generated.ts +6 -0
- package/src/core/catalogue/values.generated.ts +6 -0
- package/src/core/check/index.ts +3 -0
- package/src/core/check/library-prose.generated.ts +367 -0
- package/src/core/check/surface.ts +34 -0
- package/src/core/emit/defaults.ts +70 -0
- package/src/core/emit/index.ts +2 -0
- package/src/core/engine/index.ts +1 -1
- package/src/core/engine/load.ts +16 -2
- package/src/core/index.ts +85 -1
- package/src/core/version/version.generated.ts +1 -1
- package/src/editor/complete.ts +266 -0
- package/src/editor/diagnose.ts +71 -0
- package/src/editor/format.ts +85 -0
- package/src/editor/highlight.ts +110 -0
- package/src/editor/hover.ts +199 -0
- package/src/editor/index.ts +65 -0
- package/src/editor/kinds.ts +157 -0
- package/src/editor/layout.ts +189 -0
- package/src/editor/manifest.ts +119 -0
- package/src/editor/reading.ts +69 -0
- package/src/editor/scan.ts +162 -0
- package/src/editor/scope.ts +122 -0
- package/src/editor/signature.ts +150 -0
- package/src/editor/site.ts +233 -0
- package/src/editor/spacing.ts +132 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the cursor is, in the language's own terms.
|
|
3
|
+
*
|
|
4
|
+
* `complete` and `signature` both need the same three answers: what word is
|
|
5
|
+
* being typed, whether it follows a namespace and a dot, and which argument of
|
|
6
|
+
* which call it sits in. They are worked out once, here, because two copies of
|
|
7
|
+
* this would disagree on the day somebody fixes one of them.
|
|
8
|
+
*
|
|
9
|
+
* ## It reads tokens, not the tree
|
|
10
|
+
*
|
|
11
|
+
* A file being typed into is a file that does not parse: `ema(close, ` is the
|
|
12
|
+
* state a call is in for as long as it takes to write one, and it has no
|
|
13
|
+
* complete tree node for the parser to hand back. The tokens, on the other hand,
|
|
14
|
+
* are exactly what the writer has typed so far, and the real lexer produced
|
|
15
|
+
* them. So the enclosing call is found by walking bracket depth backwards over
|
|
16
|
+
* the token stream rather than by looking for a `Call` node that is not there
|
|
17
|
+
* yet.
|
|
18
|
+
*
|
|
19
|
+
* This is not a second parser. It asks one question the grammar already fixes,
|
|
20
|
+
* which bracket is still open, and it answers nothing else: what the call means,
|
|
21
|
+
* whether its arguments type check and whether the name exists are the checker's
|
|
22
|
+
* and are asked of the checker.
|
|
23
|
+
*
|
|
24
|
+
* **An unclosed bracket swallows the rest of the file**, and that is the
|
|
25
|
+
* compiler's reading too: a call opened on line 5 and never closed makes every
|
|
26
|
+
* line below it part of one unfinished statement, which is what the parser
|
|
27
|
+
* reports and what `diagnose-typing` in the benchmark suite measures. A special
|
|
28
|
+
* case here that stopped at the end of a line would give a writer a different
|
|
29
|
+
* answer from the diagnostics beside them.
|
|
30
|
+
*/
|
|
31
|
+
import { endOffset } from '../core/index.js';
|
|
32
|
+
import type { SourceFile, Span, Token } from '../core/index.js';
|
|
33
|
+
import { isReserved } from './kinds.js';
|
|
34
|
+
|
|
35
|
+
/** The call the cursor is inside, as far as the brackets say. */
|
|
36
|
+
export interface CallSite {
|
|
37
|
+
/** The callee as written, with its namespace: `ema`, `draw.box`. */
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly nameSpan: Span;
|
|
40
|
+
/** Which argument the cursor is in, counting from zero. */
|
|
41
|
+
readonly argument: number;
|
|
42
|
+
/** The label written for this argument, when it was written as `name = ...`. */
|
|
43
|
+
readonly label: string | undefined;
|
|
44
|
+
/** Every label already written in this call, so a completion can drop them. */
|
|
45
|
+
readonly labels: readonly string[];
|
|
46
|
+
/** The bracket that opened it, which is what a host anchors a tooltip to. */
|
|
47
|
+
readonly open: Span;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** What the cursor is on, and what it is inside. */
|
|
51
|
+
export interface Site {
|
|
52
|
+
/** The word being typed, when the cursor is in or at the end of one. */
|
|
53
|
+
readonly word: string;
|
|
54
|
+
/** Where it sits, so a host replaces the word rather than inserting into it. */
|
|
55
|
+
readonly replace: Span;
|
|
56
|
+
/** The namespace the word follows, when it was written as `draw.` */
|
|
57
|
+
readonly namespace: string | undefined;
|
|
58
|
+
/** Whether the cursor sits after a dot, where only members may be written. */
|
|
59
|
+
readonly afterDot: boolean;
|
|
60
|
+
readonly call: CallSite | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const OPENERS: ReadonlySet<string> = new Set(['(', '[']);
|
|
64
|
+
const CLOSERS: ReadonlySet<string> = new Set([')', ']']);
|
|
65
|
+
|
|
66
|
+
/** Whether a token is a word: a name, or a reserved word, which is also a word. */
|
|
67
|
+
function isWord(token: Token | undefined): boolean {
|
|
68
|
+
return token !== undefined && (token.kind === 'identifier' || isReserved(token.kind));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The tokens that end at or before an offset, and the one the offset is inside.
|
|
73
|
+
*
|
|
74
|
+
* A layout token is skipped: an `indent` covers a line's leading spaces and a
|
|
75
|
+
* `dedent` and the end of the file cover nothing, so none of them is ever the
|
|
76
|
+
* word somebody is typing, and a bracket scan that counted them would still be
|
|
77
|
+
* correct but a word scan that returned one would not.
|
|
78
|
+
*/
|
|
79
|
+
function before(tokens: readonly Token[], offset: number): readonly Token[] {
|
|
80
|
+
const taken: Token[] = [];
|
|
81
|
+
for (const token of tokens) {
|
|
82
|
+
if (token.span.offset >= offset) break;
|
|
83
|
+
if (token.kind === 'newline' || token.kind === 'indent') continue;
|
|
84
|
+
if (token.kind === 'dedent' || token.kind === 'endOfFile') continue;
|
|
85
|
+
taken.push(token);
|
|
86
|
+
}
|
|
87
|
+
return taken;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The call whose bracket is still open at the cursor, and where in it.
|
|
92
|
+
*
|
|
93
|
+
* Walked forwards with a stack rather than backwards with a counter, because
|
|
94
|
+
* the answer wanted is the innermost frame that is still open and a stack is
|
|
95
|
+
* what that is. A square bracket counts too: `f(a[1], b` has the cursor in `f`'s
|
|
96
|
+
* second argument, and a scan that only knew round brackets would put it in the
|
|
97
|
+
* first.
|
|
98
|
+
*/
|
|
99
|
+
function callAt(tokens: readonly Token[], text: string): CallSite | undefined {
|
|
100
|
+
interface Frame {
|
|
101
|
+
readonly open: Token;
|
|
102
|
+
readonly callee: readonly Token[];
|
|
103
|
+
commas: number;
|
|
104
|
+
label: string | undefined;
|
|
105
|
+
readonly labels: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const stack: Frame[] = [];
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
111
|
+
const token = tokens[i];
|
|
112
|
+
if (token === undefined) continue;
|
|
113
|
+
|
|
114
|
+
if (OPENERS.has(token.kind)) {
|
|
115
|
+
stack.push({
|
|
116
|
+
open: token,
|
|
117
|
+
callee: calleeOf(tokens, i),
|
|
118
|
+
commas: 0,
|
|
119
|
+
label: undefined,
|
|
120
|
+
labels: [],
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (CLOSERS.has(token.kind)) {
|
|
125
|
+
stack.pop();
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const frame = stack[stack.length - 1];
|
|
130
|
+
if (frame === undefined) continue;
|
|
131
|
+
if (token.kind === ',') {
|
|
132
|
+
frame.commas += 1;
|
|
133
|
+
frame.label = undefined;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// `name =` is a label, and `name ==` is a comparison. The lexer has already
|
|
137
|
+
// told the two apart: they are different tokens.
|
|
138
|
+
if (token.kind === '=' && isWord(tokens[i - 1])) {
|
|
139
|
+
const label = tokens[i - 1];
|
|
140
|
+
if (label !== undefined) {
|
|
141
|
+
frame.label = label.text;
|
|
142
|
+
frame.labels.push(label.text);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const frame = stack[stack.length - 1];
|
|
148
|
+
if (frame === undefined || frame.open.kind !== '(' || frame.callee.length === 0) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const first = frame.callee[0];
|
|
153
|
+
const last = frame.callee[frame.callee.length - 1];
|
|
154
|
+
if (first === undefined || last === undefined) return undefined;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
name: text.slice(first.span.offset, endOffset(last.span)),
|
|
158
|
+
nameSpan: first.span,
|
|
159
|
+
argument: frame.commas,
|
|
160
|
+
label: frame.label,
|
|
161
|
+
labels: frame.labels,
|
|
162
|
+
open: frame.open.span,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The name in front of a bracket, as one or three tokens.
|
|
168
|
+
*
|
|
169
|
+
* A dotted name is `identifier . identifier` to the lexer, so a call to
|
|
170
|
+
* `draw.box` is found by reading back over the dot. A bracket with no name in
|
|
171
|
+
* front of it is a grouping or an array literal rather than a call, and this
|
|
172
|
+
* returns nothing for it.
|
|
173
|
+
*/
|
|
174
|
+
function calleeOf(tokens: readonly Token[], open: number): readonly Token[] {
|
|
175
|
+
const name = tokens[open - 1];
|
|
176
|
+
if (!isWord(name) || name === undefined) return [];
|
|
177
|
+
const dot = tokens[open - 2];
|
|
178
|
+
const namespace = tokens[open - 3];
|
|
179
|
+
if (dot?.kind === '.' && isWord(namespace) && namespace !== undefined) {
|
|
180
|
+
return [namespace, dot, name];
|
|
181
|
+
}
|
|
182
|
+
return [name];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Everything the two functions need to know about a position.
|
|
187
|
+
*
|
|
188
|
+
* The word is the token the offset is inside or immediately after, which is
|
|
189
|
+
* what a writer half way through typing a name has. A cursor sitting on a space
|
|
190
|
+
* has no word and an empty replacement span at the cursor, so a host inserts
|
|
191
|
+
* there rather than overwriting whatever is to the left.
|
|
192
|
+
*/
|
|
193
|
+
export function siteAt(file: SourceFile, tokens: readonly Token[], offset: number): Site {
|
|
194
|
+
const text = file.text;
|
|
195
|
+
const at = Math.min(Math.max(Math.trunc(offset), 0), text.length);
|
|
196
|
+
const taken = before(tokens, at);
|
|
197
|
+
|
|
198
|
+
const last = taken[taken.length - 1];
|
|
199
|
+
const touching = last !== undefined && endOffset(last.span) === at;
|
|
200
|
+
const word = touching && isWord(last) ? last : undefined;
|
|
201
|
+
|
|
202
|
+
// The dot is the token before the word being typed, or the last token when
|
|
203
|
+
// nothing has been typed after it yet.
|
|
204
|
+
const dot = word === undefined ? last : taken[taken.length - 2];
|
|
205
|
+
const holder = word === undefined ? taken[taken.length - 2] : taken[taken.length - 3];
|
|
206
|
+
const afterDot = dot?.kind === '.' && (word !== undefined || touching);
|
|
207
|
+
const namespace = afterDot && isWord(holder) ? holder?.text : undefined;
|
|
208
|
+
|
|
209
|
+
const scanned = word === undefined ? taken : taken.slice(0, -1);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
word: word?.text ?? '',
|
|
213
|
+
replace: word?.span ?? file.spanAt(at, 0),
|
|
214
|
+
namespace,
|
|
215
|
+
afterDot: afterDot === true,
|
|
216
|
+
call: callAt(scanned, text),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The token an offset is inside, for a hover, which is not the same question.
|
|
222
|
+
*
|
|
223
|
+
* A completion is about what is being typed, so the token that ends at the
|
|
224
|
+
* cursor is the word. A hover is about what is under the pointer, so the token
|
|
225
|
+
* that contains the offset is the answer and one that merely ends there is not:
|
|
226
|
+
* pointing at the space after `ema` is not pointing at `ema`.
|
|
227
|
+
*/
|
|
228
|
+
export function tokenOn(tokens: readonly Token[], offset: number): Token | undefined {
|
|
229
|
+
return tokens.find(
|
|
230
|
+
(token) =>
|
|
231
|
+
token.span.length > 0 && offset >= token.span.offset && offset < endOffset(token.span),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What goes between two tokens on a line, and how each rule is decided.
|
|
3
|
+
*
|
|
4
|
+
* A formatter's spacing is where a language tool usually starts guessing. The
|
|
5
|
+
* classic guess is the one every reprinter makes about a minus sign: it looks at
|
|
6
|
+
* the token before it and decides from a list of what can precede a binary
|
|
7
|
+
* operator. That list is a second, smaller grammar, it is wrong in a case
|
|
8
|
+
* nobody thought of, and being wrong means printing `a - -b` as `a--b` or
|
|
9
|
+
* `dir == -1` as `dir == - 1`.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here guesses. Three questions cannot be answered by looking at two
|
|
12
|
+
* tokens, and all three are put to the parser, which has already answered them
|
|
13
|
+
* to build the tree:
|
|
14
|
+
*
|
|
15
|
+
* - **Is this minus a sign or a subtraction?** A `unary` node begins at its
|
|
16
|
+
* operator, so the tree names the offset of every sign in the file.
|
|
17
|
+
* - **Does this bracket open a call or group an expression?** A `grouping`
|
|
18
|
+
* node begins at its `(` and an `arrayLiteral` at its `[`. Every other
|
|
19
|
+
* bracket is an argument list, a parameter list or an index, which are the
|
|
20
|
+
* brackets that touch the name in front of them.
|
|
21
|
+
* - **Is this colon a ternary's or a type annotation's?** A `ternary` node
|
|
22
|
+
* carries the span of the arm before the colon, so the colon is the first
|
|
23
|
+
* one after it. Every other colon introduces a type.
|
|
24
|
+
*
|
|
25
|
+
* The rest is a table of marks, and it is small because the language is small.
|
|
26
|
+
*/
|
|
27
|
+
import { endOffset, walk } from '../core/index.js';
|
|
28
|
+
import type { Script, Token } from '../core/index.js';
|
|
29
|
+
|
|
30
|
+
/** The three questions the tree answers, as sets of source offsets. */
|
|
31
|
+
export interface Shapes {
|
|
32
|
+
/** Where a `+` or a `-` is a sign rather than an operator between two values. */
|
|
33
|
+
readonly signs: ReadonlySet<number>;
|
|
34
|
+
/** Where a `(` groups an expression, or a `[` opens an array literal. */
|
|
35
|
+
readonly grouped: ReadonlySet<number>;
|
|
36
|
+
/** Where a `:` separates the two arms of a ternary. */
|
|
37
|
+
readonly arms: ReadonlySet<number>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reads the three from a tree.
|
|
42
|
+
*
|
|
43
|
+
* A tree from a file that did not parse is not passed here: the formatter
|
|
44
|
+
* refuses such a file before this is reached, because a tree with recovery in it
|
|
45
|
+
* describes a program nobody wrote.
|
|
46
|
+
*/
|
|
47
|
+
export function shapesOf(script: Script, tokens: readonly Token[]): Shapes {
|
|
48
|
+
const colons = tokens.filter((token) => token.kind === ':').map((token) => token.span.offset);
|
|
49
|
+
const signs = new Set<number>();
|
|
50
|
+
const grouped = new Set<number>();
|
|
51
|
+
const arms = new Set<number>();
|
|
52
|
+
|
|
53
|
+
walk(script, {
|
|
54
|
+
enter(node) {
|
|
55
|
+
switch (node.kind) {
|
|
56
|
+
case 'unary':
|
|
57
|
+
signs.add(node.span.offset);
|
|
58
|
+
return;
|
|
59
|
+
case 'grouping':
|
|
60
|
+
case 'arrayLiteral':
|
|
61
|
+
grouped.add(node.span.offset);
|
|
62
|
+
return;
|
|
63
|
+
case 'ternary': {
|
|
64
|
+
const after = endOffset(node.whenTrue.span);
|
|
65
|
+
const colon = colons.find((offset) => offset >= after);
|
|
66
|
+
if (colon !== undefined) arms.add(colon);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return { signs, grouped, arms };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether the arrow of a single-line `fn` is being written.
|
|
80
|
+
*
|
|
81
|
+
* `=>` is not one token. The punctuation table has `=` and `>` and no arrow, and
|
|
82
|
+
* the lexer reads the pair as one only while the two touch, which is why the
|
|
83
|
+
* form is recognised by the two tokens being adjacent rather than by a mark. A
|
|
84
|
+
* space between them turns a function declaration into an assignment followed by
|
|
85
|
+
* a comparison, so this is the one place a formatter may not insert one.
|
|
86
|
+
*/
|
|
87
|
+
function isArrow(left: Token, right: Token): boolean {
|
|
88
|
+
return (
|
|
89
|
+
left.kind === '=' &&
|
|
90
|
+
right.kind === '>' &&
|
|
91
|
+
endOffset(left.span) === right.span.offset
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Whether a space goes between two tokens that are printed on one line.
|
|
97
|
+
*
|
|
98
|
+
* The default is a space, and every rule below takes one away. That direction is
|
|
99
|
+
* deliberate: a missing space is almost always harmless to read and never
|
|
100
|
+
* changes what the lexer sees, and an added space between two marks that have to
|
|
101
|
+
* touch is the one mistake that changes a program. The arrow above is the only
|
|
102
|
+
* case where that could happen, and it is refused first.
|
|
103
|
+
*/
|
|
104
|
+
export function spaceBetween(left: Token, right: Token, shapes: Shapes): boolean {
|
|
105
|
+
if (isArrow(left, right)) return false;
|
|
106
|
+
|
|
107
|
+
// A sign belongs to the value it signs. `not` is a word and keeps its space:
|
|
108
|
+
// taking it away would spell a different name.
|
|
109
|
+
if (shapes.signs.has(left.span.offset) && (left.kind === '-' || left.kind === '+')) return false;
|
|
110
|
+
|
|
111
|
+
// Inside a bracket, against the bracket.
|
|
112
|
+
if (left.kind === '(' || left.kind === '[') return false;
|
|
113
|
+
if (right.kind === ')' || right.kind === ']') return false;
|
|
114
|
+
|
|
115
|
+
// A comma belongs to the item before it.
|
|
116
|
+
if (right.kind === ',') return false;
|
|
117
|
+
|
|
118
|
+
// A dotted name is one name to read, so it is printed as one.
|
|
119
|
+
if (left.kind === '.' || right.kind === '.') return false;
|
|
120
|
+
|
|
121
|
+
// A bracket that is not grouping an expression is an argument list, a
|
|
122
|
+
// parameter list or an index, and all three touch what they are applied to.
|
|
123
|
+
if ((right.kind === '(' || right.kind === '[') && !shapes.grouped.has(right.span.offset)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// A type annotation's colon belongs to the name it follows. A ternary's has a
|
|
128
|
+
// space on both sides, which is what the default already gives it.
|
|
129
|
+
if (right.kind === ':' && !shapes.arms.has(right.span.offset)) return false;
|
|
130
|
+
|
|
131
|
+
return true;
|
|
132
|
+
}
|