utopia-project 0.37.6 → 0.38.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/documentation-guidelines.md +12 -7
- data/lib/utopia/project/import_map.rb +1 -0
- data/lib/utopia/project/version.rb +1 -1
- data/pages/_page.xnode +8 -13
- data/public/_components/@socketry/syntax/Syntax/CodeElement.js +293 -0
- data/public/_components/@socketry/syntax/Syntax/Errors.js +52 -0
- data/public/_components/@socketry/syntax/Syntax/Language/apache.js +49 -0
- data/public/_components/@socketry/syntax/Syntax/Language/applescript.js +157 -0
- data/public/_components/@socketry/syntax/Syntax/Language/assembly.js +42 -0
- data/public/_components/@socketry/syntax/Syntax/Language/bash-script.js +108 -0
- data/public/_components/@socketry/syntax/Syntax/Language/bash.js +32 -0
- data/public/_components/@socketry/syntax/Syntax/Language/basic.js +232 -0
- data/public/_components/@socketry/syntax/Syntax/Language/c++.js +1 -0
- data/public/_components/@socketry/syntax/Syntax/Language/c.js +1 -0
- data/public/_components/@socketry/syntax/Syntax/Language/clang.js +201 -0
- data/public/_components/@socketry/syntax/Syntax/Language/cpp.js +1 -0
- data/public/_components/@socketry/syntax/Syntax/Language/csharp.js +166 -0
- data/public/_components/@socketry/syntax/Syntax/Language/css.js +244 -0
- data/public/_components/@socketry/syntax/Syntax/Language/diff.js +24 -0
- data/public/_components/@socketry/syntax/Syntax/Language/go.js +135 -0
- data/public/_components/@socketry/syntax/Syntax/Language/haskell.js +110 -0
- data/public/_components/@socketry/syntax/Syntax/Language/html.js +69 -0
- data/public/_components/@socketry/syntax/Syntax/Language/io.js +68 -0
- data/public/_components/@socketry/syntax/Syntax/Language/java.js +134 -0
- data/public/_components/@socketry/syntax/Syntax/Language/javascript.js +89 -0
- data/public/_components/@socketry/syntax/Syntax/Language/json.js +36 -0
- data/public/_components/@socketry/syntax/Syntax/Language/lisp.js +38 -0
- data/public/_components/@socketry/syntax/Syntax/Language/lua.js +87 -0
- data/public/_components/@socketry/syntax/Syntax/Language/markdown.js +112 -0
- data/public/_components/@socketry/syntax/Syntax/Language/nginx.js +37 -0
- data/public/_components/@socketry/syntax/Syntax/Language/objective-c.js +1 -0
- data/public/_components/@socketry/syntax/Syntax/Language/ocaml.js +225 -0
- data/public/_components/@socketry/syntax/Syntax/Language/pascal.js +166 -0
- data/public/_components/@socketry/syntax/Syntax/Language/patch.js +2 -0
- data/public/_components/@socketry/syntax/Syntax/Language/perl5.js +317 -0
- data/public/_components/@socketry/syntax/Syntax/Language/php-script.js +112 -0
- data/public/_components/@socketry/syntax/Syntax/Language/php.js +18 -0
- data/public/_components/@socketry/syntax/Syntax/Language/plain.js +20 -0
- data/public/_components/@socketry/syntax/Syntax/Language/protobuf.js +77 -0
- data/public/_components/@socketry/syntax/Syntax/Language/python.js +208 -0
- data/public/_components/@socketry/syntax/Syntax/Language/ruby.js +124 -0
- data/public/_components/@socketry/syntax/Syntax/Language/scala.js +81 -0
- data/public/_components/@socketry/syntax/Syntax/Language/smalltalk.js +30 -0
- data/public/_components/@socketry/syntax/Syntax/Language/sql.js +865 -0
- data/public/_components/@socketry/syntax/Syntax/Language/super-collider.js +70 -0
- data/public/_components/@socketry/syntax/Syntax/Language/swift.js +176 -0
- data/public/_components/@socketry/syntax/Syntax/Language/xml.js +76 -0
- data/public/_components/@socketry/syntax/Syntax/Language/xrb.js +33 -0
- data/public/_components/@socketry/syntax/Syntax/Language/yaml.js +29 -0
- data/public/_components/@socketry/syntax/Syntax/Language.js +276 -0
- data/public/_components/@socketry/syntax/Syntax/Loader.js +78 -0
- data/public/_components/@socketry/syntax/Syntax/Match.js +546 -0
- data/public/_components/@socketry/syntax/Syntax/Rule.js +306 -0
- data/public/_components/@socketry/syntax/Syntax.js +356 -0
- data/public/_components/@socketry/syntax/license.md +21 -0
- data/public/_components/@socketry/syntax/package.json +43 -0
- data/public/_components/@socketry/syntax/readme.md +162 -0
- data/public/_components/@socketry/syntax/themes/base/apache.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/applescript.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/assembly.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/bash.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/basic.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/c.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/clang.css +0 -0
- data/public/_components/@socketry/syntax/themes/base/csharp.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/css.css +22 -0
- data/public/_components/@socketry/syntax/themes/base/diff.css +48 -0
- data/public/_components/@socketry/syntax/themes/base/go.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/haskell.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/html.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/io.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/java.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/javascript.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/json.css +41 -0
- data/public/_components/@socketry/syntax/themes/base/lisp.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/lua.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/markdown.css +16 -0
- data/public/_components/@socketry/syntax/themes/base/nginx.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/ocaml.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/pascal.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/perl5.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/php-script.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/php.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/plain.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/protobuf.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/python.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/ruby.css +23 -0
- data/public/_components/@socketry/syntax/themes/base/scala.css +3 -0
- data/public/_components/@socketry/syntax/themes/base/smalltalk.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/sql.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/super-collider.css +33 -0
- data/public/_components/@socketry/syntax/themes/base/swift.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/syntax.css +63 -0
- data/public/_components/@socketry/syntax/themes/base/xml.css +1 -0
- data/public/_components/@socketry/syntax/themes/base/xrb.css +29 -0
- data/public/_components/@socketry/syntax/themes/base/yaml.css +1 -0
- data/public/_components/@socketry/syntax/themes/theming.md +233 -0
- data/public/_static/sidebar.js +55 -22
- data/public/_static/site.css +0 -4
- data.tar.gz.sig +0 -0
- metadata +94 -1
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export class Loader {
|
|
2
|
+
#cache = new Map();
|
|
3
|
+
#pending = new Map();
|
|
4
|
+
#load;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a new loader
|
|
8
|
+
* @param {Function} load - Async function that resolves the key to a resource.
|
|
9
|
+
*/
|
|
10
|
+
constructor(load) {
|
|
11
|
+
this.#load = load;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load a resource with automatic deduplication and caching.
|
|
16
|
+
* @param {string|URL} key - Unique identifier for the resource.
|
|
17
|
+
* @returns {Promise<any>} The loaded resource.
|
|
18
|
+
*/
|
|
19
|
+
async load(key) {
|
|
20
|
+
const id = key.toString();
|
|
21
|
+
|
|
22
|
+
// Return cached result if available:
|
|
23
|
+
if (this.#cache.has(id)) {
|
|
24
|
+
return this.#cache.get(id);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Return pending promise if already fetching:
|
|
28
|
+
if (this.#pending.has(id)) {
|
|
29
|
+
return this.#pending.get(id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Start new fetch and track it:
|
|
33
|
+
const promise = (async () => {
|
|
34
|
+
try {
|
|
35
|
+
const result = await this.#load(this, key);
|
|
36
|
+
this.#cache.set(id, result);
|
|
37
|
+
return result;
|
|
38
|
+
} finally {
|
|
39
|
+
this.#pending.delete(id);
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
this.#pending.set(id, promise);
|
|
44
|
+
return promise;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a resource is cached
|
|
49
|
+
*/
|
|
50
|
+
has(key) {
|
|
51
|
+
return this.#cache.has(key.toString());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get a cached resource (synchronous)
|
|
56
|
+
*/
|
|
57
|
+
get(key) {
|
|
58
|
+
return this.#cache.get(key.toString());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set a resource in the cache
|
|
63
|
+
*/
|
|
64
|
+
set(key, value) {
|
|
65
|
+
this.#cache.set(key.toString(), value);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Clear the cache
|
|
71
|
+
*/
|
|
72
|
+
clear() {
|
|
73
|
+
this.#cache.clear();
|
|
74
|
+
this.#pending.clear();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default Loader;
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
export class Match {
|
|
2
|
+
// Public properties
|
|
3
|
+
offset;
|
|
4
|
+
endOffset;
|
|
5
|
+
length;
|
|
6
|
+
expression;
|
|
7
|
+
type;
|
|
8
|
+
value;
|
|
9
|
+
children = [];
|
|
10
|
+
parent = null;
|
|
11
|
+
// A pointer to the next match if a match is bisected.
|
|
12
|
+
next = null;
|
|
13
|
+
complete;
|
|
14
|
+
|
|
15
|
+
constructor(offset, length, expression, value) {
|
|
16
|
+
this.offset = offset;
|
|
17
|
+
this.endOffset = offset + length;
|
|
18
|
+
this.length = length;
|
|
19
|
+
|
|
20
|
+
// Handle expression being either a string (type) or an object
|
|
21
|
+
if (typeof expression === 'string') {
|
|
22
|
+
this.type = expression;
|
|
23
|
+
this.expression = {type: expression};
|
|
24
|
+
} else {
|
|
25
|
+
this.expression = expression;
|
|
26
|
+
this.type = expression?.type;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.value = value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Shifts an entire tree forward or backwards.
|
|
34
|
+
*/
|
|
35
|
+
shift(offset, text) {
|
|
36
|
+
this.adjust(offset, null, text);
|
|
37
|
+
|
|
38
|
+
for (const child of this.children) {
|
|
39
|
+
child.shift(offset, text);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Adjust the current match to have different offset and length.
|
|
45
|
+
*/
|
|
46
|
+
adjust(offset, length, text) {
|
|
47
|
+
this.offset += offset;
|
|
48
|
+
this.endOffset += offset;
|
|
49
|
+
|
|
50
|
+
if (length) {
|
|
51
|
+
this.length = length;
|
|
52
|
+
this.endOffset = this.offset + length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (text) {
|
|
56
|
+
this.value = text.substr(this.offset, this.length);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sort helper for sorting matches in forward order
|
|
62
|
+
*/
|
|
63
|
+
static sort(a, b) {
|
|
64
|
+
return a.offset - b.offset || b.length - a.length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Is the given match contained in the range of the parent match?
|
|
69
|
+
*/
|
|
70
|
+
contains(match) {
|
|
71
|
+
return match.offset >= this.offset && match.endOffset <= this.endOffset;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Default reduce callback - converts nodes to DOM
|
|
76
|
+
*/
|
|
77
|
+
static defaultReduceCallback(node, container) {
|
|
78
|
+
// We avoid using complex DOM manipulation for performance
|
|
79
|
+
if (typeof node === 'string') {
|
|
80
|
+
node = document.createTextNode(node);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
container.appendChild(node);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert a tree of matches into DOM nodes
|
|
88
|
+
*/
|
|
89
|
+
reduce(append, process) {
|
|
90
|
+
let start = this.offset;
|
|
91
|
+
let container = null;
|
|
92
|
+
|
|
93
|
+
if (this.expression && this.expression.element) {
|
|
94
|
+
container = this.expression.element.cloneNode(false);
|
|
95
|
+
} else {
|
|
96
|
+
container = document.createElement('span');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
append = append || Match.defaultReduceCallback;
|
|
100
|
+
|
|
101
|
+
if (this.expression && this.expression.type) {
|
|
102
|
+
if (container.className.length > 0) {
|
|
103
|
+
container.className += ' ';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
container.className += this.expression.type;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const child of this.children) {
|
|
110
|
+
const end = child.offset;
|
|
111
|
+
|
|
112
|
+
if (child.offset < this.offset) {
|
|
113
|
+
console.warn(
|
|
114
|
+
'Syntax Warning: Offset of child',
|
|
115
|
+
child,
|
|
116
|
+
'is before offset of parent',
|
|
117
|
+
this
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const text = this.value.substr(start - this.offset, end - start);
|
|
122
|
+
|
|
123
|
+
append(text, container);
|
|
124
|
+
append(child.reduce(append, process), container);
|
|
125
|
+
|
|
126
|
+
start = child.endOffset;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (start === this.offset) {
|
|
130
|
+
append(this.value, container);
|
|
131
|
+
} else if (start < this.endOffset) {
|
|
132
|
+
append(
|
|
133
|
+
this.value.substr(start - this.offset, this.endOffset - start),
|
|
134
|
+
container
|
|
135
|
+
);
|
|
136
|
+
} else if (start > this.endOffset) {
|
|
137
|
+
console.warn(
|
|
138
|
+
'Syntax Warning: Start position ' +
|
|
139
|
+
start +
|
|
140
|
+
' exceeds end of value ' +
|
|
141
|
+
this.endOffset
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (process) {
|
|
146
|
+
container = process(container, this);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return container;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Main nesting check - can a match contain the given match?
|
|
154
|
+
*/
|
|
155
|
+
canContain(match) {
|
|
156
|
+
// This is a special conditional for explicitly added ranges by the user.
|
|
157
|
+
if (match.expression.force) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Can't add anything into complete trees.
|
|
162
|
+
if (this.complete) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// match.expression.only will be checked on insertion using this.canHaveChild(match)
|
|
167
|
+
if (match.expression.only) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If allow is undefined, default behaviour is no children.
|
|
172
|
+
if (typeof this.expression.allow === 'undefined') {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// false if {disallow: [..., type, ...]}
|
|
177
|
+
if (
|
|
178
|
+
Array.isArray(this.expression.disallow) &&
|
|
179
|
+
this.expression.disallow.includes(match.expression.type)
|
|
180
|
+
) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// true if {allow: '*'}
|
|
185
|
+
if (this.expression.allow === '*') {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// true if {allow: [..., type, ...]}
|
|
190
|
+
if (
|
|
191
|
+
Array.isArray(this.expression.allow) &&
|
|
192
|
+
this.expression.allow.includes(match.expression.type)
|
|
193
|
+
) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Return true if the given match can be spliced in as a child.
|
|
202
|
+
*/
|
|
203
|
+
canHaveChild(match) {
|
|
204
|
+
const only = match.expression.only;
|
|
205
|
+
|
|
206
|
+
if (only) {
|
|
207
|
+
let cur = this;
|
|
208
|
+
|
|
209
|
+
while (cur !== null) {
|
|
210
|
+
if (only.includes(cur.expression.type)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
cur = cur.parent;
|
|
215
|
+
|
|
216
|
+
// We don't traverse into other trees.
|
|
217
|
+
if (cur && cur.complete) {
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Add a child into the list of children for a given match.
|
|
230
|
+
*/
|
|
231
|
+
#splice(i, match) {
|
|
232
|
+
if (this.canHaveChild(match)) {
|
|
233
|
+
this.children.splice(i, 0, match);
|
|
234
|
+
match.parent = this;
|
|
235
|
+
|
|
236
|
+
// For matches added using tags.
|
|
237
|
+
if (!match.expression.owner) {
|
|
238
|
+
match.expression.owner = this.expression.owner;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return this;
|
|
242
|
+
} else {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Insert a match, potentially splitting the tree to fit.
|
|
249
|
+
*/
|
|
250
|
+
insert(match, whole) {
|
|
251
|
+
if (!this.contains(match)) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (whole) {
|
|
256
|
+
let top = this,
|
|
257
|
+
i = 0;
|
|
258
|
+
while (i < top.children.length) {
|
|
259
|
+
if (top.children[i].contains(match)) {
|
|
260
|
+
top = top.children[i];
|
|
261
|
+
i = 0;
|
|
262
|
+
} else {
|
|
263
|
+
i += 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return top.#insertWhole(match);
|
|
268
|
+
} else {
|
|
269
|
+
return this.#insert(match);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Insert a whole match, splitting children as needed.
|
|
275
|
+
*/
|
|
276
|
+
#insertWhole(match) {
|
|
277
|
+
const parts = this.bisectAtOffsets([match.offset, match.endOffset]);
|
|
278
|
+
this.children = [];
|
|
279
|
+
|
|
280
|
+
if (parts[0]) {
|
|
281
|
+
this.children = this.children.concat(parts[0].children);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (parts[1]) {
|
|
285
|
+
match.children = [];
|
|
286
|
+
|
|
287
|
+
// Update the match's expression based on the current position in the tree:
|
|
288
|
+
if (this.expression && this.expression.owner) {
|
|
289
|
+
match.expression =
|
|
290
|
+
this.expression.owner.getRuleForType(match.expression.type) ||
|
|
291
|
+
match.expression;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const child of parts[1].children) {
|
|
295
|
+
if (match.canContain(child)) {
|
|
296
|
+
match.children.push(child);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.children.push(match);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (parts[2]) {
|
|
304
|
+
this.children = this.children.concat(parts[2].children);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Insert at end - optimized for sorted insertion.
|
|
312
|
+
*/
|
|
313
|
+
insertAtEnd(match) {
|
|
314
|
+
if (!this.contains(match)) {
|
|
315
|
+
console.error('Syntax Error: Child is not contained in parent node!');
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (!this.canContain(match)) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (this.children.length > 0) {
|
|
324
|
+
const i = this.children.length - 1;
|
|
325
|
+
const child = this.children[i];
|
|
326
|
+
|
|
327
|
+
if (match.offset < child.offset) {
|
|
328
|
+
// Displacement: Before or LHS Overlap
|
|
329
|
+
if (match.force) {
|
|
330
|
+
return this.#insert(match);
|
|
331
|
+
} else {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
} else if (match.offset < child.endOffset) {
|
|
335
|
+
if (match.endOffset <= child.endOffset) {
|
|
336
|
+
// Displacement: Contains
|
|
337
|
+
return child.insertAtEnd(match);
|
|
338
|
+
} else {
|
|
339
|
+
// Displacement: RHS Overlap
|
|
340
|
+
if (match.force) {
|
|
341
|
+
return this.#insert(match);
|
|
342
|
+
} else {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
// Displacement: After
|
|
348
|
+
return this.#splice(i + 1, match);
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
// Displacement: Contains [but currently no children]
|
|
352
|
+
return this.#splice(0, match);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* General insertion function that splits match over children.
|
|
358
|
+
*/
|
|
359
|
+
#insert(match) {
|
|
360
|
+
if (this.children.length === 0) {
|
|
361
|
+
return this.#splice(0, match);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
for (let i = 0; i < this.children.length; i += 1) {
|
|
365
|
+
const child = this.children[i];
|
|
366
|
+
|
|
367
|
+
// If the match ends before this child, it must be before it.
|
|
368
|
+
if (match.endOffset <= child.offset) {
|
|
369
|
+
return this.#splice(i, match);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// If the match starts after this child, we continue.
|
|
373
|
+
if (match.offset >= child.endOffset) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// First, the easiest case:
|
|
378
|
+
if (child.contains(match)) {
|
|
379
|
+
return child.#insert(match);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const parts = match.bisectAtOffsets([child.offset, child.endOffset]);
|
|
383
|
+
|
|
384
|
+
if (parts[0]) {
|
|
385
|
+
this.#splice(i, parts[0]);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (parts[1]) {
|
|
389
|
+
child.insert(parts[1]);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Continue insertion at this level with remainder.
|
|
393
|
+
if (parts[2]) {
|
|
394
|
+
match = parts[2];
|
|
395
|
+
} else {
|
|
396
|
+
return this;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// If we got this far, insert at the end.
|
|
401
|
+
this.#splice(this.children.length, match);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Recursively bisect the tree at given offsets.
|
|
406
|
+
*/
|
|
407
|
+
bisectAtOffsets(splits) {
|
|
408
|
+
const parts = [];
|
|
409
|
+
let start = this.offset;
|
|
410
|
+
let prev = null;
|
|
411
|
+
const children = [...this.children];
|
|
412
|
+
|
|
413
|
+
// Copy the array so we can modify it.
|
|
414
|
+
splits = splits.slice(0);
|
|
415
|
+
|
|
416
|
+
// We need to split including the last part.
|
|
417
|
+
splits.push(this.endOffset);
|
|
418
|
+
|
|
419
|
+
splits.sort((a, b) => a - b);
|
|
420
|
+
|
|
421
|
+
for (let i = 0; i < splits.length; i += 1) {
|
|
422
|
+
const offset = splits[i];
|
|
423
|
+
|
|
424
|
+
if (offset > this.endOffset) {
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (offset < this.offset || offset - start === 0) {
|
|
429
|
+
parts.push(null);
|
|
430
|
+
start = offset;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (start < this.offset) {
|
|
435
|
+
start = this.offset;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const match = new Match(start, offset - start, this.expression);
|
|
439
|
+
match.value = this.value.substr(start - this.offset, match.length);
|
|
440
|
+
|
|
441
|
+
if (prev) {
|
|
442
|
+
prev.next = match;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
prev = match;
|
|
446
|
+
start = match.endOffset;
|
|
447
|
+
parts.push(match);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
splits.length = parts.length;
|
|
451
|
+
|
|
452
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
453
|
+
if (parts[i] === null) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
while (children.length > 0) {
|
|
458
|
+
if (children[0].endOffset <= parts[i].endOffset) {
|
|
459
|
+
parts[i].children.push(children.shift());
|
|
460
|
+
} else {
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (children.length) {
|
|
466
|
+
if (children[0].offset < parts[i].endOffset) {
|
|
467
|
+
const children_parts = children.shift().bisectAtOffsets(splits);
|
|
468
|
+
let j = 0;
|
|
469
|
+
|
|
470
|
+
for (; j < children_parts.length; j += 1) {
|
|
471
|
+
if (children_parts[j] === null) continue;
|
|
472
|
+
|
|
473
|
+
parts[i + j].children.push(children_parts[j]);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
i += children_parts.length - 2;
|
|
477
|
+
splits.splice(0, children_parts.length - 2);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
splits.shift();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (children.length) {
|
|
485
|
+
console.error(
|
|
486
|
+
'Syntax Error: Children nodes not consumed',
|
|
487
|
+
children.length,
|
|
488
|
+
' remaining!'
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return parts;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Split a match at points that match a specific pattern.
|
|
497
|
+
*/
|
|
498
|
+
split(pattern) {
|
|
499
|
+
const splits = [];
|
|
500
|
+
|
|
501
|
+
// Clone the regex and ensure it is global
|
|
502
|
+
if (!pattern.global) {
|
|
503
|
+
pattern = new RegExp(pattern.source, pattern.flags + 'g');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
let match;
|
|
507
|
+
while ((match = pattern.exec(this.value)) !== null) {
|
|
508
|
+
splits.push(pattern.lastIndex);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const matches = this.bisectAtOffsets(splits);
|
|
512
|
+
|
|
513
|
+
// Remove any null placeholders.
|
|
514
|
+
return matches.filter(n => n);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Split into lines with indent/text structure.
|
|
519
|
+
*/
|
|
520
|
+
splitLines() {
|
|
521
|
+
const lines = this.split(/\n/g);
|
|
522
|
+
|
|
523
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
524
|
+
const line = lines[i];
|
|
525
|
+
const indentOffset = line.value.search(/\S/);
|
|
526
|
+
|
|
527
|
+
const top = new Match(line.offset, line.length, line.expression, line.value);
|
|
528
|
+
|
|
529
|
+
if (indentOffset > 0) {
|
|
530
|
+
const parts = line.bisectAtOffsets([line.offset + indentOffset]);
|
|
531
|
+
top.children = parts;
|
|
532
|
+
parts[0].expression = {type: 'indent'};
|
|
533
|
+
parts[1].expression = {type: 'text'};
|
|
534
|
+
} else {
|
|
535
|
+
line.expression = {type: 'text'};
|
|
536
|
+
top.children = [line];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
lines[i] = top;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return lines;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export default Match;
|