numeric_expr API
numeric_expr は数値テストコーパスとツールのための小さな式言語です。Expr は木であり、葉はリテラル(生のソーステキスト)、内部ノードは名前付き演算を引数式に適用します。このパッケージは数値型を一切知りません。evaluate は呼び出し側が与える 2 つのコールバック(リテラルを復号するものと演算を実行するもの)で木を畳み込みます。各ノードは SourceSpan を持つため、失敗をソースの行で報告できます。チュートリアルでは式を段階的に構築・評価し、設計ページでは評価の意味論を述べ、その不変条件を証明します。
moon.pkg でパッケージをインポートします:
import {
"Luna-Flow/floating/numeric_expr",
}
ソース位置
SourceSpan
SourceSpan は構文ノードの出所を記録します。ソース名(通常はファイルパス)、行、列です。
pub struct SourceSpan {
// private fields
} derive(Eq, @debug.Debug)
フィールドは非公開かつ不変です。2 つのスパンは、ソース、行、列がすべて等しいときに等しくなります。
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
これらのアクセサはスパンの 3 つの構成要素を返します。
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 コールバックが決めるため、同じ木を 2 進値、10 進値、区間値のいずれとしても読めます。
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
}
表現は非公開です。式を作成できるのは以下の 2 つのコンストラクタだけなので、すべての Expr は次の形をしています
Expr には等価性、Debug、Show の実装がありません。式を調べるには評価してください。
Expr::literal
Expr::literal(literal) は 1 つのリテラルからなる葉の式です。
pub fn Expr::literal(Literal) -> Self
Expr::invoke
Expr::invoke(operation, arguments) は operation を引数式に順に適用します。
pub fn Expr::invoke(Operation, Array[Self]) -> Self
配列は木にコピーされるため、後で arguments を変更しても式は変わりません。空配列も許され、0 項演算になります。
///|
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) は 2 つのコールバックを用いて 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))になる。
したがってコールバックは後行順(子を左から右へ、次に親)で、各ノードにつき高々 1 回呼ばれ、評価は最初の失敗で停止します。成功時には decode はリテラルの葉ごとに 1 回、invoke は呼び出しノードごとに 1 回実行されます。パッケージはそれ以外の作用を一切行いません。再帰の深さは木の高さに等しくなります。これらの性質は設計ページで証明されています。
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):すべての引数の評価が成功した後、invokeがoperationに対して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")
}
}
トレイト実装
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
テキストが同じでもスパンが異なる 2 つのリテラルは異なります。
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