@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
218 lines (186 loc) • 5.96 kB
JavaScript
import { expectDefined } from "@helios-lang/type-utils"
import { FTPP, ParametricName, TTPP } from "../codegen/index.js"
import { Scope } from "../scopes/index.js"
import {
FuncType,
GenericType,
Parameter,
ParametricFunc,
ParametricType
} from "../typecheck/index.js"
import { TypeParameter } from "./TypeParameter.js"
/**
* @import { Site } from "@helios-lang/compiler-utils"
* @import { TypeCheckContext } from "../index.js"
* @typedef {import("../typecheck/index.js").DataType} DataType
* @typedef {import("../typecheck/index.js").EvalEntity} EvalEntity
* @typedef {import("../typecheck/index.js").InferenceMap} InferenceMap
*/
export class TypeParameters {
/**
* @private
* @readonly
* @type {TypeParameter[]}
*/
_parameterExprs
/**
* @private
* @readonly
* @type {string}
*/
_prefix
/**
* @private
* @type {null | Parameter[]}
*/
_parameters
/**
* @param {TypeParameter[]} parameterExprs
* @param {boolean} isForFunc
*/
constructor(parameterExprs, isForFunc) {
this._parameterExprs = parameterExprs
this._prefix = isForFunc ? FTPP : TTPP
this._parameters = null
}
/**
* @returns {boolean}
*/
hasParameters() {
return this._parameterExprs.length > 0
}
/**
* @type {string[]}
*/
get parameterNames() {
return this._parameterExprs.map((pe) => pe.name)
}
/**
* @returns {Parameter[]}
*/
getParameters() {
return expectDefined(this._parameters, "parameters not yet evaluated")
}
/**
* Always include the braces, even if there aren't any type parameters, so that the mutual recursion injection function has an easier time figuring out what can depend on what
* @param {string} base
* @returns {string}
*/
genTypePath(base) {
return `${base}[${this._parameterExprs.map((_, i) => `${this._prefix}${i}`).join("@")}]`
}
/**
* Always include the braces, even if there aren't any type parameters, so that the mutual recursion injection function has an easier time figuring out what can depend on what
* @param {string} base
* @returns {string}
*/
genFuncPath(base) {
if (this.hasParameters()) {
return this.genTypePath(base)
} else {
return base
}
}
/**
* @returns {string}
*/
toString() {
if (!this.hasParameters()) {
return ""
} else {
return `[${this._parameterExprs.map((p) => p.toString()).join(", ")}]`
}
}
/**
* @param {TypeCheckContext} ctx
* @param {Scope} scope
* @returns {Scope}
*/
evalParams(ctx, scope) {
const subScope = new Scope(scope)
this._parameters = []
this._parameterExprs.forEach((pe, i) => {
const p = pe.eval(ctx, subScope, `${this._prefix}${i}`)
if (p) {
this._parameters?.push(p)
}
})
return subScope
}
/**
* @param {TypeCheckContext} ctx
* @param {Scope} scope
* @param {(scope: Scope) => (FuncType)} evalConcrete
* @returns {ParametricFunc | FuncType}
*/
evalParametricFuncType(ctx, scope, evalConcrete, impl = null) {
const typeScope = this.evalParams(ctx, scope)
const type = evalConcrete(typeScope)
typeScope.assertAllUsed()
return this.hasParameters()
? new ParametricFunc(this.getParameters(), type)
: type
}
/**
* @param {TypeCheckContext} ctx
* @param {Scope} scope
* @param {(scope: Scope) => (FuncType)} evalConcrete
* @returns {EvalEntity}
*/
evalParametricFunc(ctx, scope, evalConcrete) {
const type = this.evalParametricFuncType(ctx, scope, evalConcrete)
if (type.asType) {
return type.asType.toTyped()
} else {
return type
}
}
/**
* @param {TypeCheckContext} ctx
* @param {Scope} scope
* @param {Site} site
* @param {(scope: Scope) => DataType} evalConcrete
* @returns {[DataType | ParametricType, Scope]}
*/
createParametricType(ctx, scope, site, evalConcrete) {
const typeScope = this.evalParams(ctx, scope)
const type = evalConcrete(new Scope(typeScope))
if (!this.hasParameters()) {
return [type, typeScope]
} else {
const paramType = new ParametricType({
name: type.name,
parameters: this.getParameters(),
apply: (paramTypes) => {
/**
* @type {InferenceMap}
*/
const map = new Map()
paramTypes.forEach((pt, i) => {
const p = this.getParameters()[i]
map.set(p, pt)
})
const appliedType = expectDefined(
type.infer(ctx, site, map, null).asDataType
)
const appliedPath = ParametricName.parse(type.path, true)
.toImplementation(
paramTypes.map(
(pt) => expectDefined(pt.asDataType).path
)
)
.toString()
if (appliedType instanceof GenericType) {
return appliedType.changeNameAndPath(
`${type.name}[${paramTypes.map((pt) => pt.toString()).join(",")}]`,
appliedPath
)
} else {
throw new Error("unexpected")
}
}
})
return [paramType, typeScope]
}
}
}