@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
177 lines (152 loc) • 4.66 kB
JavaScript
import { ModuleScope, builtinNamespaces } from "../scopes/index.js"
import {
ConstStatement,
ImportFromStatement,
ImportModuleStatement,
Statement
} from "../statements/index.js"
/**
* @import { Source, Word } from "@helios-lang/compiler-utils"
* @import { TypeCheckContext } from "../index.js"
*/
/**
* A Module is a collection of statements
*/
export class Module {
/**
* @readonly
* @type {Word}
*/
name
/**
* @readonly
* @type {Source}
*/
sourceCode
/**
* @private
* @readonly
* @type {Statement[]}
*/
_statements
/**
* @param {Word} name
* @param {Statement[]} statements
* @param {Source} sourceCode
*/
constructor(name, statements, sourceCode) {
this.name = name
this._statements = statements
this._statements.forEach((s) =>
s.setBasePath(`__module__${this.name.toString()}`)
)
this.sourceCode = sourceCode
}
/**
* @type {Statement[]}
*/
get statements() {
return this._statements.slice()
}
/**
* @returns {string}
*/
toString() {
return this._statements.map((s) => s.toString()).join("\n")
}
/**
* @param {TypeCheckContext} ctx
* @param {ModuleScope} scope
*/
evalTypes(ctx, scope) {
for (let s of this.statements) {
s.eval(ctx, scope)
}
}
/**
* This module can depend on other modules
* @param {TypeCheckContext} ctx
* @param {Module[]} modules
* @param {Module[]} [stack]
* @returns {Module[]}
*/
filterDependencies(ctx, modules, stack = []) {
/**
* @type {Module[]}
*/
let deps = []
/** @type {Module[]} */
let newStack = [this]
newStack = newStack.concat(stack)
for (let s of this._statements) {
if (
s instanceof ImportFromStatement ||
s instanceof ImportModuleStatement
) {
let mn = s.moduleName.value
if (mn == this.name.value) {
ctx.errors.syntax(s.site, "can't import self")
continue
} else if (stack.some((d) => d.name.value == mn)) {
ctx.errors.syntax(s.site, "circular import detected")
continue
}
// if already in deps, then don't add (because it will have been added before along with all its dependencies)
if (!deps.some((d) => d.name.value == mn)) {
let m = modules.find((m) => m.name.value == mn)
if (mn in builtinNamespaces) {
if (m) {
ctx.errors.syntax(
m.name.site,
"reserved module name"
)
continue
} else {
continue
}
}
if (!m) {
ctx.errors.reference(s.site, `module '${mn}' not found`)
continue
} else {
// only add deps that weren't added before
let newDeps = m
.filterDependencies(ctx, modules, newStack)
.concat([m])
.filter(
(d) =>
!deps.some(
(d_) => d_.name.value == d.name.value
)
)
deps = deps.concat(newDeps)
}
}
}
}
return deps
}
/**
*
* @param {(name: string, statement: ConstStatement) => void} callback
*/
loopConstStatements(callback) {
/**
* @type {[string, {statements: Statement[]}][]}
*/
const stack = [[this.name.value, this]]
let head = stack.pop()
while (head) {
const [path, stmnt] = head
stmnt.statements.forEach((statement) => {
const path_ = `${path}::${statement.name.value}`
if (statement instanceof ConstStatement) {
callback(path_, statement)
} else {
stack.push([path_, statement])
}
})
head = stack.pop()
}
}
}