Skip to content

Core.kt

src/jvmMain/kotlin/afield/lang/fieldml/core/Core.kt · 213 lines

package afield.lang.fieldml.core

import afield.lang.fieldml.ast.BinaryOp
import afield.lang.fieldml.ast.TypeDecl
import afield.lang.fieldml.type.Type

The tree FieldML actually evaluates: the typed tree with its source positions dropped and its multi-parameter lambdas curried into one-parameter CLam nodes.

Lowerer is the only thing that builds one and Evaluator the only thing that walks one. Every case here corresponds to a case of the typed tree, and the lowering is a case-for-case mapping apart from the lambda, so the two hierarchies are easy to read side by side.

What the lowering leaves behind is as informative as what it keeps. Types stay on every node, so a later pass can still ask what a subexpression is. Ranges do not, so an evaluation failure carries no position of its own, and a box reports it against the fragment that was run rather than the subexpression that failed. CLet keeps the recursive flag but has no parameter list, because by this point a let f x = ...'s parameters are the lambda sitting in its value. CoreConstructor keeps a constructor's name and arity and drops its type scheme, which has already done its work in inference.

sealed interface CoreExpr

A node of the lowered tree, carrying the type inferred for it.

val type: Type

data class CInt(val value: Long, override val type: Type) : CoreExpr

An integer literal, from TExprInt.

data class CFloat(val value: Double, override val type: Type) : CoreExpr

A floating-point literal, from TExprFloat.

data class CBool(val value: Boolean, override val type: Type) : CoreExpr

A boolean literal, from TExprBool.

data class CString(val value: String, override val type: Type) : CoreExpr

A string literal, from TExprString.

data class CVar(val name: String, override val type: Type) : CoreExpr

A name to look up in the environment, from TExprVar.

data class CTuple(val elements: List<CoreExpr>, override val type: Type) : CoreExpr

A tuple, from TExprTuple.

data class CList(val elements: List<CoreExpr>, override val type: Type) : CoreExpr

A list, from TExprList.

data class CTypeDecl

A data type declaration and the program that follows it, from TExprTypeDecl.

The declaration arrives here as the TypeDecl the parser built, unchanged by either later stage.

data class CTypeDecl(
    val declaration: TypeDecl,
    val constructors: List<CoreConstructor>,
    val body: CoreExpr,
    override val type: Type,
) : CoreExpr

data class CoreConstructor

A constructor as the evaluator needs it: its name, and how many arguments it takes before it is a value.

This is what is left of TypedConstructor once the lowering drops its type scheme, which has already done its work in inference.

data class CoreConstructor(
    val name: String,
    val arity: Int,
)

data class CLet

A binding and the program that follows it, from TExprLet.

There is no parameter list here: by this point a let f x = ...'s parameters are the CLam nodes sitting in its value.

data class CLet(
    val name: String,
    val value: CoreExpr,
    val body: CoreExpr,
    override val type: Type,
    /** See [afield.lang.fieldml.type.TExprLet.recursive]. */
    val recursive: Boolean = false,
) : CoreExpr

data class CLam

A lambda of exactly one parameter, so the evaluator only ever binds one at a time.

This is the one node the lowering builds rather than renames: a TExprLambda of several parameters becomes that many nested CLams. Each carries its own type, and they differ — the outer node's is the whole arrow, the inner node's is the arrow that starts at the next parameter. parameterType is the type of the one parameter this node binds.

data class CLam(
    val parameter: String,
    val parameterType: Type,
    val body: CoreExpr,
    override val type: Type,
) : CoreExpr

data class CApp

One argument applied to one function, from TExprCall.

data class CApp(
    val function: CoreExpr,
    val argument: CoreExpr,
    override val type: Type,
) : CoreExpr

data class CIf

A conditional, from TExprIf. Both branches are present and have the same type.

data class CIf(
    val condition: CoreExpr,
    val thenBranch: CoreExpr,
    val elseBranch: CoreExpr,
    override val type: Type,
) : CoreExpr

sealed interface CorePattern

A pattern in the lowered tree, carrying the type it matches against.

Patterns are already checked by the time they get here, so matching one at run time is a question about the value's shape rather than about its type.

val type: Type

data class CPWildcard(override val type: Type) : CorePattern

Matches anything and binds nothing, from TPWildcard.

data class CPVar(val name: String, override val type: Type) : CorePattern

Matches anything and binds it under name, from TPVar.

data class CPInt(val value: Long, override val type: Type) : CorePattern

Matches one integer value, from TPInt.

data class CPBool(val value: Boolean, override val type: Type) : CorePattern

Matches one boolean value, from TPBool.

data class CPString(val value: String, override val type: Type) : CorePattern

Matches one string value, from TPString.

data class CPTuple(val elements: List<CorePattern>, override val type: Type) : CorePattern

Matches a tuple element by element, from TPTuple.

data class CPList(val elements: List<CorePattern>, override val type: Type) : CorePattern

Matches a list of exactly this length, from TPList.

data class CPConstructor

Matches one constructor and its arguments, from TPConstructor.

data class CPConstructor(
    val name: String,
    val arguments: List<CorePattern>,
    override val type: Type,
) : CorePattern

data class CPCons

Matches a non-empty list, splitting it into head and tail, from TPCons.

data class CPCons(
    val head: CorePattern,
    val tail: CorePattern,
    override val type: Type,
) : CorePattern

data class CMatchCase

One arm of a CMatch: a pattern, and the expression to evaluate when it matches.

data class CMatchCase(
    val pattern: CorePattern,
    val body: CoreExpr,
)

data class CMatch

A match over its arms, from TExprMatch.

The arms stay in the order they were written, which is the order the evaluator tries them in: the first pattern that matches wins. Nothing checks that they cover every case, so a value that matches no arm is a run-time failure rather than a compile error.

data class CMatch(
    val scrutinee: CoreExpr,
    val cases: List<CMatchCase>,
    override val type: Type,
) : CoreExpr

data class CRecord

A record literal, from TExprRecord, as name-and-value pairs in their written order.

data class CRecord(
    val fields: List<Pair<String, CoreExpr>>,
    override val type: Type,
) : CoreExpr

data class CField

Reads one field off a record or a host value, from TExprField.

data class CField(
    val target: CoreExpr,
    val field: String,
    override val type: Type,
) : CoreExpr

data class CBinary

An infix operator over two operands, from TExprBinary.

AND and OR short-circuit: the right operand is evaluated only when it can change the answer.

data class CBinary(
    val op: BinaryOp,
    val left: CoreExpr,
    val right: CoreExpr,
    override val type: Type,
) : CoreExpr

data class CNegate

Negation, from TExprNegate, over an integer, a float, or each component of a vector.

data class CNegate(
    val value: CoreExpr,
    override val type: Type,
) : CoreExpr