@rohal12/spindle 0.56.0 → 0.58.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.
@@ -160,3 +160,314 @@ export interface ValidateMarkupOptions {
160
160
  * `Passage "Start", line 3, column 5 (story.twee:12): Unclosed {if}: …`.
161
161
  */
162
162
  export declare function formatDiagnostic(diagnostic: MarkupDiagnostic): string;
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // Parsing rules
166
+ //
167
+ // The leaf rules Spindle parses passages with, so that editor tooling can use
168
+ // them instead of mirroring them (see docs/tooling.md). They are pure
169
+ // string-to-data functions that do not load the runtime.
170
+ // ---------------------------------------------------------------------------
171
+
172
+ /** The sigil of a variable reference: story, temporary, local, transient. */
173
+ export type Sigil = '$' | '_' | '@' | '%';
174
+
175
+ /**
176
+ * What a piece of code is: one expression (a macro argument) or a list of
177
+ * statements (a `{do}` body).
178
+ */
179
+ export type JsGoal = 'expression' | 'statements';
180
+
181
+ /** What `lexJs` reports, in source order, every character exactly once. */
182
+ export interface JsLexHandlers {
183
+ /**
184
+ * One character of code, outside literals and comments. `nesting` is the
185
+ * number of template-literal `${…}` interpolations around it.
186
+ */
187
+ code?(ch: string, index: number, nesting: number): void;
188
+ /**
189
+ * Literal text passed through verbatim: a string or regex literal, a
190
+ * comment, or a piece of a template literal.
191
+ */
192
+ literal?(text: string, index: number, nesting: number): void;
193
+ /** A sigil variable reference (`$name`), covering the sigil and name. */
194
+ variable?(sigil: Sigil, name: string, index: number, nesting: number): void;
195
+ }
196
+
197
+ /**
198
+ * Walk `src` as JavaScript, reporting code characters, literal text and
199
+ * variable references to `handlers`. Text that is no JavaScript is read
200
+ * leniently: an unterminated literal runs to the end. Returns `src.length`.
201
+ */
202
+ export declare function lexJs(
203
+ src: string,
204
+ handlers: JsLexHandlers,
205
+ goal?: JsGoal,
206
+ ): number;
207
+
208
+ /**
209
+ * Lex the template literal opening at `start` (a backtick) as `lexJs` does,
210
+ * its interpolations one nesting level deeper. Returns the index just past
211
+ * its closing backtick, or `src.length` if it is unterminated.
212
+ */
213
+ export declare function lexTemplate(
214
+ src: string,
215
+ start: number,
216
+ handlers?: JsLexHandlers,
217
+ nesting?: number,
218
+ ): number;
219
+
220
+ export interface FindCodeEndOptions {
221
+ /** What the code is (default `expression`). */
222
+ goal?: JsGoal;
223
+ /** End the code at a `{` in code, at any depth, for which this holds. */
224
+ stop?: (index: number) => boolean;
225
+ }
226
+
227
+ /**
228
+ * Where the code starting at `start` ends: the index of the `}` that closes
229
+ * the `{…}` around it (or, with `stop`, of the first `{` for which it holds).
230
+ * Braces and quotes in literals and comments don't count. `-1` when there is
231
+ * no such end, or the code before it can't be JavaScript.
232
+ */
233
+ export declare function findCodeEnd(
234
+ src: string,
235
+ start: number,
236
+ options?: FindCodeEndOptions,
237
+ ): number;
238
+
239
+ /**
240
+ * Scan the `"…"` or `'…'` string literal opening at `start`: `end` is just
241
+ * past its closing quote, or `src.length` when unterminated (`closed` false).
242
+ */
243
+ export declare function scanStringLiteral(
244
+ src: string,
245
+ start: number,
246
+ ): { end: number; closed: boolean };
247
+
248
+ /** The namespace a variable reference reads. */
249
+ export type VariableScope = 'variable' | 'temporary' | 'local' | 'transient';
250
+
251
+ /** The scope each variable sigil names. */
252
+ export declare const SIGIL_SCOPES: Readonly<Record<Sigil, VariableScope>>;
253
+
254
+ /** Whether `c` is a variable sigil. */
255
+ export declare function isSigil(c: string | undefined): c is Sigil;
256
+
257
+ /** The `.class#id` selectors written before a link, variable or macro. */
258
+ export interface Selectors {
259
+ className?: string;
260
+ id?: string;
261
+ }
262
+
263
+ /**
264
+ * The selectors that start `source` at `at`, and `end`, the index just past
265
+ * them and the one space that may follow (`at` if there are none). A name
266
+ * may hold `{$name}` interpolations.
267
+ */
268
+ export declare function parseSelectors(
269
+ source: string,
270
+ at?: number,
271
+ ): Selectors & { end: number };
272
+
273
+ /** Where a token is in the input: from `start` up to `end`. */
274
+ interface TokenSpan {
275
+ start: number;
276
+ end: number;
277
+ }
278
+
279
+ export interface TextToken extends TokenSpan {
280
+ type: 'text';
281
+ value: string;
282
+ }
283
+
284
+ export interface LinkToken extends TokenSpan, Selectors {
285
+ type: 'link';
286
+ display: string;
287
+ target: string;
288
+ }
289
+
290
+ export interface MacroToken extends TokenSpan, Selectors {
291
+ type: 'macro';
292
+ name: string;
293
+ rawArgs: string;
294
+ isClose: boolean;
295
+ }
296
+
297
+ export interface VariableToken extends TokenSpan, Selectors {
298
+ type: 'variable';
299
+ name: string;
300
+ scope: VariableScope;
301
+ }
302
+
303
+ export interface ExpressionToken extends TokenSpan, Selectors {
304
+ type: 'expression';
305
+ expression: string;
306
+ }
307
+
308
+ export interface HtmlToken extends TokenSpan {
309
+ type: 'html';
310
+ tag: string;
311
+ attributes: Record<string, string>;
312
+ isClose: boolean;
313
+ isSelfClose: boolean;
314
+ }
315
+
316
+ /** A flat markup token, with its offsets (UTF-16 code units) in the input. */
317
+ export type Token =
318
+ | TextToken
319
+ | LinkToken
320
+ | MacroToken
321
+ | VariableToken
322
+ | ExpressionToken
323
+ | HtmlToken;
324
+
325
+ /** Malformed markup, with where it starts (0-based offset, 1-based line and column). */
326
+ export declare class MarkupError extends Error {
327
+ reason: string;
328
+ offset: number;
329
+ line: number;
330
+ column: number;
331
+ }
332
+
333
+ export interface ParseMarkupOptions {
334
+ /**
335
+ * Text mode, for markup that becomes a string (HTML attribute values,
336
+ * macro labels): only `{…}` markup and brace escapes are recognized.
337
+ */
338
+ text?: boolean;
339
+ }
340
+
341
+ /**
342
+ * The flat tokens of markup, without nesting, so unclosed or mismatched
343
+ * macros and elements are no error here. Throws a `MarkupError` for a
344
+ * malformed tag, such as an unclosed `{`, `[[` or attribute value.
345
+ */
346
+ export declare function tokenizeMarkup(
347
+ source: string,
348
+ options?: ParseMarkupOptions,
349
+ ): Token[];
350
+
351
+ /**
352
+ * Split macro arguments at top-level commas (outside strings, templates and
353
+ * brackets); with no comma, adjacent standalone values separated by
354
+ * whitespace (`"Label" "target"`).
355
+ */
356
+ export declare function splitArgs(raw: string): string[];
357
+
358
+ /**
359
+ * Split `src` at every top-level character for which `isSeparator` holds
360
+ * (code outside literals, comments and brackets). Segments are untrimmed and
361
+ * empty ones are kept.
362
+ */
363
+ export declare function splitTopLevel(
364
+ src: string,
365
+ isSeparator: (ch: string) => boolean,
366
+ ): string[];
367
+
368
+ /**
369
+ * Read the `"…"` or `'…'` string whose opening quote is at `start`: its
370
+ * unescaped value (`\"`, `\'` and `\\` only) and the index just past it, or
371
+ * `null` if there is none or it is unterminated.
372
+ */
373
+ export declare function readQuoted(
374
+ src: string,
375
+ start: number,
376
+ ): { value: string; end: number } | null;
377
+
378
+ /** Undo `\"`, `\'` and `\\` in the body of a quoted macro argument. */
379
+ export declare function unescapeQuoted(body: string): string;
380
+
381
+ /** Strip an optional quote from each end of a loosely quoted argument. */
382
+ export declare function stripLooseQuotes(src: string): string;
383
+
384
+ /** Whether code ends with an operator that still needs an operand. */
385
+ export declare function endsWithOperator(src: string): boolean;
386
+
387
+ /**
388
+ * The arguments of `{include}`: whether it has the standalone `inline` flag
389
+ * (first or last word, outside quotes and brackets), and the passage
390
+ * expression that is left.
391
+ */
392
+ export declare function splitIncludeFlag(rawArgs: string): {
393
+ inline: boolean;
394
+ passage: string | undefined;
395
+ };
396
+
397
+ /** The tokens of markup that may be malformed, and its errors. */
398
+ export interface TolerantTokens {
399
+ tokens: Token[];
400
+ /** One for each malformed tag, in source order, with offsets in the source. */
401
+ errors: MarkupError[];
402
+ }
403
+
404
+ /**
405
+ * `tokenizeMarkup` for half-typed markup: the tokens it can read, and every
406
+ * error, instead of throwing at the first. At a malformed tag the tokens
407
+ * before it are kept, its first character is read as text, and tokenizing
408
+ * resumes after it. Offsets are in `source`. For well-formed markup the
409
+ * tokens are those of `tokenizeMarkup` and there are no errors.
410
+ */
411
+ export declare function tokenizeMarkupTolerant(
412
+ source: string,
413
+ options?: ParseMarkupOptions,
414
+ ): TolerantTokens;
415
+
416
+ /**
417
+ * The code Spindle runs for `expr`: its variable references (`$var`, `_var`,
418
+ * `@var`, `%var`) turned into namespace lookups (`variables["var"]`).
419
+ * String, template and regex literals and comments are untouched. `goal` is
420
+ * `'statements'` for a `{do}` body. Throws a `SyntaxError` for code that is
421
+ * not well-formed, and for a reference to a variable named `__proto__`.
422
+ */
423
+ export declare function transform(expr: string, goal?: JsGoal): string;
424
+
425
+ /** What a `passage` argument is: a quoted name, or an expression. */
426
+ export type PassageTarget =
427
+ | { kind: 'name'; name: string }
428
+ | { kind: 'expression'; expression: string };
429
+
430
+ /**
431
+ * Read a `passage` argument (`{goto "Hall"}`, `{include $room}`) as written:
432
+ * a quoted string is the name its JavaScript literal has (`"Hall"` is
433
+ * `Hall`), anything else an expression whose value, as a string, is the name
434
+ * when it runs.
435
+ */
436
+ export declare function passageTarget(arg: string): PassageTarget;
437
+
438
+ /**
439
+ * The passage a `passage` argument names, as `{goto}`, `{include}` and
440
+ * `{link}` find it: `String(evaluate(expr))`. Throws what `evaluate` throws,
441
+ * and an error naming the current passage if the story has no passage of
442
+ * that name.
443
+ */
444
+ export declare function evaluatePassageName(
445
+ expr: string | undefined,
446
+ evaluate: (expr: string) => unknown,
447
+ state: {
448
+ storyData: { passages: { has(name: string): boolean } } | null;
449
+ currentPassage: string;
450
+ },
451
+ ): string;
452
+
453
+ /** A passage that markup names, and where. */
454
+ export interface PassageReference {
455
+ /** The macro it is the argument of; `link` for `[[…]]` links. */
456
+ macro: string;
457
+ /** The name written out, or the expression whose value is the name. */
458
+ target: PassageTarget;
459
+ /** Where it is written, as written (quotes included): offsets in `source`. */
460
+ start: number;
461
+ end: number;
462
+ }
463
+
464
+ /**
465
+ * The passages the markup of `source` names, in source order: `[[…]]` links,
466
+ * the passage of `{goto}`, `{include}` and `{link}` (and of macros that
467
+ * declare a `passage` argument), the `goto` and `dialog` actions of
468
+ * `{watch}` and the body of `{dialog}`. Malformed tags are skipped, so
469
+ * half-typed markup reads.
470
+ */
471
+ export declare function collectPassageReferences(
472
+ source: string,
473
+ ): PassageReference[];