Skip to content

5.2. Handling Syntax Errors#

If there are syntax errors with the input, the ParseOutput object will contain the list of errors found. Each error will have a message, and a TextRange that indicates the location of the error in the input.

Additionally, the parsed tree will contain an error node for each error encountered, which can be one of two kinds, missing or unrecognized, depending on the error.

You can use the TerminalKindExtensions.isValid() API to check if a terminal is valid or is an error node.

Missing Nodes#

These occur when the parser expects a certain node to be present, but it is not. It will create a missing node in its place, and continue parsing as if it was present.

missing-error-nodes.mts
import assert from "node:assert";
import { Parser } from "@nomicfoundation/slang/parser";
import { assertTerminalNode, NonterminalKind, TerminalKind, TerminalKindExtensions } from "@nomicfoundation/slang/cst";

test("missing error nodes", () => {
  const source = `contract`;

  const parser = Parser.create("0.8.28");
  const parseOutput = parser.parseNonterminal(NonterminalKind.ContractDefinition, source);
  assert(!parseOutput.isValid());

  const errors = parseOutput.errors();
  assert.strictEqual(errors.length, 1);

  assert.strictEqual(errors[0].message, "Expected Identifier.");
  assert.deepStrictEqual(errors[0].textRange, {
    start: { line: 0, column: 8, utf8: 8, utf16: 8 },
    end: { line: 0, column: 8, utf8: 8, utf16: 8 },
  });

  const children = parseOutput.tree.children();
  assert.strictEqual(children.length, 2);

  assertTerminalNode(children[0].node, TerminalKind.ContractKeyword, "contract");

  assertTerminalNode(children[1].node, TerminalKind.Missing, "");
  assert(!TerminalKindExtensions.isValid(children[1].node.kind));
});

Unrecognized Nodes#

These occur when the parser encounters a token that it does not recognize, and it cannot parse. It will create an unrecognized node in its place, and continue parsing as if it doesn't exist.

unrecognized-error-nodes.mts
import assert from "node:assert";
import { Parser } from "@nomicfoundation/slang/parser";
import {
  assertNonterminalNode,
  assertTerminalNode,
  NonterminalKind,
  TerminalKind,
  TerminalKindExtensions,
} from "@nomicfoundation/slang/cst";

test("unrecognized error nodes", () => {
  const source = `
    foo();
    %`;

  const parser = Parser.create("0.8.28");
  const parseOutput = parser.parseNonterminal(NonterminalKind.Statements, source);
  assert(!parseOutput.isValid());

  const errors = parseOutput.errors();
  assert.strictEqual(errors.length, 1);

  assert.match(errors[0].message, /^Expected AddressKeyword or/);
  assert.deepStrictEqual(errors[0].textRange, {
    start: { line: 2, column: 4, utf8: 16, utf16: 16 },
    end: { line: 2, column: 5, utf8: 17, utf16: 17 },
  });

  const children = parseOutput.tree.children();
  assert.strictEqual(children.length, 3);

  assertNonterminalNode(children[0].node, NonterminalKind.Statement);

  assertTerminalNode(children[1].node, TerminalKind.Whitespace, "    ");
  assert(TerminalKindExtensions.isValid(children[1].node.kind));

  assertTerminalNode(children[2].node, TerminalKind.Unrecognized, "%");
  assert(!TerminalKindExtensions.isValid(children[2].node.kind));
});