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

188 lines (156 loc) 5.46 kB
import { $ } from "@helios-lang/ir" import { expectDefined } from "@helios-lang/type-utils" /** * @import { SourceMappedStringI } from "@helios-lang/ir" * @import { Definitions } from "../index.js" */ /** * @typedef {import("../typecheck/index.js").ScriptTypes} ScriptTypes */ export const TAB = " " export const PARAM_IR_PREFIX = "__" export const PARAM_IR_MACRO = `${PARAM_IR_PREFIX}param` /** * Wraps 'inner' IR source with some definitions (used for top-level statements and for builtins) * @param {SourceMappedStringI} inner * @param {Definitions} definitions - name -> definition * @returns {SourceMappedStringI} */ export function wrapWithDefs(inner, definitions) { const keys = Array.from(definitions.keys()).reverse() let res = inner for (let key of keys) { const definition = definitions.get(key) if (definition === undefined) { throw new Error("unexpected") } else { res = $([ $("("), definition.keySite ? $(key, definition.keySite) : $(key), $(") -> {\n"), res, $(`\n}(\n${TAB}/*${key}*/\n${TAB}`), definition.content, $("\n)") ]) } } return res } /** * @typedef {{ * dependsOnOwnHash: boolean * purpose?: string * name: string * hashDependencies: Record<string, string> * validatorTypes?: ScriptTypes * validatorIndices?: Record<string, number> * currentScriptValue?: string * makeParamsSubstitutable: boolean * }} ExtraDefOptions */ /** * @param {ExtraDefOptions} options * @returns {Definitions} */ export function genExtraDefs(options) { /** * @type {Definitions} */ const extra = new Map() // inject hashes of other validators Object.entries(options.hashDependencies).forEach(([depName, dep]) => { dep = dep.startsWith("#") ? dep : `#${dep}` const key = `__helios__scripts__${depName}` if (options.makeParamsSubstitutable) { extra.set(key, { content: $`${PARAM_IR_MACRO}("${key}", ${dep})` }) } else { extra.set(key, { content: $(dep) }) } }) if (options.dependsOnOwnHash) { const key = `__helios__scripts__${options.name}` let ir = expectDefined( /** @type {Record<string, SourceMappedStringI>} */ ({ mixed: $(`__helios__scriptcontext__get_current_script_hash()`), spending: $( `__helios__scriptcontext__get_current_validator_hash()` ), minting: $( `__helios__scriptcontext__get_current_minting_policy_hash()` ), staking: $( `__helios__scriptcontext__get_current_staking_validator_hash()` ) })[expectDefined(options.purpose)] ) const ownHash = options.hashDependencies[options.name] if (ownHash && ownHash.length > 1) { // make sure ownHash isn't a dummy value // this is a special situation in which we know the ownHash because it is derived from another compilation, but we still want to call these functions because they might fail ir = $`(_ignored) -> { #${ownHash.startsWith("#") ? ownHash.slice(1) : ownHash} }(${ir})` } extra.set(key, { content: ir }) } if (options.currentScriptValue) { extra.set(`__helios__scriptcontext__current_script`, { content: $(options.currentScriptValue) }) } // also add script enum `__is` methods if (options.validatorIndices) { const validatorIndices = options.validatorIndices Object.keys(validatorIndices).forEach((scriptName) => { const key = `__helios__script__${scriptName}____is` // only way to instantiate a Script is via ScriptContext::current_script, so `__new` functions aren't needed const index = validatorIndices[scriptName] const ir = $`(cs) -> { __core__equalsInteger(__core__fstPair(__core__unConstrData(cs)), ${index}) }` extra.set(key, { content: ir }) }) } else if (options.validatorTypes) { // backup way of defining __helios__script__<name>____is Object.keys(options.validatorTypes).forEach((scriptName) => { const key = `__helios__script__${scriptName}____is` const ir = $`(_) -> { ${options.name == scriptName ? "true" : "false"} }` extra.set(key, { content: ir }) }) } return extra } /** * @protected * @param {SourceMappedStringI} ir * @param {Definitions} definitions * @returns {Set<string>} */ export function collectAllUsed(ir, definitions) { /** * Set of global paths * @type {Set<string>} */ const used = new Set() /** * @type {SourceMappedStringI[]} */ const stack = [ir] const RE = /__[a-zA-Z0-9_[\]@]+/g while (stack.length > 0) { const ir = expectDefined(stack.pop()) ir.search(RE, (match) => { if (!used.has(match)) { used.add(match) const def = definitions.get(match) if (def) { stack.push(def.content) } } }) } return used }