@stxt-lang/core 0.14.0 → 0.15.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.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Parser and schema validator for **STXT**, an indentation-based structured-text f
5
5
  STXT is a plain-text format for writing structured, semantic documents: no braces, no closing tags, just indentation. It is designed to be equally readable by humans and by machines, and it comes with an optional schema layer so documents can be validated.
6
6
 
7
7
  - Website and language reference: <https://stxt.dev>
8
- - VSCode extension: [STXT - Semantic Text](https://marketplace.visualstudio.com/items?itemName=stxt-lang.stxt)
8
+ - VSCode extension: [STXT Language](https://marketplace.visualstudio.com/items?itemName=stxt-lang.stxt)
9
9
  - Java implementation: [`dev.stxt:stxt-core`](https://central.sonatype.com/artifact/dev.stxt/stxt-core) on Maven Central
10
10
  - Python implementation: [`stxt`](https://pypi.org/project/stxt/) on PyPI
11
11
 
@@ -334,6 +334,10 @@ if (errors.length === 0) {
334
334
  }
335
335
  ```
336
336
 
337
+ `Formatter.format` takes the same limits as the parser as an optional third argument —
338
+ `Formatter.format(source, IndentStyle.TABS, { maxInputSize: -1 })` — since formatting parses
339
+ the document with them (STXT-SPEC §11.2).
340
+
337
341
  ## API surface
338
342
 
339
343
  Everything importable from the package:
@@ -64,7 +64,7 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
64
64
  if (lastNodeBlock && level > lastLevel) {
65
65
  const text = StringUtils_1.StringUtils.rightTrim(line.substring(pointer + 1));
66
66
  // The prefix covering the block level must be homogeneous (spec 10.2, rule 2);
67
- // empty lines are always preserved and are exempt from it (spec 10.3)
67
+ // empty lines are never an error and are exempt from it (spec 10.3)
68
68
  if (validate && sawSpace && sawTab && text.length > 0) {
69
69
  throw new ParseException_1.ParseException(numLine, "INDENTATION_MIXED", `Mixed tabs and spaces in indentation`);
70
70
  }
@@ -257,6 +257,13 @@ class Parser {
257
257
  closeToLevel(stack, targetLevel, result) {
258
258
  while (stack.length > targetLevel) {
259
259
  const completed = stack.pop();
260
+ // A closing block node drops its final empty lines (STXT-SPEC §10.3): they are not
261
+ // content, only visual separation or an editor's final line breaks. The validators
262
+ // and observers below already see the trimmed node; onTextLine did fire for these
263
+ // lines while the block was open, as process observation of the source.
264
+ if (completed instanceof TextNode_1.TextNode) {
265
+ completed.removeTrailingEmptyLines();
266
+ }
260
267
  // Hand it over to the validators
261
268
  this.validators.forEach(validator => {
262
269
  try {
@@ -56,6 +56,13 @@ export declare class TextNode extends Node {
56
56
  addTextLine(line: string): void;
57
57
  /** Removes every text line. */
58
58
  clearText(): void;
59
+ /**
60
+ * Removes the final empty lines (`""` elements at the end of the lines). The {@link Parser}
61
+ * calls it when the block closes (STXT-SPEC §10.3: the final empty lines of a block are not
62
+ * content); it is public because a programmatically built node may want the same
63
+ * normalization before writing.
64
+ */
65
+ removeTrailingEmptyLines(): void;
59
66
  getText(): string;
60
67
  isTextNode(): boolean;
61
68
  private static splitLines;
@@ -61,6 +61,17 @@ class TextNode extends Node_1.Node {
61
61
  clearText() {
62
62
  this.lines.length = 0;
63
63
  }
64
+ /**
65
+ * Removes the final empty lines (`""` elements at the end of the lines). The {@link Parser}
66
+ * calls it when the block closes (STXT-SPEC §10.3: the final empty lines of a block are not
67
+ * content); it is public because a programmatically built node may want the same
68
+ * normalization before writing.
69
+ */
70
+ removeTrailingEmptyLines() {
71
+ while (this.lines.length > 0 && this.lines[this.lines.length - 1] === "") {
72
+ this.lines.pop();
73
+ }
74
+ }
64
75
  getText() {
65
76
  return this.lines.join("\n");
66
77
  }
@@ -1,3 +1,4 @@
1
+ import { ParserOptions } from "../core/Parser";
1
2
  import { ParseException } from "../exceptions/ParseException";
2
3
  import { IndentStyle } from "./NodeWriter";
3
4
  /** The outcome of {@link Formatter.format}. */
@@ -32,10 +33,10 @@ export interface FormatResult {
32
33
  * - A **text line of a block** gets the indentation of the block (its level plus one) in the
33
34
  * requested style, followed by its content; any indentation the line had beyond the block's
34
35
  * is content (STXT-SPEC §10.2, relative indentation is preserved) and is kept exactly. A
35
- * blank line of the block is `""` in the content whatever it looks like in the source
36
- * (STXT-SPEC §10.3), so it is written with the indentation of the block too: the block reads
37
- * as one piece and, at the end of the file, the line is not lost — an empty last line would
38
- * be indistinguishable from the final line ending.
36
+ * blank line that precedes more block text is `""` in the content whatever it looks like in
37
+ * the source (STXT-SPEC §10.3), so it is written with the indentation of the block too: the
38
+ * block reads as one piece. The final blank lines of a block are not content (STXT-SPEC
39
+ * §10.3: the parser drops them when the block closes) and fall under the next rule.
39
40
  * - Every **other line** — a comment, a blank line outside a block, or a line the parse tree
40
41
  * does not describe because of a syntax error — is kept as the author wrote it, except that
41
42
  * its trailing blanks are removed and the whole indentation units at its start are converted
@@ -58,9 +59,13 @@ export declare class Formatter {
58
59
  *
59
60
  * @param text the document.
60
61
  * @param style indentation style to format with; tabs by default.
62
+ * @param options limits for the internal parser (STXT-SPEC 11.2); every omitted one keeps
63
+ * its recommended default, and -1 disables one. A limit exceeded shows up in
64
+ * the errors like any other syntax error, and the lines the aborted parse
65
+ * never described are converted as "other lines" (indentation units only).
61
66
  * @returns the formatted text and the syntax errors found; see {@link FormatResult}.
62
67
  */
63
- static format(text: string, style?: IndentStyle): FormatResult;
68
+ static format(text: string, style?: IndentStyle, options?: ParserOptions): FormatResult;
64
69
  /**
65
70
  * Formats one source line.
66
71
  *
@@ -23,10 +23,10 @@ const NodeWriter_1 = require("./NodeWriter");
23
23
  * - A **text line of a block** gets the indentation of the block (its level plus one) in the
24
24
  * requested style, followed by its content; any indentation the line had beyond the block's
25
25
  * is content (STXT-SPEC §10.2, relative indentation is preserved) and is kept exactly. A
26
- * blank line of the block is `""` in the content whatever it looks like in the source
27
- * (STXT-SPEC §10.3), so it is written with the indentation of the block too: the block reads
28
- * as one piece and, at the end of the file, the line is not lost — an empty last line would
29
- * be indistinguishable from the final line ending.
26
+ * blank line that precedes more block text is `""` in the content whatever it looks like in
27
+ * the source (STXT-SPEC §10.3), so it is written with the indentation of the block too: the
28
+ * block reads as one piece. The final blank lines of a block are not content (STXT-SPEC
29
+ * §10.3: the parser drops them when the block closes) and fall under the next rule.
30
30
  * - Every **other line** — a comment, a blank line outside a block, or a line the parse tree
31
31
  * does not describe because of a syntax error — is kept as the author wrote it, except that
32
32
  * its trailing blanks are removed and the whole indentation units at its start are converted
@@ -49,15 +49,19 @@ class Formatter {
49
49
  *
50
50
  * @param text the document.
51
51
  * @param style indentation style to format with; tabs by default.
52
+ * @param options limits for the internal parser (STXT-SPEC 11.2); every omitted one keeps
53
+ * its recommended default, and -1 disables one. A limit exceeded shows up in
54
+ * the errors like any other syntax error, and the lines the aborted parse
55
+ * never described are converted as "other lines" (indentation units only).
52
56
  * @returns the formatted text and the syntax errors found; see {@link FormatResult}.
53
57
  */
54
- static format(text, style = NodeWriter_1.IndentStyle.TABS) {
58
+ static format(text, style = NodeWriter_1.IndentStyle.TABS, options) {
55
59
  // STXT-TREE-SPEC 12.1: an initial BOM is not kept
56
60
  if (text.startsWith("\uFEFF")) {
57
61
  text = text.substring(1);
58
62
  }
59
63
  const sourceLines = new SourceLines();
60
- const parser = new Parser_1.Parser();
64
+ const parser = new Parser_1.Parser(options);
61
65
  parser.registerObserver(sourceLines);
62
66
  const result = parser.parseResult(text);
63
67
  const eol = text.includes("\r\n") ? "\r\n" : "\n";
@@ -81,8 +85,11 @@ class Formatter {
81
85
  if (node) {
82
86
  return Formatter.renderNode(node, line, style);
83
87
  }
88
+ // A final empty line of a block is not content (STXT-SPEC §10.3): the parser removed it
89
+ // from the node when the block closed, so its index falls beyond the logical lines. It
90
+ // is kept as any other line: blank, unindented.
84
91
  const text = sourceLines.textAt(lineNumber);
85
- if (text) {
92
+ if (text && text.index < text.node.getTextLines().length) {
86
93
  return Formatter.indent(text.node.getLevel() + 1, style) + text.line.content;
87
94
  }
88
95
  return Formatter.convertUnits(StringUtils_1.StringUtils.rightTrim(line), style);
@@ -168,7 +175,10 @@ class SourceLines {
168
175
  // Comment lines need no bookkeeping: every line that opens no node is treated alike.
169
176
  }
170
177
  onTextLine(node, lineNumber, lineString, line) {
171
- this.textByLine.set(lineNumber, { node, line });
178
+ // The line was just appended: its 0-based index in the block is the current last. After
179
+ // the block closes and drops its final empty lines (STXT-SPEC §10.3), an index beyond
180
+ // getTextLines() marks the line as a final empty line, not content.
181
+ this.textByLine.set(lineNumber, { node, line, index: node.getTextLines().length - 1 });
172
182
  }
173
183
  /**
174
184
  * @param lineNumber line number, 1-indexed.
@@ -179,8 +189,9 @@ class SourceLines {
179
189
  }
180
190
  /**
181
191
  * @param lineNumber line number, 1-indexed.
182
- * @returns the block node this line is text of and the line already split into indentation
183
- * and content, or undefined if the line is not text of a block.
192
+ * @returns the block node this line is text of, the line already split into indentation and
193
+ * content, and its 0-based index in the block; or undefined if the line is not text
194
+ * of a block.
184
195
  */
185
196
  textAt(lineNumber) {
186
197
  return this.textByLine.get(lineNumber);
@@ -59,9 +59,17 @@ class NodeWriter {
59
59
  }
60
60
  if (n instanceof TextNode_1.TextNode) {
61
61
  out.push(" >>\n");
62
- for (const line of n.getTextLines()) {
62
+ // Final empty lines are not emitted (STXT-TREE-SPEC 11.1 rule 6): parsing never
63
+ // produces them (STXT-SPEC 10.3), and on a programmatically built node they would
64
+ // not survive the round trip.
65
+ const lines = n.getTextLines();
66
+ let last = lines.length;
67
+ while (last > 0 && lines[last - 1] === "") {
68
+ last--;
69
+ }
70
+ for (let i = 0; i < last; i++) {
63
71
  NodeWriter.indent(out, depth + 1, style);
64
- out.push(line, "\n");
72
+ out.push(lines[i], "\n");
65
73
  }
66
74
  }
67
75
  else if (n instanceof InlineNode_1.InlineNode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Parser and schema validator for STXT, an indentation-based structured-text format.",
5
5
  "main": "out/all.js",
6
6
  "types": "out/all.d.ts",
@@ -34,7 +34,6 @@
34
34
  },
35
35
  "keywords": [
36
36
  "stxt",
37
- "semantic text",
38
37
  "parser",
39
38
  "schema",
40
39
  "validation",