Skip to content

5.1. Using the Parser#

The Parser API provides us with fine-grained control over the parsing process. It allows us to parse not just the input as a top-level source unit, but also individual constructs like contracts, various definitions, and even expressions.

Parsing Source Files#

Let's start with this simple source file, that contains a single contract, and parse it into a concrete syntax tree. The parser will produce a ParseOutput object, which contains a SourceUnit root node:

parsing-source-files.mts
import assert from "node:assert";
import { Parser } from "@nomicfoundation/slang/parser";
import { assertNonterminalNode, NonterminalKind } from "@nomicfoundation/slang/cst";

test("parsing source files", () => {
  const source = `
    contract Foo {}
  `;

  const parser = Parser.create("0.8.28");
  const parseOutput = parser.parseFileContents(source);

  assert(parseOutput.isValid());
  assertNonterminalNode(parseOutput.tree, NonterminalKind.SourceUnit, source);
});

Parsing Nonterminals#

The parser API also allows you to parse specific nonterminal nodes, like statements or expressions. This is useful when you want to parse a snippet, and not an entire source file, like the AdditiveExpression node below:

parsing-nonterminals.mts
import assert from "node:assert";
import { Parser } from "@nomicfoundation/slang/parser";
import {
  assertNonterminalNode,
  assertTerminalNode,
  EdgeLabel,
  NonterminalKind,
  TerminalKind,
} from "@nomicfoundation/slang/cst";

test("parsing nonterminals", () => {
  const source = `x+y`;

  const parser = Parser.create("0.8.28");
  const parseOutput = parser.parseNonterminal(NonterminalKind.AdditiveExpression, source);

  assert(parseOutput.isValid());
  assertNonterminalNode(parseOutput.tree, NonterminalKind.AdditiveExpression, source);

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

  assert.strictEqual(children[0].label, EdgeLabel.LeftOperand);
  assertNonterminalNode(children[0].node, NonterminalKind.Expression, "x");

  assert.strictEqual(children[1].label, EdgeLabel.Operator);
  assertTerminalNode(children[1].node, TerminalKind.Plus, "+");

  assert.strictEqual(children[2].label, EdgeLabel.RightOperand);
  assertNonterminalNode(children[2].node, NonterminalKind.Expression, "y");
});

Handling Trivia Nodes#

Trivia nodes represent comments, whitespace, newlines, and other non-essential terminals that can appear anywhere in the source code, and they are categorized by the parser into two groups:

  • Leading Trivia: terminals that appear before a significant terminal, and can span multiple lines (for example, documentation comments).
  • Trailing Trivia: terminals that appear after a significant terminal on the same line, leading to, and including, the following newline terminal.

You can use the TerminalKindExtensions.isTrivia() API to check if a terminal is a trivia terminal.

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

test("handling trivia", () => {
  const source = `  true\n`;

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

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

  assert.strictEqual(children[0].label, EdgeLabel.LeadingTrivia);
  assertTerminalNode(children[0].node, TerminalKind.Whitespace, "  ");
  assert(TerminalKindExtensions.isTrivia(children[0].node.kind));

  assert.strictEqual(children[1].label, EdgeLabel.Variant);
  assertTerminalNode(children[1].node, TerminalKind.TrueKeyword, "true");
  assert(!TerminalKindExtensions.isTrivia(children[1].node.kind));

  assert.strictEqual(children[2].label, EdgeLabel.TrailingTrivia);
  assertTerminalNode(children[2].node, TerminalKind.EndOfLine, "\n");
  assert(TerminalKindExtensions.isTrivia(children[2].node.kind));
});