@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
205 lines (175 loc) • 5.74 kB
JavaScript
import { isCompilerError, makeSyntaxError } from "@helios-lang/compiler-utils"
import { createSource, parseScript } from "../parse/index.js"
import { MintingEntryPoint } from "./MintingEntryPoint.js"
import { SpendingEntryPoint } from "./SpendingEntryPoint.js"
import { StakingEntryPoint } from "./StakingEntryPoint.js"
import { TestingEntryPoint } from "./TestingEntryPoint.js"
import { Module } from "./Module.js"
import { MainModule } from "./MainModule.js"
import { GenericEntryPoint } from "./GenericEntryPoint.js"
import { MixedEntryPoint } from "./MixedEntryPoint.js"
import { ModuleCollection } from "./ModuleCollection.js"
/**
* @import { ErrorCollector, Source } from "@helios-lang/compiler-utils"
* @typedef {import("../typecheck/index.js").ScriptTypes} ScriptTypes
* @typedef {import("../typecheck/index.js").Type} Type
* @typedef {import("./EntryPoint.js").EntryPoint} EntryPoint
*/
/**
* Creates a new entry point
* This function can't be placed inside EntryPoint.js because that would create a circular import dependency
* @param {string | Source} mainSrc
* @param {(string | Source)[]} moduleSrcs - optional sources of modules, which can be used for imports
* @param {ScriptTypes} validatorTypes
* @param {ErrorCollector} errorCollector
* @returns {EntryPoint}
*/
export function newEntryPoint(
mainSrc,
moduleSrcs,
validatorTypes,
errorCollector
) {
const [purpose, modules] = parseMain(mainSrc, moduleSrcs, errorCollector)
const moduleCol = new ModuleCollection(modules)
/**
* @type {EntryPoint}
*/
let entryPoint
switch (purpose) {
case "testing":
entryPoint = new TestingEntryPoint(moduleCol)
break
case "spending":
entryPoint = new SpendingEntryPoint(moduleCol)
break
case "minting":
entryPoint = new MintingEntryPoint(moduleCol)
break
case "mixed":
entryPoint = new MixedEntryPoint(moduleCol)
break
case "staking":
entryPoint = new StakingEntryPoint(moduleCol)
break
default:
entryPoint = new GenericEntryPoint(purpose ?? "unknown", moduleCol)
}
// TODO: add type errors directly to ErrorCollector
try {
entryPoint.evalTypes(validatorTypes)
} catch (e) {
if (isCompilerError(e)) {
errorCollector.errors.push(e)
} else {
throw e
}
}
return entryPoint
}
/**
* @param {string | Source} mainSrc
* @param {(string | Source)[]} moduleSrcs
* @param {ErrorCollector} errorCollector
* @returns {[string | undefined, Module[]]}
*/
function parseMain(mainSrc, moduleSrcs, errorCollector) {
let [purpose, modules] = parseMainInternal(mainSrc, errorCollector)
const site = modules[0].name.site
const imports = parseImports(
modules[0].name.value,
moduleSrcs,
errorCollector
)
errorCollector.throw()
const mainImports = modules[0].filterDependencies(imports)
/**
* @type {Module[]}
*/
let postImports = []
if (modules.length > 1) {
postImports = modules[modules.length - 1]
.filterDependencies(imports)
.filter(
(m) => !mainImports.some((d) => d.name.value == m.name.value)
)
}
// create the final order of all the modules (this is the order in which statements will be added to the IR)
modules = mainImports
.concat([modules[0]])
.concat(postImports)
.concat(modules.slice(1))
if (purpose.value == "module") {
throw makeSyntaxError(site, "can't use module for main")
}
return [purpose.value, modules]
}
/**
* @param {string | Source} rawSrc
* @param {ErrorCollector} errorCollector
* @returns {[purpose, Module[]]}
*/
function parseMainInternal(rawSrc, errorCollector) {
const src = createSource(rawSrc)
const { purpose, name, statements, entryPointIndex } = parseScript(
src,
errorCollector
)
if (purpose && name) {
/**
* @type {Module[]}
*/
const modules = [
new MainModule(name, statements.slice(0, entryPointIndex + 1), src)
]
if (entryPointIndex < statements.length - 1) {
modules.push(
new Module(name, statements.slice(entryPointIndex + 1), src)
)
}
return [purpose, modules]
} else {
throw new Error("unexpected") // should've been caught by calling src.throwErrors() above
}
}
/**
* @param {string | Source} rawSrc
* @param {ErrorCollector} errorCollector
* @returns {Module}
*/
function parseModule(rawSrc, errorCollector) {
const src = createSource(rawSrc)
const { name, statements } = parseScript(src, errorCollector)
if (name) {
return new Module(name, statements, src)
} else {
throw new Error("unexpected") // should've been caught by calling src.throwErrors() above
}
}
/**
* @param {string} mainName
* @param {(string | Source)[]} moduleSrcs
* @param {ErrorCollector} errorCollector
* @returns {Module[]}
*/
function parseImports(mainName, moduleSrcs, errorCollector) {
let imports = moduleSrcs.map((src) => {
return parseModule(src, errorCollector)
})
/**
* @type {Set<string>}
*/
let names = new Set()
names.add(mainName)
for (let m of imports) {
if (names.has(m.name.value)) {
errorCollector.syntax(
m.name.site,
`non-unique module name '${m.name.value}'`
)
} else {
names.add(m.name.value)
}
}
return imports
}