@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
242 lines (207 loc) • 5.63 kB
JavaScript
import { makeTypeError, makeWord } from "@helios-lang/compiler-utils"
import { $ } from "@helios-lang/ir"
import { expectDefined } from "@helios-lang/type-utils"
import { TAB, ToIRContext } from "../codegen/index.js"
import { Scope } from "../scopes/index.js"
import { Expr } from "./Expr.js"
import { AllType, AnyType, DataEntity } from "../typecheck/common.js"
/**
* @import { SymbolToken, Word } from "@helios-lang/compiler-utils"
* @import { SourceMappedStringI } from "@helios-lang/ir"
* @import { TypeCheckContext } from "../index.js"
* @typedef {import("../typecheck/index.js").EvalEntity} EvalEntity
*/
/**
* @type {{[name: string]: string}}
*/
export const BINARY_SYMBOLS_MAP = {
"||": "__or",
"&&": "__and",
"==": "__eq",
"!=": "__neq",
"<": "__lt",
"<=": "__leq",
">": "__gt",
">=": "__geq",
"+": "__add",
"-": "__sub",
"*": "__mul",
"/": "__div",
"%": "__mod"
}
/**
* Binary operator expression
*/
export class BinaryExpr extends Expr {
/**
* @private
* @readonly
* @typedef {SymbolToken}
*/
_op
/**
* @private
* @readonly
* @typedef {Expr}
*/
_a
/**
* @private
* @readonly
* @typedef {Expr}
*/
_b
/**
* @private
* @type {boolean}
*/
_swap // swap a and b for commutative ops
/**
* @private
* @type {number}
*/
_alt // use alt (each operator can have one overload)
/**
* @param {SymbolToken} op
* @param {Expr} a
* @param {Expr} b
*/
constructor(op, a, b) {
super(op.site)
this._op = op
this._a = a
this._b = b
this._swap = false
this._alt = 0
}
/**
* @type {Expr}
*/
get first() {
return this._swap ? this._b : this._a
}
/**
* @type {Expr}
*/
get second() {
return this._swap ? this._a : this._b
}
/**
* @returns {string}
*/
toString() {
return `${this._a.toString()} ${this._op.toString()} ${this._b.toString()}`
}
/**
* Turns op symbol into internal name
* @param {number} alt
* @returns {Word}
*/
translateOp(alt = 0) {
const op = this._op.toString()
const site = this._op.site
let name = BINARY_SYMBOLS_MAP[op]
if (!name) {
throw new Error("unhandled")
}
if (alt > 0) {
name += alt.toString()
}
return makeWord({ value: name, site })
}
/**
* @returns {boolean}
*/
isCommutative() {
switch (this._op.toString()) {
case "+":
case "*":
case "==":
case "!=":
return true
default:
return false
}
}
/**
* @param {TypeCheckContext} ctx
* @param {Scope} scope
* @returns {EvalEntity}
*/
evalInternal(ctx, scope) {
const a_ = this._a.eval(ctx, scope)
const b_ = this._b.eval(ctx, scope)
const a = a_.asInstance
if (!a) {
ctx.errors.type(
this._a.site,
`lhs of ${this._op.toString()} not an instance`
)
return new DataEntity(new AllType())
}
const b = b_.asInstance
if (!b) {
ctx.errors.type(
this._b.site,
`rhs of ${this._op.toString()} not an instance`
)
return new DataEntity(new AllType())
}
if (a.type instanceof AllType || b.type instanceof AllType) {
return new DataEntity(new AllType())
}
for (let swap of this.isCommutative() ? [false, true] : [false]) {
for (let alt of [0, 1, 2]) {
let first = swap ? b : a
let second = swap ? a : b
const fnVal_ =
first.type.typeMembers[this.translateOp(alt).value]
let fnVal = fnVal_?.asType?.toTyped()?.asFunc
if (!fnVal) {
continue
}
if (
fnVal.funcType.argTypes[0].isBaseOf(first.type) &&
fnVal.funcType.argTypes[1].isBaseOf(second.type)
) {
let res = fnVal.call(ctx, this._op.site, [first, second])
this._swap = swap
this._alt = alt
return res
}
}
}
ctx.errors.type(
this.site,
`'${a.type.toString()} ${this._op.toString()} ${b.type.toString()}' undefined`
)
return new DataEntity(new AnyType())
}
/**
* @param {ToIRContext} ctx
* @returns {SourceMappedStringI}
*/
toIR(ctx) {
let path = expectDefined(this.first.cache?.asTyped?.type.asNamed).path
let op = this.translateOp(this._alt).value
if (op == "__and" || op == "__or") {
return $([
$(`${path}${op}`, this.site),
$(`(\n${ctx.indent}${TAB}() -> {`),
this.first.toIR(ctx.tab()),
$(`},\n${ctx.indent}${TAB}() -> {`),
this.second.toIR(ctx.tab()),
$(`}\n${ctx.indent})`)
])
} else {
return $([
$(`${path}__${op}`, this.site),
$("(", this.site),
this.first.toIR(ctx),
$(", "),
this.second.toIR(ctx),
$(")")
])
}
}
}