UNPKG

@helios-lang/compiler

Version:

Helios is a Domain Specific Language that compiles to Plutus-Core (i.e. Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus. With this library you can compile Helios scripts and build Cardano transactions, all you need to bu

132 lines (109 loc) 3.44 kB
import { makeErrorCollector, makeSource, makeTokenReader, makeTokenizer, mergeSites } from "@helios-lang/compiler-utils" import { Statement } from "../statements/index.js" import { ParseContext } from "./ParseContext.js" import { extractName, parseHeader } from "./parseHeader.js" import { parseStatements } from "./parseStatements.js" /** * @import { ErrorCollector, Source, Token, TokenReader, Word } from "@helios-lang/compiler-utils" * @typedef {import("./ScriptPurpose.js").ScriptPurpose} ScriptPurpose */ const ENTRY_POINT_NAME = "main" /** * `entryPointIndex` is the index in the `statements` array of the `main` function * `entryPointIndex` is set to -1 for modules * If a `main` function isn't found for a non-module script an error is thrown * @typedef {{ * purpose: Word * name: Word * statements: Statement[] * entryPointIndex: number * }} ParsedScript */ /** * @param {string | Source} src * @param {ErrorCollector | undefined} errorCollector * @returns {ParsedScript} */ export function parseScript(src, errorCollector = undefined) { const errors = errorCollector ?? makeErrorCollector() const reader = tokenizeScript(src, errors) const ctx = new ParseContext(reader) const [purpose, name] = parseHeader(ctx) const statements = parseStatements(ctx) const entryPointIndex = findEntryPoint(ctx, purpose, statements) if (!errorCollector) { errors.throw() } return { purpose, name, statements, entryPointIndex } } /** * @param {string | Source} rawSrc * @returns {Source} */ export function createSource(rawSrc) { return typeof rawSrc == "string" ? makeSource(rawSrc, { name: extractName(rawSrc) ?? "unknown" }) : makeSource(rawSrc.content, { name: rawSrc.name }) // the input Source instance might use a class from a different package } /** * @param {string | Source} rawSrc * @param {ErrorCollector} errorCollector * @returns {TokenReader} */ function tokenizeScript(rawSrc, errorCollector) { const src = createSource(rawSrc) const tokenizer = makeTokenizer(src, { errorCollector: errorCollector, preserveNewlines: true }) const ts = tokenizer.tokenize() return makeTokenReader({ tokens: ts, errors: errorCollector, ignoreNewlines: true }) } /** * Throws an error if main isn't found for a non-module * @param {ParseContext} ctx * @param {Word} purpose * @param {Statement[]} statements * @returns {number} */ function findEntryPoint({ errors }, purpose, statements) { const p = purpose.value if (p != "module") { if (statements.length == 0) { errors.syntax(purpose.site, "empty script") } else { let i = statements.findIndex( (s) => s.name.value === ENTRY_POINT_NAME ) if (i == -1) { const firstStatementSite = statements[0].site const lastStatementSite = statements[statements.length - 1].site const scriptBodySite = mergeSites( firstStatementSite, lastStatementSite ) errors.syntax( scriptBodySite, `entrypoint '${ENTRY_POINT_NAME}' not found` ) } return i } } return -1 }