numeric_expr API
numeric_expr 是一种面向数值测试语料和工具的小型表达式语言。Expr 是一棵树,其叶子是字面量(原始源文本),内部节点将具名操作应用于参数表达式。该包不了解任何数值类型:evaluate 借助调用者提供的两个回调折叠这棵树,一个解码字面量,一个执行操作。每个节点都携带一个 SourceSpan,以便在其源代码行处报告失败。教程逐步构造并求值表达式,设计页给出求值语义并证明其不变量。
在 moon.pkg 中导入该包:
import {
"Luna-Flow/floating/numeric_expr",
}
源位置
SourceSpan
SourceSpan 记录语法节点的来源:源名称(通常是文件路径)、行号和列号。
pub struct SourceSpan {
// private fields
} derive(Eq, @debug.Debug)
这些字段是私有且不可变的。当源、行号和列号都相等时,两个区段相等。
SourceSpan::new
SourceSpan::new(source, line?, column?) 创建一个区段。
pub fn SourceSpan::new(String, line? : Int, column? : Int) -> Self
line 和 column 默认为 0,表示“未知”。值按原样存储,不做范围检查。
SourceSpan::source, SourceSpan::line, SourceSpan::column
这些访问器返回区段的三个分量。
pub fn SourceSpan::source(Self) -> String
pub fn SourceSpan::line(Self) -> Int
pub fn SourceSpan::column(Self) -> Int
///|
test "source span accessors" {
let span = @numeric_expr.SourceSpan::new("add.decTest", line=12, column=1)
inspect(span.source(), content="add.decTest")
inspect(span.line(), content="12")
let unknown = @numeric_expr.SourceSpan::new("")
inspect(unknown.line(), content="0")
inspect(unknown.column(), content="0")
}
语法节点
Literal
Literal 是表达式的叶子:操作数的原始文本(例如 "1.20"、"-Inf" 或 "#7C00016E")及其区段。
pub struct Literal {
// private fields
} derive(Eq, @debug.Debug)
构造字面量时不会解析文本。其含义由 evaluate 的 decode 回调决定,因此同一棵树可以被读作二进制值、十进制值或区间值。
Literal::new, Literal::raw, Literal::span
Literal::new(raw, span?) 构造一个字面量;raw 和 span 将其读回。
pub fn Literal::new(String, span? : SourceSpan) -> Self
pub fn Literal::raw(Self) -> String
pub fn Literal::span(Self) -> SourceSpan
默认区段为 SourceSpan::new("")。
Operation
Operation 命名内部节点的操作(例如 "add" 或 "squareroot"),并带有其区段。
pub struct Operation {
// private fields
} derive(Eq, @debug.Debug)
该包不赋予名称任何含义,也不记录元数。由 evaluate 的 invoke 回调决定存在哪些名称以及每个名称接受多少个参数。
Operation::new, Operation::name, Operation::span
Operation::new(name, span?) 构造一个操作;name 和 span 将其读回。
pub fn Operation::new(String, span? : SourceSpan) -> Self
pub fn Operation::name(Self) -> String
pub fn Operation::span(Self) -> SourceSpan
默认区段为 SourceSpan::new("")。
表达式
Expr
Expr 是不可变的表达式树。
pub struct Expr {
// private fields
}
其表示是私有的。只有下面两个构造器能创建表达式,因此每个 Expr 都具有如下形状
Expr 没有相等性、Debug 或 Show 实现;要检查一个表达式,请对其求值。
Expr::literal
Expr::literal(literal) 是对应单个字面量的叶子表达式。
pub fn Expr::literal(Literal) -> Self
Expr::invoke
Expr::invoke(operation, arguments) 将 operation 按顺序应用于各参数表达式。
pub fn Expr::invoke(Operation, Array[Self]) -> Self
数组会被复制到树中,因此之后对 arguments 的修改不会改变该表达式。允许空数组,此时得到零元操作。
///|
test "build a nested expression" {
// (2 + 3) * 4
let sum = @numeric_expr.Expr::invoke(@numeric_expr.Operation::new("add"), [
@numeric_expr.Expr::literal(@numeric_expr.Literal::new("2")),
@numeric_expr.Expr::literal(@numeric_expr.Literal::new("3")),
])
let product = @numeric_expr.Expr::invoke(
@numeric_expr.Operation::new("mul"),
[sum, @numeric_expr.Expr::literal(@numeric_expr.Literal::new("4"))],
)
let result : Result[Int, @numeric_expr.EvalError[String]] = @numeric_expr.evaluate(
product,
literal => {
match literal.raw() {
"2" => Ok(2)
"3" => Ok(3)
"4" => Ok(4)
_ => Err("bad literal")
}
},
(operation, arguments) => {
match (operation.name(), arguments) {
("add", [a, b]) => Ok(a + b)
("mul", [a, b]) => Ok(a * b)
_ => Err("unknown operation")
}
},
)
inspect(result is Ok(20), content="true")
}
求值
evaluate
evaluate(expression, decode, invoke) 借助这两个回调自底向上计算 expression 的值。
pub fn[V, E] evaluate(Expr, (Literal) -> Result[V, E], (Operation, Array[V]) -> Result[V, E]) -> Result[V, EvalError[E]]
V 是回调的值类型,E 是其错误类型。规则如下:
- 字面量 求值为
decode(ℓ);Err(e)变为Err(LiteralFailure(ℓ, e)); - 调用 先从左到右求值 。第一个失败的参数会终止求值,其错误原样返回。当所有参数都成功并得到值 时,结果为
invoke(o, [v1, ..., vn]),Err(e)变为Err(OperationFailure(o, e))。
因此,回调按后序调用(先从左到右处理子节点,再处理父节点),每个节点至多一次,且求值在第一次失败时停止。成功时,decode 对每个字面量叶子运行一次,invoke 对每个调用节点运行一次。该包不产生任何其他副作用。递归深度等于树的高度。设计页证明了这些性质。
evaluate 自身从不中止;任何中止都来自回调。
EvalError
EvalError[E] 指出哪个节点失败,并携带回调的错误。
pub(all) enum EvalError[E] {
LiteralFailure(Literal, E)
OperationFailure(Operation, E)
UnsupportedExpression(SourceSpan)
}
LiteralFailure(literal, error):decode(literal)返回了Err(error)。literal.span()定位该操作数。OperationFailure(operation, error):在operation的所有参数都成功求值之后,invoke对它返回了Err(error)。UnsupportedExpression(span):树中包含求值器不处理的形式。用Expr::literal和Expr::invoke构造的树永远不会产生它;该构造器是为私有表示能够容纳、但公共构造器尚无法构造的形式(例如变量和绑定子)预留的。
///|
test "evaluation reports the failing node" {
let span = @numeric_expr.SourceSpan::new("row.txt", line=3, column=7)
let expression = @numeric_expr.Expr::invoke(
@numeric_expr.Operation::new("div", span~),
[
@numeric_expr.Expr::literal(@numeric_expr.Literal::new("1", span~)),
@numeric_expr.Expr::literal(@numeric_expr.Literal::new("0", span~)),
],
)
let result : Result[Int, @numeric_expr.EvalError[String]] = @numeric_expr.evaluate(
expression,
literal => if literal.raw() == "1" { Ok(1) } else { Ok(0) },
(_, arguments) => {
if arguments[1] == 0 {
Err("division by zero")
} else {
Ok(arguments[0] / arguments[1])
}
},
)
match result {
Err(@numeric_expr.OperationFailure(operation, message)) => {
inspect(operation.name(), content="div")
inspect(operation.span().line(), content="3")
inspect(message, content="division by zero")
}
_ => fail("expected an operation failure")
}
}
trait 实现
Literal::equal、Operation::equal、SourceSpan::equal 与 not_equal
这些方法比较所有存储的分量(文本或名称,以及区段)。新代码请使用 == 和 !=。
pub fn Literal::equal(Self, Self) -> Bool
pub fn Literal::not_equal(Self, Self) -> Bool
pub fn Operation::equal(Self, Self) -> Bool
pub fn Operation::not_equal(Self, Self) -> Bool
pub fn SourceSpan::equal(Self, Self) -> Bool
pub fn SourceSpan::not_equal(Self, Self) -> Bool
文本相同但区段不同的两个字面量是不同的。
Literal::to_repr, Operation::to_repr, SourceSpan::to_repr
这些方法为 debug_inspect 及其他 Debug 使用者渲染值。
pub fn Literal::to_repr(Self) -> @debug.Repr
pub fn Operation::to_repr(Self) -> @debug.Repr
pub fn SourceSpan::to_repr(Self) -> @debug.Repr
///|
test "syntax nodes compare by text and span" {
let a = @numeric_expr.Literal::new("1.0")
let b = @numeric_expr.Literal::new(
"1.0",
span=@numeric_expr.SourceSpan::new("x", line=1),
)
inspect(a == @numeric_expr.Literal::new("1.0"), content="true")
inspect(a != b, content="true")
}
完整公共接口
此快照是该包生成的 pkg.generated.mbti。当正文与接口不一致时,以接口为准。
// Generated using `moon info`, DON'T EDIT IT
package "Luna-Flow/floating/numeric_expr"
import {
"moonbitlang/core/debug",
}
// Values
pub fn[V, E] evaluate(Expr, (Literal) -> Result[V, E], (Operation, Array[V]) -> Result[V, E]) -> Result[V, EvalError[E]]
// Errors
// Types and methods
pub(all) enum EvalError[E] {
LiteralFailure(Literal, E)
OperationFailure(Operation, E)
UnsupportedExpression(SourceSpan)
}
pub struct Expr {
// private fields
}
pub fn Expr::invoke(Operation, Array[Self]) -> Self
pub fn Expr::literal(Literal) -> Self
pub struct Literal {
// private fields
} derive(Eq, @debug.Debug)
pub fn Literal::equal(Self, Self) -> Bool
pub fn Literal::new(String, span? : SourceSpan) -> Self
pub fn Literal::not_equal(Self, Self) -> Bool
pub fn Literal::raw(Self) -> String
pub fn Literal::span(Self) -> SourceSpan
pub fn Literal::to_repr(Self) -> @debug.Repr
pub struct Operation {
// private fields
} derive(Eq, @debug.Debug)
pub fn Operation::equal(Self, Self) -> Bool
pub fn Operation::name(Self) -> String
pub fn Operation::new(String, span? : SourceSpan) -> Self
pub fn Operation::not_equal(Self, Self) -> Bool
pub fn Operation::span(Self) -> SourceSpan
pub fn Operation::to_repr(Self) -> @debug.Repr
pub struct SourceSpan {
// private fields
} derive(Eq, @debug.Debug)
pub fn SourceSpan::column(Self) -> Int
pub fn SourceSpan::equal(Self, Self) -> Bool
pub fn SourceSpan::line(Self) -> Int
pub fn SourceSpan::new(String, line? : Int, column? : Int) -> Self
pub fn SourceSpan::not_equal(Self, Self) -> Bool
pub fn SourceSpan::source(Self) -> String
pub fn SourceSpan::to_repr(Self) -> @debug.Repr
// Type aliases
// Traits