@jxsuite/schema 5.6.0 → 6.0.2
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/README.md +1 -0
- package/defs/image-config.schema.ts +14 -2
- package/package.json +8 -4
- package/project-schema.json +3 -1
- package/schema.json +88 -1
- package/schemas/project.core.schema.json +3 -1
- package/src/document-scripts.ts +304 -0
- package/src/dynamic-tag.ts +570 -0
- package/src/guards.ts +179 -4
- package/src/jxn/complete.ts +582 -0
- package/src/jxn/document-scopes.ts +581 -0
- package/src/jxn/document.ts +6 -0
- package/src/jxn/event-handlers.ts +5 -0
- package/src/jxn/index.ts +14 -2
- package/src/jxn/outline.ts +452 -0
- package/src/jxn/parse.ts +234 -13
- package/src/jxn/schema-types.ts +299 -0
- package/src/jxn/schema-walk.ts +221 -0
- package/src/jxn/serialize.ts +113 -5
- package/src/jxn/state-types.ts +271 -0
- package/src/jxn/string-pieces.ts +240 -0
- package/src/jxn/virtual-document.ts +1077 -0
- package/src/jxn/virtual-types.ts +94 -169
- package/src/schema.ts +22 -0
- package/types.ts +16 -0
package/src/jxn/parse.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* replaced, and its own `toJson` is not used, because it turns `x (ref)"p"` into `"p"`, silently.
|
|
10
10
|
*/
|
|
11
11
|
import { InvalidKdlError, getLocation, parse } from "@bgotink/kdl";
|
|
12
|
-
import type { Document, Entry, Node } from "@bgotink/kdl";
|
|
12
|
+
import type { Document, Entry, Identifier, Node, Value } from "@bgotink/kdl";
|
|
13
13
|
import type { JsonObject, JsonValue, JxDocument } from "../../types.ts";
|
|
14
14
|
import { isJsonObject, isNumber, isString } from "../guards.ts";
|
|
15
15
|
import { normalizeObject } from "../parse.ts";
|
|
@@ -21,17 +21,48 @@ import { EVENT_HANDLER_NAMES } from "./event-handlers.ts";
|
|
|
21
21
|
/** Bytes KDL's parser admits that a person should never have in a text file. */
|
|
22
22
|
const RAW_CONTROL = /(?![\t\n\v\f\r])\p{Cc}/u;
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The annotations the grammar gives meaning to. Any other is a mistake, and not a thing to ignore.
|
|
26
|
+
*
|
|
27
|
+
* Exported for an editor's grammar, which paints the same set and is tested against this one.
|
|
28
|
+
*/
|
|
29
|
+
export const JXN_ANNOTATIONS: ReadonlySet<string> = new Set([
|
|
30
|
+
"array",
|
|
31
|
+
"export",
|
|
32
|
+
"fn",
|
|
33
|
+
"object",
|
|
34
|
+
"on",
|
|
35
|
+
"ref",
|
|
36
|
+
"server",
|
|
37
|
+
]);
|
|
26
38
|
|
|
27
39
|
/** A child node name that means something other than an element. */
|
|
28
40
|
const RESERVED_CHILDREN = new Set(["attributes", "script", "state", "style"]);
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Where single tokens are, by JSON Pointer (`spec.md` §23.3).
|
|
44
|
+
*
|
|
45
|
+
* A span says where a whole node or entry is, which is what a save needs. A type checker needs the
|
|
46
|
+
* token itself: a string with its quotes, so each character of a body can be put back where it was
|
|
47
|
+
* written, and a name alone, so renaming it in an editor rewrites the name and nothing beside it.
|
|
48
|
+
*
|
|
49
|
+
* A pointer here is built from a key as the file spells it, and a document's keys are NFC. A key
|
|
50
|
+
* the two disagree about is not found, which costs that name its mapping and never misplaces one.
|
|
51
|
+
*/
|
|
52
|
+
export interface JxnTokens {
|
|
53
|
+
/** The name of a keyed member: a node's name, or a property's. */
|
|
54
|
+
names: Map<string, JxnSpan>;
|
|
55
|
+
/** The one value a pointer holds, as written. */
|
|
56
|
+
values: Map<string, JxnSpan>;
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
/** What one read of a file is building. */
|
|
31
60
|
export interface Reader {
|
|
32
61
|
/** Where each thing came from, by JSON Pointer, when that was asked for. */
|
|
33
62
|
spans: Map<string, JxnSpan> | null;
|
|
34
63
|
text: string;
|
|
64
|
+
/** Where single tokens are, when that was asked for. */
|
|
65
|
+
tokens?: JxnTokens | undefined;
|
|
35
66
|
}
|
|
36
67
|
|
|
37
68
|
/** Set a key, refusing a repeat and a key that would be a prototype rather than data. */
|
|
@@ -58,8 +89,8 @@ export function failure(reader: Reader, at: Entry | Node, message: string): JxnE
|
|
|
58
89
|
return new JxnError(message, start === undefined ? null : locate(reader.text, start));
|
|
59
90
|
}
|
|
60
91
|
|
|
61
|
-
/** Where a node
|
|
62
|
-
function spanOf(at: Entry | Node): JxnSpan | null {
|
|
92
|
+
/** Where a node, an entry or one token of either is, from the locations the parser stored. */
|
|
93
|
+
function spanOf(at: Entry | Identifier | Node | Value): JxnSpan | null {
|
|
63
94
|
const location = getLocation(at);
|
|
64
95
|
return location ? { end: location.end.offset, start: location.start.offset } : null;
|
|
65
96
|
}
|
|
@@ -72,6 +103,35 @@ export function mark(reader: Reader, pointer: string, at: Entry | Node): void {
|
|
|
72
103
|
}
|
|
73
104
|
}
|
|
74
105
|
|
|
106
|
+
/** Record where a pointer's name was written. */
|
|
107
|
+
function markName(reader: Reader, pointer: string, at: Identifier | null): void {
|
|
108
|
+
const span = reader.tokens && at ? spanOf(at) : null;
|
|
109
|
+
if (span) {
|
|
110
|
+
reader.tokens?.names.set(pointer, span);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Record where a pointer's one value was written.
|
|
116
|
+
*
|
|
117
|
+
* The parser's location for an argument starts at its annotation, so `(ref)"#/state/a"` would be a
|
|
118
|
+
* token that begins five characters before the string does. What it kept of how the value was
|
|
119
|
+
* spelled is the token itself, and the token ends where the location does.
|
|
120
|
+
*/
|
|
121
|
+
function markValue(reader: Reader, pointer: string, at: Entry | undefined): void {
|
|
122
|
+
const span = reader.tokens && at ? spanOf(at.value) : null;
|
|
123
|
+
if (span) {
|
|
124
|
+
const written = at?.value.representation;
|
|
125
|
+
const start = written === undefined ? span.start : span.end - written.length;
|
|
126
|
+
reader.tokens?.values.set(pointer, { end: span.end, start });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The entries of a node that carry no name, in order. */
|
|
131
|
+
function positionalEntries(node: Node): Entry[] {
|
|
132
|
+
return node.entries.filter((entry) => !entry.name);
|
|
133
|
+
}
|
|
134
|
+
|
|
75
135
|
/** A JSON Pointer token. */
|
|
76
136
|
export function token(key: string): string {
|
|
77
137
|
return key.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
@@ -80,7 +140,7 @@ export function token(key: string): string {
|
|
|
80
140
|
/** A node's annotation, checked. */
|
|
81
141
|
export function annotationOf(reader: Reader, node: Node): string | null {
|
|
82
142
|
const tag = node.getTag();
|
|
83
|
-
if (tag !== null && !
|
|
143
|
+
if (tag !== null && !JXN_ANNOTATIONS.has(tag)) {
|
|
84
144
|
throw failure(reader, node, `(${tag}) is not an annotation Jx Notation has`);
|
|
85
145
|
}
|
|
86
146
|
if (tag === "ref") {
|
|
@@ -140,7 +200,9 @@ function readFunction(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
|
140
200
|
const kids = childrenOf(node);
|
|
141
201
|
for (const entry of properties(node)) {
|
|
142
202
|
setKey(fn, nameOf(entry), valueOf(reader, entry), reader, entry);
|
|
203
|
+
markValue(reader, `${pointer}/${token(nameOf(entry))}`, entry);
|
|
143
204
|
}
|
|
205
|
+
const written = reader.tokens ? positionalEntries(node) : [];
|
|
144
206
|
if (kids.length > 0) {
|
|
145
207
|
if (values.length > 0) {
|
|
146
208
|
setKey(fn, "parameters", values, reader, node);
|
|
@@ -153,28 +215,150 @@ function readFunction(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
|
153
215
|
setKey(fn, "parameters", parameters, reader, node);
|
|
154
216
|
}
|
|
155
217
|
setKey(fn, "body", body, reader, node);
|
|
218
|
+
markValue(reader, `${pointer}/body`, written.pop());
|
|
219
|
+
}
|
|
220
|
+
for (const [index, entry] of written.entries()) {
|
|
221
|
+
markValue(reader, `${pointer}/parameters/${index}`, entry);
|
|
222
|
+
}
|
|
223
|
+
mark(reader, pointer, node);
|
|
224
|
+
return fn;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* An export of the document's client module (`spec.md` §23.2): a Function whose `$src` is
|
|
229
|
+
* `#client`. The node's name is the export unless `$export` says another.
|
|
230
|
+
*/
|
|
231
|
+
function readExport(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
232
|
+
const fn: JsonObject = { $prototype: "Function", $src: "#client" };
|
|
233
|
+
if (positionals(reader, node).length > 0 || childrenOf(node).length > 0) {
|
|
234
|
+
throw failure(
|
|
235
|
+
reader,
|
|
236
|
+
node,
|
|
237
|
+
"(export) names a function the document's script exports, and holds no body of its own",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
for (const entry of properties(node)) {
|
|
241
|
+
setKey(fn, nameOf(entry), valueOf(reader, entry), reader, entry);
|
|
242
|
+
markValue(reader, `${pointer}/${token(nameOf(entry))}`, entry);
|
|
243
|
+
}
|
|
244
|
+
if (!Object.hasOwn(fn, "$export")) {
|
|
245
|
+
fn.$export = node.getName().normalize("NFC");
|
|
156
246
|
}
|
|
157
247
|
mark(reader, pointer, node);
|
|
158
248
|
return fn;
|
|
159
249
|
}
|
|
160
250
|
|
|
161
|
-
/**
|
|
162
|
-
function
|
|
251
|
+
/**
|
|
252
|
+
* A function of the document's server module (`spec.md` §23.2, §11.4): a `timing: "server"` entry
|
|
253
|
+
* whose `$src` is `#server`, exported under the node's name, with the node's named values as its
|
|
254
|
+
* `arguments`.
|
|
255
|
+
*/
|
|
256
|
+
function readServer(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
257
|
+
if (positionals(reader, node).length > 0 || childrenOf(node).length > 0) {
|
|
258
|
+
throw failure(
|
|
259
|
+
reader,
|
|
260
|
+
node,
|
|
261
|
+
"(server) names a function the document's server script exports. Its arguments are written name=value",
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const entry: JsonObject = {
|
|
265
|
+
$export: node.getName().normalize("NFC"),
|
|
266
|
+
$src: "#server",
|
|
267
|
+
timing: "server",
|
|
268
|
+
};
|
|
269
|
+
const named = properties(node);
|
|
270
|
+
if (named.length > 0) {
|
|
271
|
+
const args: JsonObject = {};
|
|
272
|
+
for (const argument of named) {
|
|
273
|
+
const key = nameOf(argument);
|
|
274
|
+
setKey(args, key, valueOf(reader, argument), reader, argument);
|
|
275
|
+
mark(reader, `${pointer}/arguments/${token(key)}`, argument);
|
|
276
|
+
markValue(reader, `${pointer}/arguments/${token(key)}`, argument);
|
|
277
|
+
}
|
|
278
|
+
entry.arguments = args;
|
|
279
|
+
}
|
|
280
|
+
mark(reader, pointer, node);
|
|
281
|
+
return entry;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** The `$script` objects made from `script` nodes, which a second `script` node may add a side to. */
|
|
285
|
+
const SCRIPT_BLOCKS = new WeakSet<JsonObject>();
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* A `script` node of the root element: one of the document's two modules (`spec.md` §23.2).
|
|
289
|
+
*
|
|
290
|
+
* @param reader The read.
|
|
291
|
+
* @param node The `script` node.
|
|
292
|
+
* @param element The root element, which gains `$script.client` or `$script.server`.
|
|
293
|
+
*/
|
|
294
|
+
function readScript(reader: Reader, node: Node, element: JsonObject): void {
|
|
295
|
+
const named: JsonObject = {};
|
|
296
|
+
for (const entry of properties(node)) {
|
|
297
|
+
const key = nameOf(entry);
|
|
298
|
+
setKey(named, key, valueOf(reader, entry), reader, entry);
|
|
299
|
+
if (key !== "timing" || named[key] !== "server") {
|
|
300
|
+
throw failure(reader, entry, "A script takes timing=server, and nothing else");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const values = positionals(reader, node);
|
|
304
|
+
const [text] = values;
|
|
305
|
+
if (values.length !== 1 || !isString(text) || childrenOf(node).length > 0) {
|
|
306
|
+
throw failure(reader, node, "A script holds one string, which is the text of its module");
|
|
307
|
+
}
|
|
308
|
+
const side = Object.hasOwn(named, "timing") ? "server" : "client";
|
|
309
|
+
let scripts = element.$script;
|
|
310
|
+
if (!isJsonObject(scripts) || !SCRIPT_BLOCKS.has(scripts)) {
|
|
311
|
+
scripts = {};
|
|
312
|
+
SCRIPT_BLOCKS.add(scripts);
|
|
313
|
+
setKey(element, "$script", scripts, reader, node);
|
|
314
|
+
}
|
|
315
|
+
setKey(scripts, side, text, reader, node);
|
|
316
|
+
mark(reader, `/$script/${side}`, node);
|
|
317
|
+
markValue(reader, `/$script/${side}`, positionalEntries(node)[0]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Record where each value of a list written on one line is, under the index it has in the list. */
|
|
321
|
+
function markItems(reader: Reader, pointer: string, node: Node): void {
|
|
322
|
+
for (const [index, entry] of (reader.tokens ? positionalEntries(node) : []).entries()) {
|
|
323
|
+
markValue(reader, `${pointer}/${index}`, entry);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The items of a block made of `-` nodes.
|
|
329
|
+
*
|
|
330
|
+
* @param reader The file being read.
|
|
331
|
+
* @param kids The block.
|
|
332
|
+
* @param pointer Where the list is.
|
|
333
|
+
* @param before How many items the list holds ahead of the block, written on the node's own line.
|
|
334
|
+
*/
|
|
335
|
+
function readItems(
|
|
336
|
+
reader: Reader,
|
|
337
|
+
kids: readonly Node[],
|
|
338
|
+
pointer: string,
|
|
339
|
+
before = 0,
|
|
340
|
+
): JsonValue[] {
|
|
163
341
|
return kids.map((kid, index) => {
|
|
164
342
|
if (kid.getName() !== "-") {
|
|
165
343
|
throw failure(reader, kid, `A list holds only "-" items, and this is "${kid.getName()}"`);
|
|
166
344
|
}
|
|
167
|
-
return readValue(reader, kid, `${pointer}/${index}`);
|
|
345
|
+
return readValue(reader, kid, `${pointer}/${before + index}`);
|
|
168
346
|
});
|
|
169
347
|
}
|
|
170
348
|
|
|
171
|
-
/** A JSON block: JSON-in-KDL, plus `(ref)` and `(
|
|
349
|
+
/** A JSON block: JSON-in-KDL, plus `(ref)`, `(fn)`, `(export)` and `(server)`. */
|
|
172
350
|
export function readValue(reader: Reader, node: Node, pointer: string): JsonValue {
|
|
173
351
|
const tag = annotationOf(reader, node);
|
|
174
352
|
mark(reader, pointer, node);
|
|
175
353
|
if (tag === "fn") {
|
|
176
354
|
return readFunction(reader, node, pointer);
|
|
177
355
|
}
|
|
356
|
+
if (tag === "export") {
|
|
357
|
+
return readExport(reader, node, pointer);
|
|
358
|
+
}
|
|
359
|
+
if (tag === "server") {
|
|
360
|
+
return readServer(reader, node, pointer);
|
|
361
|
+
}
|
|
178
362
|
const values = positionals(reader, node);
|
|
179
363
|
const props = properties(node);
|
|
180
364
|
const kids = childrenOf(node);
|
|
@@ -182,19 +366,22 @@ export function readValue(reader: Reader, node: Node, pointer: string): JsonValu
|
|
|
182
366
|
if (props.length > 0) {
|
|
183
367
|
throw failure(reader, node, "(array) holds items, and these are named");
|
|
184
368
|
}
|
|
185
|
-
|
|
369
|
+
markItems(reader, pointer, node);
|
|
370
|
+
return [...values, ...readItems(reader, kids, pointer, values.length)];
|
|
186
371
|
}
|
|
187
372
|
if (tag !== "object") {
|
|
188
373
|
if (values.length > 1) {
|
|
189
374
|
if (props.length > 0 || kids.length > 0) {
|
|
190
375
|
throw failure(reader, node, "A list of values cannot also have names or a block");
|
|
191
376
|
}
|
|
377
|
+
markItems(reader, pointer, node);
|
|
192
378
|
return values;
|
|
193
379
|
}
|
|
194
380
|
if (values.length === 1) {
|
|
195
381
|
if (props.length > 0 || kids.length > 0) {
|
|
196
382
|
throw failure(reader, node, "A value cannot also have names or a block");
|
|
197
383
|
}
|
|
384
|
+
markValue(reader, pointer, positionalEntries(node)[0]);
|
|
198
385
|
return values[0] ?? null;
|
|
199
386
|
}
|
|
200
387
|
if (kids.length > 0 && props.length === 0 && kids.every((kid) => kid.getName() === "-")) {
|
|
@@ -208,10 +395,13 @@ export function readValue(reader: Reader, node: Node, pointer: string): JsonValu
|
|
|
208
395
|
const key = nameOf(entry);
|
|
209
396
|
setKey(object, key, valueOf(reader, entry), reader, entry);
|
|
210
397
|
mark(reader, `${pointer}/${token(key)}`, entry);
|
|
398
|
+
markName(reader, `${pointer}/${token(key)}`, entry.name);
|
|
399
|
+
markValue(reader, `${pointer}/${token(key)}`, entry);
|
|
211
400
|
}
|
|
212
401
|
for (const kid of kids) {
|
|
213
402
|
const key = kid.getName();
|
|
214
403
|
setKey(object, key, readValue(reader, kid, `${pointer}/${token(key)}`), reader, kid);
|
|
404
|
+
markName(reader, `${pointer}/${token(key)}`, kid.name);
|
|
215
405
|
}
|
|
216
406
|
return object;
|
|
217
407
|
}
|
|
@@ -227,6 +417,7 @@ export function readStyle(reader: Reader, node: Node, pointer: string): JsonObje
|
|
|
227
417
|
const key = nameOf(entry);
|
|
228
418
|
setKey(style, key, valueOf(reader, entry), reader, entry);
|
|
229
419
|
mark(reader, `${pointer}/${token(key)}`, entry);
|
|
420
|
+
markValue(reader, `${pointer}/${token(key)}`, entry);
|
|
230
421
|
}
|
|
231
422
|
for (const kid of childrenOf(node)) {
|
|
232
423
|
const key = kid.getName();
|
|
@@ -257,6 +448,7 @@ export function readStyle(reader: Reader, node: Node, pointer: string): JsonObje
|
|
|
257
448
|
}
|
|
258
449
|
setKey(style, key, values[0] ?? null, reader, kid);
|
|
259
450
|
mark(reader, child, kid);
|
|
451
|
+
markValue(reader, child, positionalEntries(kid)[0]);
|
|
260
452
|
} else {
|
|
261
453
|
setKey(style, key, readStyle(reader, kid, child), reader, kid);
|
|
262
454
|
}
|
|
@@ -282,7 +474,9 @@ function readArrayRow(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
|
282
474
|
}
|
|
283
475
|
for (const entry of properties(node)) {
|
|
284
476
|
const key = nameOf(entry);
|
|
285
|
-
|
|
477
|
+
const member = key.startsWith(".") ? key.slice(1) : key;
|
|
478
|
+
setKey(row, member, valueOf(reader, entry), reader, entry);
|
|
479
|
+
markValue(reader, `${pointer}/${token(member)}`, entry);
|
|
286
480
|
}
|
|
287
481
|
if (childrenOf(node).length > 0) {
|
|
288
482
|
setKey(row, "map", readOnlyElement(reader, node, `${pointer}/map`), reader, node);
|
|
@@ -299,6 +493,7 @@ function readChild(reader: Reader, node: Node, pointer: string): JsonValue {
|
|
|
299
493
|
throw failure(reader, node, `"-" is one piece of text or one (ref)`);
|
|
300
494
|
}
|
|
301
495
|
mark(reader, pointer, node);
|
|
496
|
+
markValue(reader, pointer, positionalEntries(node)[0]);
|
|
302
497
|
return values[0] ?? null;
|
|
303
498
|
}
|
|
304
499
|
if (name === "$array") {
|
|
@@ -355,6 +550,7 @@ function readElement(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
|
355
550
|
}
|
|
356
551
|
if (text !== undefined) {
|
|
357
552
|
setKey(element, "textContent", text, reader, node);
|
|
553
|
+
markValue(reader, `${pointer}/textContent`, positionalEntries(node)[1]);
|
|
358
554
|
}
|
|
359
555
|
} else {
|
|
360
556
|
if (name.startsWith("$")) {
|
|
@@ -367,6 +563,7 @@ function readElement(reader: Reader, node: Node, pointer: string): JsonObject {
|
|
|
367
563
|
if (values[0] !== undefined) {
|
|
368
564
|
setKey(element, "textContent", values[0], reader, node);
|
|
369
565
|
mark(reader, `${pointer}/textContent`, node);
|
|
566
|
+
markValue(reader, `${pointer}/textContent`, positionalEntries(node)[0]);
|
|
370
567
|
}
|
|
371
568
|
}
|
|
372
569
|
readElementEntries(reader, node, element, pointer);
|
|
@@ -409,9 +606,11 @@ function readElementEntries(
|
|
|
409
606
|
const member = key.startsWith(".") ? key.slice(1) : key;
|
|
410
607
|
setKey(element, member, value, reader, entry);
|
|
411
608
|
mark(reader, `${pointer}/${token(member)}`, entry);
|
|
609
|
+
markValue(reader, `${pointer}/${token(member)}`, entry);
|
|
412
610
|
} else if (key.startsWith("on") && entryTag === "ref") {
|
|
413
611
|
setKey(element, key, value, reader, entry);
|
|
414
612
|
mark(reader, `${pointer}/${token(key)}`, entry);
|
|
613
|
+
markValue(reader, `${pointer}/${token(key)}`, entry);
|
|
415
614
|
} else if (key.startsWith("on") && EVENT_HANDLER_NAMES.has(key)) {
|
|
416
615
|
throw failure(
|
|
417
616
|
reader,
|
|
@@ -421,6 +620,7 @@ function readElementEntries(
|
|
|
421
620
|
} else {
|
|
422
621
|
setKey(attributesOf(element), key, value, reader, entry);
|
|
423
622
|
mark(reader, `${pointer}/attributes/${token(key)}`, entry);
|
|
623
|
+
markValue(reader, `${pointer}/attributes/${token(key)}`, entry);
|
|
424
624
|
}
|
|
425
625
|
}
|
|
426
626
|
}
|
|
@@ -447,7 +647,17 @@ function readElementChild(
|
|
|
447
647
|
// `(on)` has done its job by saying this is a handler, and what it holds is a JSON block.
|
|
448
648
|
setKey(element, name, readValue(reader, kid, member(name)), reader, kid);
|
|
449
649
|
} else if (name === "script") {
|
|
450
|
-
|
|
650
|
+
if (pointer !== "") {
|
|
651
|
+
throw failure(
|
|
652
|
+
reader,
|
|
653
|
+
kid,
|
|
654
|
+
`"script" is the document's module, and is written in its root element. A <script> element is $element "script"`,
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (tag !== null) {
|
|
658
|
+
throw failure(reader, kid, `(${tag}) cannot annotate a script`);
|
|
659
|
+
}
|
|
660
|
+
readScript(reader, kid, element);
|
|
451
661
|
} else if (name === "state") {
|
|
452
662
|
setKey(element, "state", readValue(reader, kid, member("state")), reader, kid);
|
|
453
663
|
} else if (name === "style") {
|
|
@@ -590,3 +800,14 @@ export function parseJxnWithSpans(text: string) {
|
|
|
590
800
|
const spans = new Map<string, JxnSpan>();
|
|
591
801
|
return { document: readDocument({ spans, text }), spans };
|
|
592
802
|
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Read a `.jxn` document, and where the single tokens a type checker reads were written.
|
|
806
|
+
*
|
|
807
|
+
* @param text The file.
|
|
808
|
+
* @throws {JxnError} When it is not one.
|
|
809
|
+
*/
|
|
810
|
+
export function parseJxnWithTokens(text: string) {
|
|
811
|
+
const tokens: JxnTokens = { names: new Map(), values: new Map() };
|
|
812
|
+
return { document: readDocument({ spans: null, text, tokens }), tokens };
|
|
813
|
+
}
|