Skip to content

Using the Parser#

Using the API directly provides us with a more 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:

input.sol
contract Foo {}

We begin by creating a Language object with a specified version. This is an entry point for our parser API. Then we can use it to parse the source file, specifying the top-level nonterminal to parse:

import assert from "node:assert";
import { Language } from "@nomicfoundation/slang/language";
import { NonterminalKind, TerminalKind } from "@nomicfoundation/slang/kinds";
import { NonterminalNode } from "@nomicfoundation/slang/cst";

const language = new Language("0.8.0");

const parseOutput = language.parse(NonterminalKind.ContractDefinition, source);

Checking for Syntax Errors#

If the file has errors, we can get them from the ParseOutput type, and print them out:

for (const error of parseOutput.errors()) {
  console.error(`Error at byte offset ${error.textRange().start.utf8}: ${error.message()}`);
}

Otherwise, we can check if input is valid using this helpful utility:

assert(parseOutput.isValid);

Inspecting the Parse Tree#

Now, let's try to inspect the resulting CST, and iterate on its children:

const contract = parseOutput.tree();
assert(contract instanceof NonterminalNode);
assert.equal(contract.kind, NonterminalKind.ContractDefinition);

const contractChildren = contract.children();
assert.equal(contractChildren.length, 7);

const [contractKeyword, firstSpace, contractName, secondSpace, openBrace, members, closeBrace] = contractChildren;

assert.equal(contractKeyword?.kind, TerminalKind.ContractKeyword);
assert.equal(firstSpace?.kind, TerminalKind.Whitespace);
assert.equal(contractName?.kind, TerminalKind.Identifier);
assert.equal(secondSpace?.kind, TerminalKind.Whitespace);
assert.equal(openBrace?.kind, TerminalKind.OpenBrace);
assert.equal(members?.kind, NonterminalKind.ContractMembers);
assert.equal(closeBrace?.kind, TerminalKind.CloseBrace);

Additionally, we can convert the CST node back into the input string:

const contractSource = contract.unparse();
assert.equal(contractSource, "contract Foo {}");