@formaui-net/nexo 0.0.0 → 1.0.0-preview.1
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +148 -6
- package/package.json +23 -3
- package/src/common/ArrayUtils.mjs +16 -0
- package/src/common/ObjectUtils.mjs +33 -0
- package/src/language/DefinitionLocations.mjs +41 -0
- package/src/language/DefinitionValidator.mjs +1265 -0
- package/src/language/DefinitionWriter.mjs +475 -0
- package/src/language/Dictionary.mjs +52 -0
- package/src/language/Evaluator.mjs +3480 -0
- package/src/language/ImportedVocabulary.mjs +137 -0
- package/src/language/Imports.mjs +141 -0
- package/src/language/Index.mjs +29 -0
- package/src/language/LanguageResolver.mjs +4068 -0
- package/src/language/Limits.mjs +78 -0
- package/src/language/LineStructure.mjs +385 -0
- package/src/language/Lowering.mjs +453 -0
- package/src/language/MemberSelector.mjs +89 -0
- package/src/language/Names.mjs +65 -0
- package/src/language/NexoError.mjs +145 -0
- package/src/language/Pair.mjs +21 -0
- package/src/language/ParameterBindings.mjs +361 -0
- package/src/language/ParsedTexts.mjs +62 -0
- package/src/language/Parser.mjs +1817 -0
- package/src/language/Range.mjs +51 -0
- package/src/language/RecentlyUsed.mjs +50 -0
- package/src/language/ResolvedDefinition.mjs +219 -0
- package/src/language/ResultPath.mjs +101 -0
- package/src/language/ScopeLocals.mjs +102 -0
- package/src/language/StructureTypes.mjs +48 -0
- package/src/language/SyntaxTree.mjs +203 -0
- package/src/language/TextLocations.mjs +185 -0
- package/src/language/TextPositions.mjs +23 -0
- package/src/language/Tokenizer.mjs +917 -0
- package/src/language/Type.mjs +401 -0
- package/src/language/TypeChecker.mjs +449 -0
- package/src/language/ValueCapture.mjs +315 -0
- package/src/language/Versions.mjs +14 -0
- package/src/language/vocabulary/Declarations.mjs +64 -0
- package/src/language/vocabulary/ItemPositions.mjs +48 -0
- package/src/language/vocabulary/ListOrdering.mjs +145 -0
- package/src/language/vocabulary/MemberFailure.mjs +21 -0
- package/src/language/vocabulary/NexoModule.mjs +1119 -0
- package/src/language/vocabulary/NexoRegistry.mjs +1540 -0
- package/src/language/vocabulary/TypeNameScope.mjs +63 -0
- package/src/language/vocabulary/Vocabulary.mjs +640 -0
- package/src/nexo.d.ts +743 -0
- package/src/nexo.js +338 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyntaxTree — the lossless syntax tree of a Nexo text: its constructs, each a node named after the grammar rule that produces it, holding its tokens and the nodes within it, every token with the trivia before it
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** @typedef {import('./Tokenizer.mjs').Token} Token */
|
|
6
|
+
/** @typedef {{ kind: string, children: (SyntaxNode | Token)[] }} SyntaxNode */
|
|
7
|
+
/** @typedef {{ kind: 'script', text: string, children: (SyntaxNode | Token)[] }} ScriptNode The script's import declarations, its body and the end of its text, with the trivia before it. */
|
|
8
|
+
/** @typedef {{ kind: 'whitespace' | 'line-comment' | 'block-comment', start: number, end: number }} TriviaPiece */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The kinds a node is, each a construct of the grammar, which tooling reads as they are; a rule that only passes an operand on, a level of precedence or a repetition, is none of them.
|
|
12
|
+
* A contract within Nexo until a public tooling API stabilizes it.
|
|
13
|
+
*/
|
|
14
|
+
export const SYNTAX_NODE_KINDS = Object.freeze([
|
|
15
|
+
'script',
|
|
16
|
+
'import-declaration',
|
|
17
|
+
'list-expression',
|
|
18
|
+
'dictionary-expression',
|
|
19
|
+
'dictionary-entry',
|
|
20
|
+
'conditional-expression',
|
|
21
|
+
'body',
|
|
22
|
+
'do-expression',
|
|
23
|
+
'function-expression',
|
|
24
|
+
'arrow-function',
|
|
25
|
+
'function-call',
|
|
26
|
+
'conditional-binding',
|
|
27
|
+
'disjunction',
|
|
28
|
+
'conjunction',
|
|
29
|
+
'negation',
|
|
30
|
+
'equality',
|
|
31
|
+
'comparison',
|
|
32
|
+
'additive',
|
|
33
|
+
'multiplicative',
|
|
34
|
+
'arithmetic-negation',
|
|
35
|
+
'from-end-index',
|
|
36
|
+
'range-expression',
|
|
37
|
+
'coalescing',
|
|
38
|
+
'attempt',
|
|
39
|
+
'conversion',
|
|
40
|
+
'cast',
|
|
41
|
+
'type-name',
|
|
42
|
+
'chain',
|
|
43
|
+
'namespace-access',
|
|
44
|
+
'dot-property-access',
|
|
45
|
+
'computed-property-access',
|
|
46
|
+
'member-call',
|
|
47
|
+
'indexer',
|
|
48
|
+
'extension-access',
|
|
49
|
+
'parenthesized-expression',
|
|
50
|
+
'optional-enclosed-access',
|
|
51
|
+
'member-chain',
|
|
52
|
+
'receiver',
|
|
53
|
+
'object',
|
|
54
|
+
'interpolated-string',
|
|
55
|
+
'interpolation',
|
|
56
|
+
'local',
|
|
57
|
+
'field',
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
/** The tokens that make an access optional: a property access's or a call's `?.`, an indexer's `?[` and an extension access's `?:`. */
|
|
61
|
+
const OPTIONAL_ACCESSES = new Set(['?.', '?[', '?:']);
|
|
62
|
+
|
|
63
|
+
export class SyntaxTree {
|
|
64
|
+
/** Whether a token of the type makes the access it begins optional, so it answers null for a null receiver and ends its chain there. */
|
|
65
|
+
static isOptionalAccess(tokenType) {
|
|
66
|
+
return OPTIONAL_ACCESSES.has(tokenType);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The import an import declaration writes, in a definition's shape: its module, its type where it imports one, and its alias where it gives a local name other than its own, each the word the text spells.
|
|
71
|
+
* @param {SyntaxNode} declaration
|
|
72
|
+
* @param {string} text
|
|
73
|
+
*/
|
|
74
|
+
static importOf(declaration, text) {
|
|
75
|
+
const words = declaration.children.slice(1);
|
|
76
|
+
const hasAlias = words[1]?.type === '=';
|
|
77
|
+
const source = hasAlias ? words.slice(2) : words;
|
|
78
|
+
const module = SyntaxTree.wordOf(source[0], text);
|
|
79
|
+
const type = source.length > 1 ? SyntaxTree.wordOf(source[2], text) : undefined;
|
|
80
|
+
const alias = hasAlias ? SyntaxTree.wordOf(words[0], text) : undefined;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
module,
|
|
84
|
+
...(type === undefined ? {} : { type }),
|
|
85
|
+
...(alias === undefined || alias === (type ?? module) ? {} : { alias }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The body a script holds, after its import declarations. */
|
|
90
|
+
static bodyOf(script) {
|
|
91
|
+
return script.children.at(-2);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Whether the child is a token rather than a node. */
|
|
95
|
+
static isToken(child) {
|
|
96
|
+
return child.kind === undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Where the node's first token begins, its trivia left out. */
|
|
100
|
+
static startOf(child) {
|
|
101
|
+
let current = child;
|
|
102
|
+
|
|
103
|
+
while (!SyntaxTree.isToken(current)) {
|
|
104
|
+
current = current.children[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return current.start;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Where the node's last token ends. */
|
|
111
|
+
static endOf(child) {
|
|
112
|
+
let current = child;
|
|
113
|
+
|
|
114
|
+
while (!SyntaxTree.isToken(current)) {
|
|
115
|
+
current = current.children.at(-1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return current.end;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The word a token writes, as the text spells it.
|
|
123
|
+
* @param {Token} token
|
|
124
|
+
* @param {string} text The text the token was read from.
|
|
125
|
+
*/
|
|
126
|
+
static wordOf(token, text) {
|
|
127
|
+
return text.slice(token.start, token.end);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The name of the type a cast's or a conversion's type part writes: its local name, or its module's name, a dot and its local name, whatever trivia the text puts between them.
|
|
132
|
+
* @param {SyntaxNode | Token} part A name token or a `type-name` node.
|
|
133
|
+
* @param {string} text The text the part was read from.
|
|
134
|
+
*/
|
|
135
|
+
static typeNameOf(part, text) {
|
|
136
|
+
return SyntaxTree.isToken(part) ? SyntaxTree.wordOf(part, text) : `${SyntaxTree.wordOf(part.children[0], text)}.${SyntaxTree.wordOf(part.children[2], text)}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The tokens of the node, in the order of the text.
|
|
141
|
+
* @param {SyntaxNode | Token} child
|
|
142
|
+
* @returns {Generator<Token>}
|
|
143
|
+
*/
|
|
144
|
+
static *tokensOf(child) {
|
|
145
|
+
if (SyntaxTree.isToken(child)) {
|
|
146
|
+
yield child;
|
|
147
|
+
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const each of child.children) {
|
|
152
|
+
yield* SyntaxTree.tokensOf(each);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The text the tree was read from, rebuilt from its tokens and the trivia before each, the end of the text's included.
|
|
158
|
+
* @param {ScriptNode} tree
|
|
159
|
+
*/
|
|
160
|
+
static textOf(tree) {
|
|
161
|
+
let text = '';
|
|
162
|
+
|
|
163
|
+
for (const token of SyntaxTree.tokensOf(tree)) {
|
|
164
|
+
text += tree.text.slice(token.triviaStart, token.end);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return text;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The trivia before the token, each piece whitespace, a line comment or a block comment, in the order of the text.
|
|
172
|
+
* @param {Token} token
|
|
173
|
+
* @param {string} text The text the token was read from.
|
|
174
|
+
* @returns {TriviaPiece[]}
|
|
175
|
+
*/
|
|
176
|
+
static triviaOf(token, text) {
|
|
177
|
+
const pieces = [];
|
|
178
|
+
|
|
179
|
+
for (let offset = token.triviaStart; offset < token.start;) {
|
|
180
|
+
const start = offset;
|
|
181
|
+
|
|
182
|
+
if (text.startsWith('//', offset)) {
|
|
183
|
+
const lineEnd = text.slice(offset, token.start).search(/[\n\r]/);
|
|
184
|
+
// The comment holds the line break that ends it, CRLF one, or runs to the token where none does.
|
|
185
|
+
const breakLength = lineEnd !== -1 && text.startsWith('\r\n', offset + lineEnd) ? 2 : 1;
|
|
186
|
+
|
|
187
|
+
offset = lineEnd === -1 ? token.start : offset + lineEnd + breakLength;
|
|
188
|
+
pieces.push({ kind: 'line-comment', start, end: offset });
|
|
189
|
+
} else if (text.startsWith('/*', offset)) {
|
|
190
|
+
offset = text.indexOf('*/', offset + 2) + 2;
|
|
191
|
+
pieces.push({ kind: 'block-comment', start, end: offset });
|
|
192
|
+
} else {
|
|
193
|
+
while (offset < token.start && !text.startsWith('//', offset) && !text.startsWith('/*', offset)) {
|
|
194
|
+
offset++;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pieces.push({ kind: 'whitespace', start, end: offset });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return pieces;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextLocations — where in a source text each part of its definition came from, by JSON Pointer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Lowering } from './Lowering.mjs';
|
|
6
|
+
import { Parser } from './Parser.mjs';
|
|
7
|
+
import { TextPositions } from './TextPositions.mjs';
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('./Parser.mjs').Span} Span */
|
|
10
|
+
|
|
11
|
+
export class TextLocations {
|
|
12
|
+
#text;
|
|
13
|
+
|
|
14
|
+
/** The imports a host supplied to the text, which decide how it reads. @type {readonly object[]} */
|
|
15
|
+
#suppliedImports;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The span of each part of the definition by its pointer, collected at the first location asked for, from the text read again, and kept from then on; null until then. A text reads into one definition alone, so the
|
|
19
|
+
* spans read again are the ones it was parsed with. Collecting them as each text was parsed measured a seventh of parsing, for a location most texts are never asked for, and keeping the definition it lowered until
|
|
20
|
+
* then would hold more than the text it reads from.
|
|
21
|
+
* @type {Map<string, Span> | null}
|
|
22
|
+
*/
|
|
23
|
+
#spans = null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} text The source text, which parsed.
|
|
27
|
+
* @param {readonly object[]} [suppliedImports] The imports a host supplied to it, in a definition's shape, checked already.
|
|
28
|
+
*/
|
|
29
|
+
constructor(text, suppliedImports = []) {
|
|
30
|
+
this.#text = text;
|
|
31
|
+
this.#suppliedImports = suppliedImports;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The span of each part of the definition by its pointer, collected from the text read again where none was collected yet. */
|
|
35
|
+
#getSpans() {
|
|
36
|
+
if (this.#spans === null) {
|
|
37
|
+
const { definition, spans } = Lowering.lower(new Parser(this.#text, this.#suppliedImports).parse(), this.#suppliedImports);
|
|
38
|
+
|
|
39
|
+
this.#spans = new Map();
|
|
40
|
+
definition.imports.forEach((entry, index) => {
|
|
41
|
+
const span = spans.get(entry);
|
|
42
|
+
|
|
43
|
+
if (span) {
|
|
44
|
+
this.#spans.set(`/imports/${index}`, span);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.#collect(definition.expression, '/expression', spans);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.#spans;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The text the definition was read from, from which its syntax tree is read again where a tool asks for it, since no definition keeps one. */
|
|
54
|
+
get text() {
|
|
55
|
+
return this.#text;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The text position the pointer's part began at, or the nearest enclosing part's. */
|
|
59
|
+
locate(pointer) {
|
|
60
|
+
const spans = this.#getSpans();
|
|
61
|
+
|
|
62
|
+
for (let current = pointer; ; current = current.slice(0, current.lastIndexOf('/'))) {
|
|
63
|
+
const span = spans.get(current);
|
|
64
|
+
|
|
65
|
+
if (span) {
|
|
66
|
+
return { pointer, ...TextPositions.locate(this.#text, span.start) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (current === '') {
|
|
70
|
+
return { pointer };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Records the span of the node at the pointer, and of every part beneath it. */
|
|
76
|
+
#collect(node, pointer, spans) {
|
|
77
|
+
const span = spans.get(node);
|
|
78
|
+
|
|
79
|
+
if (span) {
|
|
80
|
+
this.#spans.set(pointer, span);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A property access, an indexer, a call's member and what an object expression or an optional chain holds, each the node it continues from or ends at.
|
|
84
|
+
for (const property of ['receiver', 'callee', 'context', 'chain']) {
|
|
85
|
+
if (node[property]) {
|
|
86
|
+
this.#collect(node[property], `${pointer}/${property}`, spans);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (node.kind === 'memberAccess' && typeof node.name !== 'string') {
|
|
91
|
+
this.#collect(node.name, `${pointer}/name`, spans);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (node.argument) {
|
|
95
|
+
this.#collect(node.argument, `${pointer}/argument`, spans);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (node.kind === 'conditional') {
|
|
99
|
+
if (node.binding !== undefined) {
|
|
100
|
+
const bindingSpan = spans.get(node.binding);
|
|
101
|
+
|
|
102
|
+
if (bindingSpan) {
|
|
103
|
+
this.#spans.set(`${pointer}/binding`, bindingSpan);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.#collect(node.binding.value, `${pointer}/binding/value`, spans);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const branch of ['condition', 'consequent', 'alternative']) {
|
|
110
|
+
if (node[branch] !== undefined) {
|
|
111
|
+
this.#collect(node[branch], `${pointer}/${branch}`, spans);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (node.kind === 'equality') {
|
|
117
|
+
this.#collect(node.left, `${pointer}/left`, spans);
|
|
118
|
+
this.#collect(node.right, `${pointer}/right`, spans);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (node.kind === 'arithmetic') {
|
|
122
|
+
this.#collect(node.left, `${pointer}/left`, spans);
|
|
123
|
+
this.#collect(node.right, `${pointer}/right`, spans);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A negation's, an arithmetic negation's, a cast's, a conversion's and an attempt's operand.
|
|
127
|
+
if (node.operand) {
|
|
128
|
+
this.#collect(node.operand, `${pointer}/operand`, spans);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// An index's offset, and a range's boundaries where it holds them.
|
|
132
|
+
for (const property of ['offset', 'start', 'end']) {
|
|
133
|
+
if (node.kind === 'fromEndIndex' || node.kind === 'range') {
|
|
134
|
+
if (node[property] !== undefined) {
|
|
135
|
+
this.#collect(node[property], `${pointer}/${property}`, spans);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (node.kind === 'coalescing') {
|
|
141
|
+
this.#collect(node.value, `${pointer}/value`, spans);
|
|
142
|
+
this.#collect(node.fallback, `${pointer}/fallback`, spans);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
node.operands?.forEach((operand, index) => this.#collect(operand, `${pointer}/operands/${index}`, spans));
|
|
146
|
+
|
|
147
|
+
if (node.kind === 'interpolatedString') {
|
|
148
|
+
node.parts.forEach((part, index) => {
|
|
149
|
+
if (part.kind === 'expression') {
|
|
150
|
+
this.#collect(part.expression, `${pointer}/parts/${index}/expression`, spans);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (node.kind === 'body') {
|
|
156
|
+
this.#collect(node.result, `${pointer}/result`, spans);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (node.kind === 'function' || node.kind === 'do') {
|
|
160
|
+
this.#collect(node.body, `${pointer}/body`, spans);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (node.kind === 'call') {
|
|
164
|
+
node.arguments.forEach((argument, index) => this.#collect(argument, `${pointer}/arguments/${index}`, spans));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (node.kind === 'list') {
|
|
168
|
+
node.items.forEach((item, index) => this.#collect(item, `${pointer}/items/${index}`, spans));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// An object expression's locals and object fields, and a dictionary's entries, each spanning its head and its value.
|
|
172
|
+
for (const entriesProperty of ['locals', 'objectFields', 'entries']) {
|
|
173
|
+
node[entriesProperty]?.forEach((entry, index) => {
|
|
174
|
+
const entryPointer = `${pointer}/${entriesProperty}/${index}`;
|
|
175
|
+
const entrySpan = spans.get(entry);
|
|
176
|
+
|
|
177
|
+
if (entrySpan) {
|
|
178
|
+
this.#spans.set(entryPointer, entrySpan);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.#collect(entry.value, `${entryPointer}/value`, spans);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextPositions — where an offset stands in a text: its line and its column, as a location gives them
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class TextPositions {
|
|
6
|
+
/** The offset's line and column in the text, each line break, LF, CRLF or a lone CR, beginning a line, so CRLF advances it once and its two characters count toward the offset. */
|
|
7
|
+
static locate(text, offset) {
|
|
8
|
+
let line = 1;
|
|
9
|
+
let lineStart = 0;
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < offset; index++) {
|
|
12
|
+
const character = text[index];
|
|
13
|
+
|
|
14
|
+
// A carriage return before a line feed is one line break with it, which the line feed ends.
|
|
15
|
+
if (character === '\n' || (character === '\r' && text[index + 1] !== '\n')) {
|
|
16
|
+
line++;
|
|
17
|
+
lineStart = index + 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { offset, line, column: offset - lineStart + 1 };
|
|
22
|
+
}
|
|
23
|
+
}
|