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/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, and the line it starts on. */
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reladraw",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "description": "A diagram language where you say where things go.",
5
5
  "type": "module",
6
6
  "bin": {