Reference

std/expression/lib

std/expression/src/lib.trb

Quoted expressions: Expression, the typed tree a quoted parameter hands over, and ExpressionNode, the plain data anyone can build, match and transform. assert and nameOf are what most callers reach for; a provider that renders its own explanation of a captured expression works with ExpressionNode directly.

type Expression

native type Expression<Value>

The typed tree behind a quoted parameter: Expression<Value> type checks its argument as an ordinary Value and hands it over together with the expression tree that produced it.

A caller writes nothing special for this - passing an ordinary Value where a parameter is declared Expression<Value> is enough. Only the compiler creates one; ExpressionNode is the data an ordinary function can build as well.

Related

  • assert - the one function most callers need an Expression<Bool> for.

field tree

tree: ExpressionNode

Static data, created at compile time.

field source

source: String

The source text of the quoted expression: "_.age >= minAge"

field location

location: SourceLocation

Where the quoted expression starts in its file.

fn value

native fn value(): Value

The ordinary value (for function types: the closure). Evaluated at most once.

fn captures

native fn captures(): List<EncodedValue>

The values of the captured variables, in the order of their Captured.index.

type SourceLocation

type SourceLocation with Show

A place in a source file: what Expression.location and a diagnostic point at.

field file

file: String

The path of the file.

field line

line: Int

The line the location points at.

field column

column: Int

The column the location points at.

fn show

fn show(): String

"{file}:{line}:{column}".

type TypeReference

type TypeReference with Show

A description of a type. Data, not reflection: there is no way back from a TypeReference to a type.

field name

name: String

The type's name, without its arguments.

field arguments

arguments: List<TypeReference> = []

The type arguments, empty for a type with none.

fn show

fn show(): String

name, or name<arguments> when there are any.

type UnaryOperator

type UnaryOperator

The prefix operators a quoted expression can carry, one case per operator.

case Negate

case Negate

-.

case Not

case Not

!.

case BitwiseNot

case BitwiseNot

~.

type BinaryOperator

type BinaryOperator

The infix operators a quoted expression can carry, one case per operator.

case Add

case Add

+.

case Subtract

case Subtract

-.

case Multiply

case Multiply

*.

case Divide

case Divide

/.

case Remainder

case Remainder

%.

case Power

case Power

**.

case BitwiseAnd

case BitwiseAnd

&.

case BitwiseOr

case BitwiseOr

|.

case BitwiseExclusiveOr

case BitwiseExclusiveOr

^.

case ShiftLeft

case ShiftLeft

<<.

case ShiftRight

case ShiftRight

>>.

case Equal

case Equal

==.

case NotEqual

case NotEqual

!=.

case Less

case Less

<.

case LessOrEqual

case LessOrEqual

<=.

case Greater

case Greater

>.

case GreaterOrEqual

case GreaterOrEqual

>=.

case And

case And

&&.

case Or

case Or

||.

type ExpressionNode

type ExpressionNode

Names are already resolved and everything is typed: implicit _, named closure parameters, receivers and implicit self show up as explicit Parameter, Field and Call nodes.

An ordinary ADT. New node kinds come with new versions of the language; providers end their match with _ => Fail(Unsupported(...)) anyway, because they need that arm for calls they do not know.

case Literal

case Literal(value: EncodedValue, of: TypeReference)

A literal value written in the source.

case Parameter

case Parameter(index: Int, name: String, of: TypeReference)

An explicit parameter of the quoted closure, or its implicit _.

case Captured

case Captured(index: Int, name: String, of: TypeReference)

A binding from outside the quoted expression, captured by value.

case Field

case Field(target: ExpressionNode, name: String, of: TypeReference)

A field read off target, or a receiver method turned into a field access.

case Call

case Call(target: ExpressionNode?, owner: TypeReference, method: String, arguments: List<ExpressionNode>, of: TypeReference)

A method call on target, or a free function call when target is None.

case Construct

case Construct(arguments: List<ExpressionNode>, of: TypeReference)

A constructor call for the type of.

case Unary

case Unary(operator: UnaryOperator, operand: ExpressionNode, of: TypeReference)

A prefix operator applied to operand.

case Binary

case Binary(operator: BinaryOperator, left: ExpressionNode, right: ExpressionNode, of: TypeReference)

An infix operator applied to left and right.

case Conditional

case Conditional(condition: ExpressionNode, then: ExpressionNode, otherwise: ExpressionNode, of: TypeReference)

An if/else expression.

case Lambda

case Lambda(parameters: List<String>, body: ExpressionNode, of: TypeReference)

A closure literal, with its parameter names and its body.

case Items

case Items(items: List<ExpressionNode>, of: TypeReference)

A list or tuple literal.

case Interpolation

case Interpolation(parts: List<ExpressionNode>)

"{a} and {b}": literals and expressions in order. ?. and ?? have no nodes, they are calls on Option.

fn capturedNodes

fn capturedNodes(): List<ExpressionNode>

All Captured nodes below this node, e.g. to explain a failed assertion.

fn nameOf

fn nameOf<Value>(expression: Expression<Value>): String

The name of what was written, never its value: nameOf(user.email) is "email", nameOf(limit) is "limit". For a type there is the compile-time function typeName<User>() instead.

Examples

fn label(value: Expression<Int>): String {
  nameOf value
}

const limit = 5
print label(limit)

fn assert

fn assert(condition: Expression<Bool>)

Panics if condition is false. There are no matchers: the message names the source text of the condition and every value the condition read from around it, one per line, and then the site:

panic: Assertion failed: count < limit
  count = the Int 7
  limit = the Int 5
  at acme/app/src/main.trb:12:1

A scalar - every integer type, every float, Bool, Char and String - is shown as the <kind> <value>, where the word is the language's name for the kind of value and not the name of its type, so Int for an Int64 and Float for a Float64. Everything else is shown by its name and its type, found: Point: a value of the program shown in full needs the whole Show machinery of its type and of every type under it in the binary, and one type that has none would refuse the whole build. Until a capture carries its own encoded value, that is the one thing the two implementations word differently - the interpreter writes a value of type List where a compiled binary writes the name and the type.

Examples

const limit = 5
const count = 7
assert(count < limit)

Panics

When condition is false.