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:
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:
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.
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));
});