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

816 lines (722 loc) 17.2 kB
import { makeDummySite, makeTypeError } from "@helios-lang/compiler-utils" import { expectDefined } from "@helios-lang/type-utils" import { Common, DataEntity, FuncType, GenericType, GenericEnumMemberType, TypedEntity, AllType } from "./common.js" import { Parameter } from "./Parameter.js" import { BoolType, ByteArrayType, RawDataType } from "./primitives.js" /** * @import { Site } from "@helios-lang/compiler-utils" * @import { TypeSchema } from "@helios-lang/type-utils" * @import { TypeCheckContext } from "../index.js" */ /** * @typedef {import("./common.js").GenericTypeProps} GenericTypeProps * @typedef {import("./common.js").GenericEnumMemberTypeProps} GenericEnumMemberTypeProps * @typedef {import("./common.js").EnumMemberType} EnumMemberType * @typedef {import("./common.js").ParameterI} ParameterI * @typedef {import("./common.js").InferenceMap} InferenceMap * @typedef {import("./common.js").DataType} DataType * @typedef {import("./common.js").Func} Func * @typedef {import("./common.js").EvalEntity} EvalEntity * @typedef {import("./common.js").Named} Named * @typedef {import("./common.js").Parametric} Parametric * @typedef {import("./common.js").Type} Type * @typedef {import("./common.js").Typed} Typed * @typedef {import("./common.js").TypeClass} TypeClass * @typedef {import("./common.js").InstanceMembers} InstanceMembers * @typedef {import("./common.js").TypeMembers} TypeMembers * @typedef {import("./common.js").TypeClassMembers} TypeClassMembers */ /** * Created by statements * @implements {DataType} */ export class GenericParametricType extends GenericType { /** * * @param {GenericTypeProps} props */ constructor(props) { super(props) } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { if (type) { return this } else { let isMaybeParametric = false map.forEach((v) => { if (v.isParametric()) { isMaybeParametric = true } }) const props = this.applyInternal(ctx, site, map) return isMaybeParametric ? new GenericParametricType(props) : new GenericType(props) } } } /** * Created by statements * @implements {EnumMemberType} * @extends {GenericEnumMemberType} */ export class GenericParametricEnumMemberType extends GenericEnumMemberType { /** * * @param {GenericEnumMemberTypeProps} props */ constructor(props) { super(props) } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { if (type) { return this } else { let isMaybeParametric = false map.forEach((v) => { if (v.isParametric()) { isMaybeParametric = true } }) const parentType = expectDefined( this.parentType.infer(ctx, site, map, null).asDataType ) const partialProps = this.applyInternal(ctx, site, map) /** * @type {GenericEnumMemberTypeProps} */ const props = { ...partialProps, parentType: parentType, constrIndex: this.constrIndex, genTypeSchema: (self, parents) => { const typeMembers = self.typeMembers return { kind: "variant", tag: this.constrIndex, name: this.name, id: partialProps.path, fieldTypes: partialProps.fieldNames.map((fn) => ({ name: fn, type: expectDefined( typeMembers[fn].asDataType ).toSchema(parents) })) } } } return isMaybeParametric ? new GenericParametricEnumMemberType(props) : new GenericEnumMemberType(props) } } } /** * @implements {Type} */ export class TypeClassImpl extends Common { /** * @private * @readonly * @type {string} */ _name /** * @private * @readonly * @type {null | ParameterI} */ _parameter /** * @private * @readonly * @type {InstanceMembers} */ _instanceMembers /** * @private * @readonly * @type {TypeMembers} */ _typeMembers /** * @param {TypeClass} typeClass * @param {string} name * @param {null | ParameterI} parameter - reference to original parameter, which is more unique than name */ constructor(typeClass, name, parameter) { super() this._name = name this._parameter = parameter this._instanceMembers = typeClass.genInstanceMembers(this) this._typeMembers = typeClass.genTypeMembers(this) } /** * @returns {boolean} */ isParametric() { return true } /** * @type {InstanceMembers} */ get instanceMembers() { return this._instanceMembers } /** * @type {string} */ get name() { return this._name } /** * @type {TypeMembers} */ get typeMembers() { return this._typeMembers } /** * @type {Type} */ get asType() { return this } /** * @internal * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { const p = expectDefined( this._parameter, "unable to infer dummy TypeClass instantiation" ) const prev = map.get(p) if (!prev) { if (type) { map.set(p, type) return type } else { // type not yet available: could be parametric func inside a parametric type return this } } else { return prev } } /** * Returns 'true' if 'this' is a base-type of 'type'. Throws an error if 'this' isn't a Type. * @param {Type} type * @returns {boolean} */ isBaseOf(type) { if (type instanceof TypeClassImpl) { // we cans simply use name because name-shadowing isn't allowed return type.name == this.name } else { return false } } /** * @returns {string} */ toString() { return this.name } /** * @returns {Typed} */ toTyped() { return new TypedEntity(this) } } /** * @implements {DataType} */ export class DataTypeClassImpl extends TypeClassImpl { /** * @private * @readonly * @type {string} */ _path /** * @param {TypeClass} typeClass * @param {string} name * @param {string} path * @param {null | ParameterI} parameter */ constructor(typeClass, name, path, parameter) { super(typeClass, name, parameter) this._path = path } /** * @type {DataType} */ get asDataType() { return this } /** * @type {Named} */ get asNamed() { return this } /** * @type {string[]} */ get fieldNames() { return [] } /** * @type {string} */ get path() { return this._path } /** * @param {Set<string>} parents * @returns {TypeSchema} */ toSchema(parents = new Set()) { return { kind: "internal", name: "Data" } } /** * @returns {Typed} */ toTyped() { return new DataEntity(this) } } /** * @implements {TypeClass} */ export class AnyTypeClass extends Common { constructor() { super() } /** * @type {TypeClass} */ get asTypeClass() { return this } /** * @param {Type} impl * @returns {TypeClassMembers} */ genInstanceMembers(impl) { return {} } /** * @param {Type} impl * @returns {TypeClassMembers} */ genTypeMembers(impl) { return {} } /** * @param {Type} type * @returns {boolean} */ isImplementedBy(type) { return true } /** * @returns {string} */ toString() { return "Any" } /** * @param {string} name * @param {string} path * @param {null | ParameterI} parameter * @returns {Type} */ toType(name, path, parameter = null) { return new TypeClassImpl(this, name, parameter) } } /** * @implements {TypeClass} */ export class DefaultTypeClass extends Common { constructor() { super() } /** * @type {TypeClass} */ get asTypeClass() { return this } /** * @param {Type} impl * @returns {TypeClassMembers} */ genTypeMembers(impl) { return { __eq: new FuncType([impl, impl], BoolType), __neq: new FuncType([impl, impl], BoolType), __to_data: new FuncType([impl], RawDataType), from_data: new FuncType([RawDataType], impl) } } /** * @param {Type} impl * @returns {TypeClassMembers} */ genInstanceMembers(impl) { return { serialize: new FuncType([], ByteArrayType) } } /** * @param {Type} type * @returns {boolean} */ isImplementedBy(type) { return Common.typeImplements(type, this) } /** * @returns {string} */ toString() { return "" } /** * @param {string} name * @param {string} path * @param {null | ParameterI} parameter * @returns {DataType} */ toType(name, path, parameter = null) { return new DataTypeClassImpl(this, name, path, parameter) } } /** * @implements {TypeClass} */ export class SummableTypeClass extends Common { constructor() { super() } /** * @type {TypeClass} */ get asTypeClass() { return this } /** * @param {Type} impl * @returns {TypeClassMembers} */ genTypeMembers(impl) { return { __add: new FuncType([impl, impl], impl), __sub: new FuncType([impl, impl], impl) } } /** * @param {Type} impl * @returns {TypeClassMembers} */ genInstanceMembers(impl) { return {} } /** * @param {Type} type * @returns {boolean} */ isImplementedBy(type) { return Common.typeImplements(type, this) } /** * @returns {string} */ toString() { return "Summable" } /** * @param {string} name * @param {string} path * @param {null | ParameterI} parameter * @returns {DataType} */ toType(name, path, parameter = null) { return new DataTypeClassImpl(this, name, path, parameter) } } /** * @implements {DataType} */ class AppliedType extends Common { /** * @private * @readonly * @type {Type[]} */ _types /** * @private * @readonly * @type {(types: Type[]) => DataType} */ _apply /** * @private * @readonly * @type {DataType} */ _inner /** * @param {Type[]} types * @param {(types: Type[]) => DataType} apply * @param {DataType} inner */ constructor(types, apply, inner) { super() this._types = types this._apply = apply this._inner = inner } /** * @type {string[]} */ get fieldNames() { return this._inner.fieldNames } /** * @type {InstanceMembers} */ get instanceMembers() { return this._inner.instanceMembers } /** * @type {string} */ get name() { return this._inner.name } /** * @type {string} */ get path() { return this._inner.path } /** * @type {TypeMembers} */ get typeMembers() { return this._inner.typeMembers } /** * @param {Set<string>} parents * @returns {TypeSchema} */ toSchema(parents = new Set()) { return this._inner.toSchema(parents) } /** * @type {DataType} */ get asDataType() { return this } /** * @type {Named} */ get asNamed() { return this } /** * @type {Type} */ get asType() { return this } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { if (!type) { const infered = this._types.map((t) => t.infer(ctx, site, map, null) ) return new AppliedType(infered, this._apply, this._apply(infered)) } else if ( type instanceof AppliedType && type._types.length == this._types.length ) { const infered = this._types.map((t, i) => t.infer(ctx, site, map, type._types[i]) ) const res = new AppliedType( infered, this._apply, this._apply(infered) ) if (!res.isBaseOf(type)) { ctx.errors.type(site, "unable to infer type") } return res } else { ctx.errors.type(site, "unable to infer type") return new AllType() } } /** * @param {Type} other * @returns {boolean} */ isBaseOf(other) { return this._inner.isBaseOf(other) } /** * @returns {string} */ toString() { return this._inner.toString() } /** * @returns {Typed} */ toTyped() { return new DataEntity(this) } } /** * @implements {Parametric} */ export class ParametricType extends Common { /** * @private * @readonly * @type {string} */ _name /** * @private * @readonly * @type {Parameter[]} */ _parameters /** * @private * @readonly * @type {(types: Type[]) => DataType} */ _apply /** * @param {{ * name: string, * parameters: Parameter[] * apply: (types: Type[]) => DataType * }} props */ constructor({ name, parameters, apply }) { super() this._name = name this._parameters = parameters this._apply = apply } /** * @type {Parametric} */ get asParametric() { return this } /** * @type {TypeClass[]} */ get typeClasses() { return this._parameters.map((p) => p.typeClass) } /** * @param {TypeCheckContext} ctx * @param {Type[]} types * @param {Site} site * @returns {EvalEntity} */ apply(ctx, types, site = makeDummySite()) { if (types.length != this._parameters.length) { ctx.errors.type( site, `expected ${this._parameters.length} type parameter(s), got ${types.length}` ) return new AllType() } this._parameters.forEach((p, i) => { if (!p.typeClass.isImplementedBy(types[i])) { ctx.errors.type( site, `${types[i].toString()} doesn't implement ${p.typeClass.toString()}` ) } }) // TODO: recursive problem, defer the implementation check return new AppliedType(types, this._apply, this._apply(types)) } /** * Must infer before calling * @param {TypeCheckContext} ctx * @param {Site} site * @param {Typed[]} args * @param {{[name: string]: Typed}} namedArgs * @param {Type[]} paramTypes - so that paramTypes can be accessed by caller * @returns {Func | undefined} */ inferCall(ctx, site, args, namedArgs = {}, paramTypes = []) { ctx.errors.type(site, "not a parametric function") return undefined } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @returns {Parametric | undefined} */ infer(ctx, site, map) { ctx.errors.type(site, "not a parametric function") return undefined } /** * @returns {string} */ toString() { return `${this._name}` //[${this._parameters.map(p => p.toString())}]`; } }