8. Examples#
Throughout the examples in this section we will use a sample contract. We need some setup code to create a CompilationUnit
with the contents of the contract:
sample-contract.mts
import { CompilationUnit } from "@nomicfoundation/slang/compilation";
import { buildCompilationUnit } from "./compilation-builder.mjs";
const CONTRACT_VFS = new Map<string, string>([
[
"contract.sol",
`
contract Counter {
uint _count;
constructor(uint initialCount) {
_count = initialCount;
}
function count() public view returns (uint) {
return _count;
}
function increment(uint delta) public returns (uint) {
require(delta > 0, "Delta must be positive");
_count += delta;
return _count;
}
}
`,
],
]);
export default function (): Promise<CompilationUnit> {
return buildCompilationUnit(CONTRACT_VFS, "0.8.0", "contract.sol");
}
compilation-builder.mts
import { Cursor } from "@nomicfoundation/slang/cst";
import { CompilationBuilder, CompilationUnit } from "@nomicfoundation/slang/compilation";
function buildReadFile(virtualFs: Map<string, string>): (fileId: string) => Promise<string> {
return async (fileId: string) => {
const contents = virtualFs.get(fileId);
if (!contents) {
throw new Error(`${fileId} not found`);
}
return contents;
};
}
async function resolveImport(_sourceFileId: string, importPath: Cursor) {
const importLiteral = importPath.node.unparse();
const importString = importLiteral.replace(/^["']/, "").replace(/["']$/, "");
return importString;
}
export async function buildCompilationUnit(
virtualFs: Map<string, string>,
version: string,
mainFileId: string,
): Promise<CompilationUnit> {
const builder = CompilationBuilder.create({
languageVersion: version,
readFile: buildReadFile(virtualFs),
resolveImport,
});
await builder.addFile(mainFileId);
return builder.build();
}
For the last two examples we also need an easy way to obtain a Cursor
pointing to a specific line and column in a file. This can be achieved using the Cursor Navigation API:
find-terminal-node-at.mts
import assert from "node:assert";
import { Cursor } from "@nomicfoundation/slang/cst";
export function findTerminalNodeAt(cursor: Cursor, line: number, column: number): Cursor | undefined {
const range = cursor.textRange;
if (
line < range.start.line ||
(line == range.start.line && column < range.start.column) ||
line > range.end.line ||
(line == range.end.line && column > range.end.column)
) {
// initial cursor is outside of the range of the CST tree
return undefined;
}
outer: while (cursor.node.isNonterminalNode()) {
assert(cursor.goToFirstChild());
do {
const childRange = cursor.textRange;
if (line < childRange.end.line || (line == childRange.end.line && column <= childRange.end.column)) {
continue outer;
}
} while (cursor.goToNextSibling());
// we should have found a child to recurse into (or the target terminal node)
throw new Error("should not be reached");
}
assert(cursor.node.isTerminalNode());
return cursor;
}