reladraw 0.18.0 → 0.19.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/README.md +5 -5
- package/SYNTAX.md +70 -3
- package/dist/cli.js +188 -1
- package/dist/format-settings.d.ts +60 -0
- package/dist/format-settings.js +250 -0
- package/dist/format.d.ts +14 -0
- package/dist/format.js +511 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lexer.d.ts +8 -0
- package/dist/lexer.js +19 -6
- package/dist/lines.d.ts +1 -0
- package/dist/lines.js +10 -4
- package/dist/model.d.ts +2 -0
- package/dist/parser.d.ts +25 -1
- package/dist/parser.js +44 -9
- package/dist/resolve.js +1 -0
- package/package.json +1 -1
package/dist/parser.d.ts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import type { Document } from './ast.js';
|
|
2
2
|
import { SourceError } from './errors.js';
|
|
3
|
+
import { type Token } from './lexer.js';
|
|
4
|
+
/**
|
|
5
|
+
* One statement as `parse` gathered it, before reading: the line it starts on,
|
|
6
|
+
* and each of its lines with that line's tokens. A comment-only line inside it
|
|
7
|
+
* has no tokens and is not listed.
|
|
8
|
+
*/
|
|
9
|
+
export interface Gathered {
|
|
10
|
+
line: number;
|
|
11
|
+
lines: {
|
|
12
|
+
line: number;
|
|
13
|
+
tokens: Token[];
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse a source for the formatter: each statement as gathered, and the first
|
|
18
|
+
* token of each clause read. Throws as `parse` does, so a file that does not
|
|
19
|
+
* parse is never rewritten.
|
|
20
|
+
*/
|
|
21
|
+
export declare function clausesOf(source: string): {
|
|
22
|
+
statements: Gathered[];
|
|
23
|
+
starts: Set<Token>;
|
|
24
|
+
};
|
|
3
25
|
/**
|
|
4
26
|
* Parse a whole source file. A statement starts on an unindented line and runs
|
|
5
27
|
* on through any line that starts with whitespace; a blank line or the next
|
|
@@ -15,7 +37,7 @@ import { SourceError } from './errors.js';
|
|
|
15
37
|
* are its own, blank or not, and a line opening with one continues the
|
|
16
38
|
* statement above without being indented.
|
|
17
39
|
*/
|
|
18
|
-
export declare function parse(source: string): Document;
|
|
40
|
+
export declare function parse(source: string, seen?: (statement: Gathered) => void): Document;
|
|
19
41
|
/**
|
|
20
42
|
* Find the line at fault for an error reported on the first line of a
|
|
21
43
|
* continued statement. Everything after the parser reports a statement by the
|
|
@@ -30,3 +52,5 @@ export declare function parse(source: string): Document;
|
|
|
30
52
|
* This costs a few extra runs, and only when there is an error to report.
|
|
31
53
|
*/
|
|
32
54
|
export declare function blame(source: string, error: SourceError, run: (source: string) => unknown): SourceError;
|
|
55
|
+
/** Whether a token is an arrow, or begins one: a bracketed mark, or a run of dashes with marks at its ends. */
|
|
56
|
+
export declare function looksLikeArrow(token: Token): boolean;
|
package/dist/parser.js
CHANGED
|
@@ -9,6 +9,30 @@ import { checkOpacity } from './place.js';
|
|
|
9
9
|
const SHAPE_FLAGS = [...new Set(Object.values(SHAPE_SETTINGS).flatMap((one) => Object.entries(one).filter(([, kind]) => kind === 'flag').map(([name]) => name)))];
|
|
10
10
|
import { readSize, SIZE_KEYS, writeSize } from './size.js';
|
|
11
11
|
import { THEME_COLORS, THEME_NAMES, THEMES } from './themes.js';
|
|
12
|
+
/**
|
|
13
|
+
* Set while `clausesOf` runs: the first token of every clause the parser
|
|
14
|
+
* reads, so the formatter can tell where one clause ends and the next begins
|
|
15
|
+
* without a second grammar of its own. Clauses inside brackets are recorded
|
|
16
|
+
* too, and the formatter passes over them.
|
|
17
|
+
*/
|
|
18
|
+
let clauseStarts = null;
|
|
19
|
+
/**
|
|
20
|
+
* Parse a source for the formatter: each statement as gathered, and the first
|
|
21
|
+
* token of each clause read. Throws as `parse` does, so a file that does not
|
|
22
|
+
* parse is never rewritten.
|
|
23
|
+
*/
|
|
24
|
+
export function clausesOf(source) {
|
|
25
|
+
const statements = [];
|
|
26
|
+
const starts = new Set();
|
|
27
|
+
clauseStarts = starts;
|
|
28
|
+
try {
|
|
29
|
+
parse(source, (statement) => statements.push(statement));
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
clauseStarts = null;
|
|
33
|
+
}
|
|
34
|
+
return { statements, starts };
|
|
35
|
+
}
|
|
12
36
|
/**
|
|
13
37
|
* Parse a whole source file. A statement starts on an unindented line and runs
|
|
14
38
|
* on through any line that starts with whitespace; a blank line or the next
|
|
@@ -24,16 +48,18 @@ import { THEME_COLORS, THEME_NAMES, THEMES } from './themes.js';
|
|
|
24
48
|
* are its own, blank or not, and a line opening with one continues the
|
|
25
49
|
* statement above without being indented.
|
|
26
50
|
*/
|
|
27
|
-
export function parse(source) {
|
|
51
|
+
export function parse(source, seen) {
|
|
28
52
|
const statements = [];
|
|
29
|
-
/** The statement being gathered,
|
|
53
|
+
/** The statement being gathered, the line it starts on, and its lines' tokens. */
|
|
30
54
|
let open = null;
|
|
31
55
|
// Brackets may close on a later line, and the lexer only reads `)` as a
|
|
32
56
|
// bracket while one is open, so the depth is carried from line to line.
|
|
33
57
|
let depth = 0;
|
|
34
58
|
const close = () => {
|
|
35
|
-
if (open)
|
|
59
|
+
if (open) {
|
|
60
|
+
seen?.({ line: open.line, lines: open.lines });
|
|
36
61
|
statements.push(parseStatement(open.tokens, open.line));
|
|
62
|
+
}
|
|
37
63
|
open = null;
|
|
38
64
|
depth = 0;
|
|
39
65
|
};
|
|
@@ -74,7 +100,7 @@ export function parse(source) {
|
|
|
74
100
|
if (tokens.length === 0)
|
|
75
101
|
continue;
|
|
76
102
|
if (!continues) {
|
|
77
|
-
open = { tokens, line: lineNumber };
|
|
103
|
+
open = { tokens: [...tokens], line: lineNumber, lines: [{ line: lineNumber, tokens }] };
|
|
78
104
|
}
|
|
79
105
|
else {
|
|
80
106
|
const first = tokens[0];
|
|
@@ -90,6 +116,7 @@ export function parse(source) {
|
|
|
90
116
|
`the one before it, so remove the indentation to start a new statement`, lineNumber);
|
|
91
117
|
}
|
|
92
118
|
open.tokens.push(...tokens);
|
|
119
|
+
open.lines.push({ line: lineNumber, tokens });
|
|
93
120
|
}
|
|
94
121
|
for (const token of tokens) {
|
|
95
122
|
if (token.quoted)
|
|
@@ -256,6 +283,7 @@ function parseTail(tokens, start, line, subject, other) {
|
|
|
256
283
|
let i = start;
|
|
257
284
|
while (i < tokens.length) {
|
|
258
285
|
const token = tokens[i];
|
|
286
|
+
clauseStarts?.add(token);
|
|
259
287
|
if (isAttrKey(token)) {
|
|
260
288
|
i = readAttr(tokens, i, attrs, line, subject);
|
|
261
289
|
continue;
|
|
@@ -324,6 +352,7 @@ function setOnce(attrs, key, value, subject, line, shown = { key, value: (v) =>
|
|
|
324
352
|
/** Read one `key: value` pair, and refuse the words that used to be keys. */
|
|
325
353
|
function readAttr(tokens, at, attrs, line, subject) {
|
|
326
354
|
const keyToken = tokens[at];
|
|
355
|
+
clauseStarts?.add(keyToken);
|
|
327
356
|
const key = keyToken.text.slice(0, -1);
|
|
328
357
|
if (key === 'gap') {
|
|
329
358
|
throw new SourceError(`${subject}: a node has no gap:, only a placement does — write it in the placement's bracket, ` +
|
|
@@ -1511,7 +1540,7 @@ function eachChildAt(head) {
|
|
|
1511
1540
|
return found;
|
|
1512
1541
|
}
|
|
1513
1542
|
/** Whether a token is an arrow, or begins one: a bracketed mark, or a run of dashes with marks at its ends. */
|
|
1514
|
-
function looksLikeArrow(token) {
|
|
1543
|
+
export function looksLikeArrow(token) {
|
|
1515
1544
|
if (token.quoted)
|
|
1516
1545
|
return false;
|
|
1517
1546
|
if (token.text === '(' || /^\[[^\]]*\]-*$/.test(token.text))
|
|
@@ -1758,6 +1787,7 @@ function parsePoint(head, line) {
|
|
|
1758
1787
|
if (!head[start] || isAttrKey(head[start])) {
|
|
1759
1788
|
throw new SourceError(`${subject} says where it is, such as \`point ${name} between a and b\``, line);
|
|
1760
1789
|
}
|
|
1790
|
+
clauseStarts?.add(head[start]);
|
|
1761
1791
|
const read = readOneTarget(head, start, subject, line, true);
|
|
1762
1792
|
if (read.listed)
|
|
1763
1793
|
throw new SourceError(`${subject} is one point, so it takes no list`, line);
|
|
@@ -1776,6 +1806,7 @@ function parseRegion(head, line) {
|
|
|
1776
1806
|
if (!head[start] || isAttrKey(head[start])) {
|
|
1777
1807
|
throw new SourceError(`${subject} says what it covers: two corners, \`region ${name} a top-left to b bottom-right\`, or \`region ${name} between a and b\``, line);
|
|
1778
1808
|
}
|
|
1809
|
+
clauseStarts?.add(head[start]);
|
|
1779
1810
|
const first = readOneTarget(head, start, subject, line, true);
|
|
1780
1811
|
if (first.listed)
|
|
1781
1812
|
throw new SourceError(`${subject}: a region runs from one point to another, written with "to", not a list`, line);
|
|
@@ -1988,8 +2019,12 @@ function refuseNameMark(token, where, line) {
|
|
|
1988
2019
|
* helps and never demanded. Shorthands added later extend this without
|
|
1989
2020
|
* disturbing what it already reads.
|
|
1990
2021
|
*/
|
|
1991
|
-
function readPlacement(tokens, at, line, subject) {
|
|
2022
|
+
function readPlacement(tokens, at, line, subject, nested = false) {
|
|
1992
2023
|
const word = tokens[at];
|
|
2024
|
+
// A placement read after its own distance or part (`6 above x`) is still
|
|
2025
|
+
// the clause that word began, so only the outermost read starts one.
|
|
2026
|
+
if (!nested)
|
|
2027
|
+
clauseStarts?.add(word);
|
|
1993
2028
|
if (word.quoted) {
|
|
1994
2029
|
throw new SourceError(`${subject}: unexpected text "${word.text}"`, line);
|
|
1995
2030
|
}
|
|
@@ -1998,7 +2033,7 @@ function readPlacement(tokens, at, line, subject) {
|
|
|
1998
2033
|
// the distance is required.
|
|
1999
2034
|
if (!word.quoted && isPart(word.text) && !follows(tokens, at + 1, 'of') && !follows(tokens, at + 1, 'even')) {
|
|
2000
2035
|
if (opensPartPlacement(tokens, at)) {
|
|
2001
|
-
const read = readPlacement(tokens, at + 1, line, subject);
|
|
2036
|
+
const read = readPlacement(tokens, at + 1, line, subject, true);
|
|
2002
2037
|
if (read.placement.kind !== 'offset')
|
|
2003
2038
|
return read;
|
|
2004
2039
|
return { placement: { ...read.placement, own: word.text }, next: read.next };
|
|
@@ -2031,7 +2066,7 @@ function readPlacement(tokens, at, line, subject) {
|
|
|
2031
2066
|
throw new SourceError(`${subject}: "${word.text} ${first} and ${other.text} ${second}" gives two distances, one for each axis, ` +
|
|
2032
2067
|
'so one direction is above or below and the other left or right', line);
|
|
2033
2068
|
}
|
|
2034
|
-
const read = readPlacement(tokens, at + 3, line, subject);
|
|
2069
|
+
const read = readPlacement(tokens, at + 3, line, subject, true);
|
|
2035
2070
|
if (read.placement.kind !== 'offset')
|
|
2036
2071
|
return read;
|
|
2037
2072
|
const y = vertical(first) ? first : second;
|
|
@@ -2043,7 +2078,7 @@ function readPlacement(tokens, at, line, subject) {
|
|
|
2043
2078
|
next: read.next,
|
|
2044
2079
|
};
|
|
2045
2080
|
}
|
|
2046
|
-
const read = readPlacement(tokens, at + 1, line, subject);
|
|
2081
|
+
const read = readPlacement(tokens, at + 1, line, subject, true);
|
|
2047
2082
|
if (read.placement.kind !== 'offset')
|
|
2048
2083
|
return read;
|
|
2049
2084
|
if (read.placement.gap !== undefined) {
|
package/dist/resolve.js
CHANGED
|
@@ -1700,6 +1700,7 @@ function lineLook(written, appearance, what, line) {
|
|
|
1700
1700
|
turn,
|
|
1701
1701
|
turnWritten: written['line.turn'],
|
|
1702
1702
|
stepOut,
|
|
1703
|
+
stepOutWritten: written['line.step-out'],
|
|
1703
1704
|
};
|
|
1704
1705
|
// Said on the edge itself, a property its path doesn't have is refused; from
|
|
1705
1706
|
// a style or a default it lands on the lines that have it.
|