@josd/eyecode 0.1.4 → 0.1.5
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/SPEC.md +3 -3
- package/package.json +1 -1
- package/src/lang.js +188 -6
- package/src/reader.js +74 -57
- package/src/version.js +1 -1
package/SPEC.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Title: The eyecode Rule Language and Proof Format
|
|
5
|
-
Version: eyecode 0.1.
|
|
5
|
+
Version: eyecode 0.1.5
|
|
6
6
|
Status: Informational
|
|
7
7
|
Author: Jos De Roo, KNoWS office of IDLab, Ghent University - imec
|
|
8
8
|
Repository: https://github.com/eyereasoner/eyecode
|
|
@@ -22,7 +22,7 @@ command line.
|
|
|
22
22
|
## Status of This Memo
|
|
23
23
|
|
|
24
24
|
This document is not an Internet Standards Track specification. It describes
|
|
25
|
-
the language and formats as implemented by eyecode 0.1.
|
|
25
|
+
the language and formats as implemented by eyecode 0.1.5, for readers who want
|
|
26
26
|
to write programs, produce or consume proofs and reports, or implement a
|
|
27
27
|
compatible reasoner or checker. Where this document and the implementation
|
|
28
28
|
disagree, that is a defect in one of them.
|
|
@@ -827,7 +827,7 @@ conclusions and proof documents in the canonical text, and checks every proof
|
|
|
827
827
|
it produces as Section 11 describes. A conforming **checker** implements
|
|
828
828
|
Sections 9, 11 and 12 and does not depend on a reasoner. For the same
|
|
829
829
|
program, a conforming reasoner and checker MUST produce the same
|
|
830
|
-
conclusions, proof documents and reports as eyecode 0.1.
|
|
830
|
+
conclusions, proof documents and reports as eyecode 0.1.5, byte for byte,
|
|
831
831
|
except for the `Detail` texts of failures (Section 12), the internal names of
|
|
832
832
|
variables where Section 3 says so, and where the host's floating-point
|
|
833
833
|
library functions (`exp`, `log`, the trigonometric and hyperbolic functions,
|
package/package.json
CHANGED
package/src/lang.js
CHANGED
|
@@ -137,6 +137,10 @@ function renameAnonymous(term, counter) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// Collects the clauses a program states, in order.
|
|
140
|
+
// The line of the statement call being run, when the call names it (see
|
|
141
|
+
// markStatements); otherwise statements find it on the stack.
|
|
142
|
+
let statedLine = null;
|
|
143
|
+
|
|
140
144
|
class Builder {
|
|
141
145
|
constructor(locate) {
|
|
142
146
|
this.sources = [];
|
|
@@ -144,7 +148,7 @@ class Builder {
|
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
add(kind, head, body, line = null) {
|
|
147
|
-
if (line === null) line = this.locate();
|
|
151
|
+
if (line === null) line = statedLine !== null ? statedLine : this.locate();
|
|
148
152
|
const counter = new Anonymous([head, ...body]);
|
|
149
153
|
// In the order the statement writes them: a forward rule, implies(),
|
|
150
154
|
// writes its premise first.
|
|
@@ -239,7 +243,10 @@ const LIBRARY = Object.freeze({
|
|
|
239
243
|
// The names a program is given: the statements and the terms it builds goals
|
|
240
244
|
// with. The statements report to the innermost program being built.
|
|
241
245
|
function exportsFor(io) {
|
|
242
|
-
const builder = {
|
|
246
|
+
const builder = {
|
|
247
|
+
add: (...args) => active().add(...args),
|
|
248
|
+
locate: () => (statedLine !== null ? statedLine : active().locate()),
|
|
249
|
+
};
|
|
243
250
|
const statements = {
|
|
244
251
|
fact: (...terms) => {
|
|
245
252
|
for (const term of terms) builder.add('fact', toTerm(term), []);
|
|
@@ -308,6 +315,23 @@ function exportsFor(io) {
|
|
|
308
315
|
fail: 'fail',
|
|
309
316
|
eyecode: LIBRARY,
|
|
310
317
|
};
|
|
318
|
+
const named = new Set(STATEMENT_NAMES.map((name) => statements[name]));
|
|
319
|
+
// A statement call rewritten by markStatements: the statement, run with its
|
|
320
|
+
// line known.
|
|
321
|
+
Object.defineProperty(statements, AT, {
|
|
322
|
+
value: (statement, line) => {
|
|
323
|
+
if (!named.has(statement)) return statement;
|
|
324
|
+
return (...args) => {
|
|
325
|
+
const outer = statedLine;
|
|
326
|
+
statedLine = line;
|
|
327
|
+
try {
|
|
328
|
+
return statement(...args);
|
|
329
|
+
} finally {
|
|
330
|
+
statedLine = outer;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
});
|
|
311
335
|
for (const primitiveKey of PRIMITIVE_KEYS) {
|
|
312
336
|
const name = keyName(primitiveKey);
|
|
313
337
|
const arity = Number(primitiveKey.slice(name.length + 1));
|
|
@@ -329,6 +353,9 @@ const KEPT = new Set([
|
|
|
329
353
|
function programScope(exported) {
|
|
330
354
|
const locals = new Map();
|
|
331
355
|
const predicates = new Map();
|
|
356
|
+
// Variables are matched by name and each _ is renamed where it occurs, so
|
|
357
|
+
// one Var per name serves every use.
|
|
358
|
+
const variables = new Map();
|
|
332
359
|
return new Proxy(Object.create(null), {
|
|
333
360
|
has(target, name) {
|
|
334
361
|
return typeof name === 'string' && !KEPT.has(name);
|
|
@@ -337,7 +364,15 @@ function programScope(exported) {
|
|
|
337
364
|
if (typeof name !== 'string') return undefined;
|
|
338
365
|
if (locals.has(name)) return locals.get(name);
|
|
339
366
|
if (Object.hasOwn(exported, name)) return exported[name];
|
|
340
|
-
|
|
367
|
+
const first = name.charCodeAt(0);
|
|
368
|
+
if ((first >= 65 && first <= 90) || first === 95) {
|
|
369
|
+
let variable = variables.get(name);
|
|
370
|
+
if (variable === undefined) {
|
|
371
|
+
variable = new Var(name);
|
|
372
|
+
variables.set(name, variable);
|
|
373
|
+
}
|
|
374
|
+
return variable;
|
|
375
|
+
}
|
|
341
376
|
let made = predicates.get(name);
|
|
342
377
|
if (made === undefined) {
|
|
343
378
|
made = predicate(name);
|
|
@@ -379,6 +414,146 @@ function lineOffset() {
|
|
|
379
414
|
return firstLine;
|
|
380
415
|
}
|
|
381
416
|
|
|
417
|
+
const AT = '__eyecode_at__';
|
|
418
|
+
const STATEMENT_NAMES = ['fact', 'implies', 'implied_by', 'query', 'contradiction', 'facts_from'];
|
|
419
|
+
const STATEMENT_SET = new Set(STATEMENT_NAMES);
|
|
420
|
+
const IDENTIFIER = /[A-Za-z0-9_$]/;
|
|
421
|
+
// Words after which a slash starts a regular expression rather than a division.
|
|
422
|
+
const BEFORE_EXPRESSION = new Set(['return', 'typeof', 'case', 'in', 'of', 'new', 'delete', 'void', 'throw', 'else', 'do',
|
|
423
|
+
'instanceof', 'yield', 'await']);
|
|
424
|
+
|
|
425
|
+
// The program with each call of a statement, such as fact(...), rewritten to
|
|
426
|
+
// name its own line: __eyecode_at__(fact, 12)(...). Finding the line of a call
|
|
427
|
+
// on the stack costs microseconds, which adds up over a program that states a
|
|
428
|
+
// hundred thousand clauses. The rewriting adds no line breaks, skips strings,
|
|
429
|
+
// comments, template text and regular expressions, and leaves property calls
|
|
430
|
+
// and declarations alone; a call it misses still finds its line on the stack.
|
|
431
|
+
function markStatements(source) {
|
|
432
|
+
const out = [];
|
|
433
|
+
const templates = []; // for each open brace: true when it closes a template's ${
|
|
434
|
+
let from = 0;
|
|
435
|
+
let line = 1;
|
|
436
|
+
let i = 0;
|
|
437
|
+
let previous = ''; // the previous token: an identifier, or one punctuation character
|
|
438
|
+
const n = source.length;
|
|
439
|
+
const skipTo = (end) => {
|
|
440
|
+
for (; i < end; i++) if (source[i] === '\n') line++;
|
|
441
|
+
};
|
|
442
|
+
const template = () => {
|
|
443
|
+
// From inside a template's text to its end, or to a ${ that opens code.
|
|
444
|
+
while (i < n) {
|
|
445
|
+
const ch = source[i];
|
|
446
|
+
if (ch === '\\') skipTo(i + 2);
|
|
447
|
+
else if (ch === '`') {
|
|
448
|
+
i++;
|
|
449
|
+
previous = ')';
|
|
450
|
+
return;
|
|
451
|
+
} else if (ch === '$' && source[i + 1] === '{') {
|
|
452
|
+
i += 2;
|
|
453
|
+
templates.push(true);
|
|
454
|
+
previous = '{';
|
|
455
|
+
return;
|
|
456
|
+
} else skipTo(i + 1);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
while (i < n) {
|
|
460
|
+
const ch = source[i];
|
|
461
|
+
if (ch === '\n') {
|
|
462
|
+
line++;
|
|
463
|
+
i++;
|
|
464
|
+
} else if (ch === ' ' || ch === '\t' || ch === '\r') {
|
|
465
|
+
i++;
|
|
466
|
+
} else if (ch === '/' && source[i + 1] === '/') {
|
|
467
|
+
while (i < n && source[i] !== '\n') i++;
|
|
468
|
+
} else if (ch === '/' && source[i + 1] === '*') {
|
|
469
|
+
const end = source.indexOf('*/', i + 2);
|
|
470
|
+
skipTo(end < 0 ? n : end + 2);
|
|
471
|
+
} else if (ch === "'" || ch === '"') {
|
|
472
|
+
i++;
|
|
473
|
+
while (i < n && source[i] !== ch && source[i] !== '\n') skipTo(source[i] === '\\' ? i + 2 : i + 1);
|
|
474
|
+
i++;
|
|
475
|
+
previous = ')';
|
|
476
|
+
} else if (ch === '`') {
|
|
477
|
+
i++;
|
|
478
|
+
template();
|
|
479
|
+
} else if (ch === '/' && (previous === '' || BEFORE_EXPRESSION.has(previous) || '(,=:[!&|?{};+-*%<>~^'.includes(previous))) {
|
|
480
|
+
// A regular expression literal.
|
|
481
|
+
i++;
|
|
482
|
+
let inClass = false;
|
|
483
|
+
while (i < n && source[i] !== '\n' && (inClass || source[i] !== '/')) {
|
|
484
|
+
if (source[i] === '\\') i++;
|
|
485
|
+
else if (source[i] === '[') inClass = true;
|
|
486
|
+
else if (source[i] === ']') inClass = false;
|
|
487
|
+
i++;
|
|
488
|
+
}
|
|
489
|
+
i++;
|
|
490
|
+
while (i < n && IDENTIFIER.test(source[i])) i++;
|
|
491
|
+
previous = ')';
|
|
492
|
+
} else if (IDENTIFIER.test(ch)) {
|
|
493
|
+
const start = i;
|
|
494
|
+
while (i < n && IDENTIFIER.test(source[i])) i++;
|
|
495
|
+
const word = source.slice(start, i);
|
|
496
|
+
if (STATEMENT_SET.has(word) && previous !== '.' && previous !== 'function') {
|
|
497
|
+
let next = i;
|
|
498
|
+
while (source[next] === ' ' || source[next] === '\t') next++;
|
|
499
|
+
if (source[next] === '(') {
|
|
500
|
+
out.push(source.slice(from, start), `${AT}(${word}, ${line})`);
|
|
501
|
+
from = i;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
previous = /^[0-9]/.test(word) ? ')' : word;
|
|
505
|
+
} else if (ch === '{') {
|
|
506
|
+
templates.push(false);
|
|
507
|
+
previous = '{';
|
|
508
|
+
i++;
|
|
509
|
+
} else if (ch === '}') {
|
|
510
|
+
i++;
|
|
511
|
+
if (templates.pop()) template();
|
|
512
|
+
else previous = '}';
|
|
513
|
+
} else {
|
|
514
|
+
previous = ch === ')' || ch === ']' ? ')' : ch;
|
|
515
|
+
i++;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
out.push(source.slice(from));
|
|
519
|
+
return out.join('');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// The line of the innermost frame of the script at url, or null when none is
|
|
523
|
+
// on the stack. Where the host gives stack frames as objects (V8), they are
|
|
524
|
+
// read directly, which is many times faster than formatting the stack as
|
|
525
|
+
// text for every statement; elsewhere this returns undefined and the text is
|
|
526
|
+
// searched.
|
|
527
|
+
function innermostLine(url) {
|
|
528
|
+
if (typeof Error.captureStackTrace !== 'function') return undefined;
|
|
529
|
+
// A statement is usually called from the program itself, a few frames up,
|
|
530
|
+
// so a short stack is captured first and the whole one only when needed.
|
|
531
|
+
for (const limit of [10, Infinity]) {
|
|
532
|
+
const frames = callSites(limit);
|
|
533
|
+
if (!Array.isArray(frames)) return undefined;
|
|
534
|
+
for (const frame of frames) {
|
|
535
|
+
if (frame.getScriptNameOrSourceURL() === url) return frame.getLineNumber();
|
|
536
|
+
}
|
|
537
|
+
if (frames.length < limit) break;
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function callSites(limit) {
|
|
543
|
+
const prepare = Error.prepareStackTrace;
|
|
544
|
+
const stackLimit = Error.stackTraceLimit;
|
|
545
|
+
const holder = {};
|
|
546
|
+
try {
|
|
547
|
+
Error.prepareStackTrace = (_, sites) => sites;
|
|
548
|
+
Error.stackTraceLimit = limit;
|
|
549
|
+
Error.captureStackTrace(holder, innermostLine);
|
|
550
|
+
return holder.stack;
|
|
551
|
+
} finally {
|
|
552
|
+
Error.prepareStackTrace = prepare;
|
|
553
|
+
Error.stackTraceLimit = stackLimit;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
382
557
|
function compile(source, url) {
|
|
383
558
|
// eslint-disable-next-line no-new-func
|
|
384
559
|
return new Function('__eyecode_scope__', `with (__eyecode_scope__) {\n${source}\n}\n//# sourceURL=${url}`);
|
|
@@ -429,11 +604,18 @@ function execute(source, filename, builder, io) {
|
|
|
429
604
|
const url = `eyecode-program-${id}.js`;
|
|
430
605
|
let fn;
|
|
431
606
|
try {
|
|
432
|
-
fn = compile(source, url);
|
|
433
|
-
} catch
|
|
434
|
-
|
|
607
|
+
fn = compile(markStatements(source), url);
|
|
608
|
+
} catch {
|
|
609
|
+
// Should the rewriting ever break the program, it runs as written.
|
|
610
|
+
try {
|
|
611
|
+
fn = compile(source, url);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
throw new EyecodeError(`${filename}: ${error.name}: ${error.message}`);
|
|
614
|
+
}
|
|
435
615
|
}
|
|
436
616
|
builder.locate = () => {
|
|
617
|
+
const line = innermostLine(url);
|
|
618
|
+
if (line !== undefined) return line === null ? null : line - lineOffset() + 1;
|
|
437
619
|
const lines = frameLines(new Error().stack, id);
|
|
438
620
|
return lines.length ? lines[0] - lineOffset() + 1 : null;
|
|
439
621
|
};
|
package/src/reader.js
CHANGED
|
@@ -22,77 +22,85 @@ function variableName(text, line) {
|
|
|
22
22
|
|
|
23
23
|
// Numbers and names of any length, matched where the scan stands.
|
|
24
24
|
const NUMBER = /(?:([0-9]+)n|((?:[0-9]+\.[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)|([0-9]+))/y;
|
|
25
|
-
const NAME = /[A-Za-z0-9_$]+/y;
|
|
26
25
|
|
|
27
26
|
const SIMPLE_ESCAPES = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v', 0: '\0', "'": "'", '"': '"', '\\': '\\' };
|
|
28
27
|
|
|
28
|
+
const isDigit = (code) => code >= 48 && code <= 57;
|
|
29
|
+
const isNameStart = (code) => (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95 || code === 36;
|
|
30
|
+
const isNamePart = (code) => isNameStart(code) || isDigit(code);
|
|
31
|
+
|
|
32
|
+
// The tokens of a text, read one ahead of the parser as it asks for them, so
|
|
33
|
+
// a large document is never held as tokens all at once.
|
|
29
34
|
class Tokens {
|
|
30
35
|
constructor(text) {
|
|
31
36
|
this.text = text;
|
|
32
37
|
this.pos = 0;
|
|
33
38
|
this.line = 1;
|
|
34
|
-
this.
|
|
35
|
-
this.scan();
|
|
36
|
-
this.index = 0;
|
|
39
|
+
this.current = this.read();
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
error(message, line = this.line) {
|
|
40
43
|
return new EyecodeError(`line ${line}: ${message}`);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
// The token at this.pos, moving past it.
|
|
47
|
+
read() {
|
|
44
48
|
const { text } = this;
|
|
45
49
|
const n = text.length;
|
|
46
|
-
let pos =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
50
|
+
let { pos, line } = this;
|
|
51
|
+
try {
|
|
52
|
+
while (pos < n) {
|
|
53
|
+
const ch = text[pos];
|
|
54
|
+
const code = ch.charCodeAt(0);
|
|
55
|
+
if (ch === '\n') {
|
|
56
|
+
pos++;
|
|
57
|
+
line++;
|
|
58
|
+
return { kind: 'newline', text: '\n', line: line - 1 };
|
|
59
|
+
} else if (ch === ' ' || ch === '\t' || ch === '\r' || ch === '\ufeff') {
|
|
60
|
+
pos++;
|
|
61
|
+
} else if (ch === '/' && text[pos + 1] === '/') {
|
|
62
|
+
while (pos < n && text[pos] !== '\n') pos++;
|
|
63
|
+
} else if (ch === '/' && text[pos + 1] === '*') {
|
|
64
|
+
const end = text.indexOf('*/', pos + 2);
|
|
65
|
+
if (end < 0) throw this.error('unterminated comment', line);
|
|
66
|
+
for (let i = pos; i < end; i++) if (text[i] === '\n') line++;
|
|
67
|
+
pos = end + 2;
|
|
68
|
+
} else if (ch === "'" || ch === '"') {
|
|
69
|
+
const [value, next] = this.string(pos, line);
|
|
70
|
+
pos = next;
|
|
71
|
+
return { kind: 'string', text: value, line };
|
|
72
|
+
} else if (isDigit(code) || (ch === '.' && isDigit(text.charCodeAt(pos + 1)))) {
|
|
73
|
+
NUMBER.lastIndex = pos;
|
|
74
|
+
const match = NUMBER.exec(text);
|
|
75
|
+
const after = text.charCodeAt(pos + match[0].length);
|
|
76
|
+
if (isNamePart(after) || after === 46) throw this.error(`malformed number ${text.slice(pos, pos + match[0].length + 1)}`, line);
|
|
77
|
+
pos += match[0].length;
|
|
78
|
+
if (match[1] !== undefined || match[3] !== undefined) {
|
|
79
|
+
const digits = match[1] !== undefined ? match[1] : match[3];
|
|
80
|
+
if (digits.length > 1 && digits[0] === '0') throw this.error(`an integer has no leading zero: ${digits}`, line);
|
|
81
|
+
return { kind: 'int', text: digits, line };
|
|
82
|
+
}
|
|
83
|
+
return { kind: 'float', text: match[2], line };
|
|
84
|
+
} else if (isNameStart(code)) {
|
|
85
|
+
const start = pos;
|
|
86
|
+
pos++;
|
|
87
|
+
while (pos < n && isNamePart(text.charCodeAt(pos))) pos++;
|
|
88
|
+
return { kind: 'name', text: text.slice(start, pos), line };
|
|
89
|
+
} else if (ch === '.' && text[pos + 1] === '.' && text[pos + 2] === '.') {
|
|
90
|
+
pos += 3;
|
|
91
|
+
return { kind: 'punct', text: '...', line };
|
|
92
|
+
} else if ('()[]{},:;-'.includes(ch)) {
|
|
93
|
+
pos++;
|
|
94
|
+
return { kind: 'punct', text: ch, line };
|
|
76
95
|
} else {
|
|
77
|
-
this.
|
|
96
|
+
throw this.error(`unexpected character ${JSON.stringify(ch)}`, line);
|
|
78
97
|
}
|
|
79
|
-
pos += match[0].length;
|
|
80
|
-
} else if (/[A-Za-z_$]/.test(ch)) {
|
|
81
|
-
NAME.lastIndex = pos;
|
|
82
|
-
const match = NAME.exec(text);
|
|
83
|
-
this.list.push({ kind: 'name', text: match[0], line });
|
|
84
|
-
pos += match[0].length;
|
|
85
|
-
} else if (text.startsWith('...', pos)) {
|
|
86
|
-
this.list.push({ kind: 'punct', text: '...', line });
|
|
87
|
-
pos += 3;
|
|
88
|
-
} else if ('()[]{},:;-'.includes(ch)) {
|
|
89
|
-
this.list.push({ kind: 'punct', text: ch, line });
|
|
90
|
-
pos++;
|
|
91
|
-
} else {
|
|
92
|
-
throw this.error(`unexpected character ${JSON.stringify(ch)}`, line);
|
|
93
98
|
}
|
|
99
|
+
return { kind: 'end', text: '', line };
|
|
100
|
+
} finally {
|
|
101
|
+
this.pos = pos;
|
|
102
|
+
this.line = line;
|
|
94
103
|
}
|
|
95
|
-
this.list.push({ kind: 'end', text: '', line });
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
string(start, line) {
|
|
@@ -109,8 +117,15 @@ class Tokens {
|
|
|
109
117
|
// A document spells control characters as escapes, as the writer does.
|
|
110
118
|
if (ch < ' ' && ch !== '\t') throw this.error('a control character in a string must be escaped', line);
|
|
111
119
|
if (ch !== '\\') {
|
|
112
|
-
|
|
113
|
-
pos
|
|
120
|
+
// A run of plain characters at once.
|
|
121
|
+
let end = pos + 1;
|
|
122
|
+
while (end < text.length) {
|
|
123
|
+
const code = text.charCodeAt(end);
|
|
124
|
+
if (code < 32 || code === 92 || text[end] === quote) break;
|
|
125
|
+
end++;
|
|
126
|
+
}
|
|
127
|
+
out += text.slice(pos, end);
|
|
128
|
+
pos = end;
|
|
114
129
|
continue;
|
|
115
130
|
}
|
|
116
131
|
const next = text[pos + 1];
|
|
@@ -140,20 +155,22 @@ class Tokens {
|
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
peek() {
|
|
143
|
-
return this.
|
|
158
|
+
return this.current;
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
next() {
|
|
147
|
-
|
|
162
|
+
const token = this.current;
|
|
163
|
+
if (token.kind !== 'end') this.current = this.read();
|
|
164
|
+
return token;
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
// Newlines inside brackets are layout.
|
|
151
168
|
skipNewlines() {
|
|
152
|
-
while (this.
|
|
169
|
+
while (this.current.kind === 'newline') this.current = this.read();
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
at(text) {
|
|
156
|
-
const token = this.
|
|
173
|
+
const token = this.current;
|
|
157
174
|
return token.kind === 'punct' && token.text === text;
|
|
158
175
|
}
|
|
159
176
|
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The release this checkout is; ./release keeps it in step with package.json.
|
|
2
|
-
export const VERSION = '0.1.
|
|
2
|
+
export const VERSION = '0.1.5';
|