frontend/gda_expr API

frontend/gda_expr は General Decimal Arithmetic のテストファイル(Cowlishaw の 10 進仕様の .decTest 形式)を読み込み、その行を decimal_gda に対して実行します。構文解析はすべての行を、そのコンテキストと numeric_expr の式を持つ GdaCase に変換します。実行は式を評価し、結果とステータスフラグを行と比較します。このパッケージはファイルやプロセスの IO を行いません。コマンドラインランナーは cli/gda_expr_cli です。ワークフローはチュートリアルで示し、行の対応付けと合格規則は設計ページで規定します。

moon.pkg でパッケージをインポートします:

import {
  "Luna-Flow/floating/frontend/gda_expr",
}

解析

parse_dectest

parse_dectest(source, text) は .decTest 文書全体を構文解析します。

pub fn parse_dectest(String, String) -> Result[GdaDocument, Array[ParseDiagnostic]]

source はスパンで使われる名前(通常はファイルパス)で、text はファイルの内容です。テキストは \n で分割され、行番号は 1 から始まります。各行は次のように扱われます。

  1. 引用符の外の -- はコメントを開始し、行の残りは捨てられます。先頭と末尾の空白は除去され、空行はスキップされます。
  2. 引用符の外に -> を含まない行はディレクティブ name: value です。名前は -、_、空白、タブを除去したうえで大文字小文字を区別せずに比較されます。precision、rounding、minexponent、maxexponent、clamp、extended、dectest はコンテキストを更新し、version と未知の名前は無視されます。precision、minExponent、maxExponent は整数でなければならず、そうでなければその行は診断になります。clamp は値 1 のときだけ有効になり、extended は値 0 のときだけ無効になります。: を含まない行は診断になります。
  3. -> を含む行はテスト行です。左辺は id operation operand…(2 トークン以上)に、右辺は expected condition…(1 トークン以上)にトークン化されます。トークンは空白、タブ、改行で区切られます。'…' または "…" で囲まれたトークンは空白を含むことができ、引用符は取り除かれます。-> の後の閉じられていない引用符は診断 unterminated quoted token になります。-> の前で開かれた引用符は矢印を隠すため、その行は expected directive or testcase row として報告されます。トークンが必要数に満たない場合は malformed testcase row になります。

診断が生成されなかった場合、結果は Ok(document) です。そうでなければ、文書のすべての診断を行順に含む Err です。

文書と行

GdaDocument

GdaDocument は構文解析済みのファイルで、ソース名とファイル順の行を持ちます。

pub struct GdaDocument {
  // private fields
}

GdaDocument::source, GdaDocument::cases, GdaDocument::case_count

これらのメソッドはソース名、行のコピー、行数を返します。

pub fn GdaDocument::source(Self) -> String
pub fn GdaDocument::cases(Self) -> Array[GdaCase]
pub fn GdaDocument::case_count(Self) -> Int

GdaCase

GdaCase は 1 つのテスト行と、その行の時点で有効なコンテキストの組です。

pub struct GdaCase {
  // private fields
}

GdaCase::id, GdaCase::operation, GdaCase::normalized_operation

これらのメソッドは行 id、記述どおりの演算、およびディスパッチに使われる演算名(小文字にし、-、_、空白、タブを除去したもの)を返します。

pub fn GdaCase::id(Self) -> String
pub fn GdaCase::operation(Self) -> String
pub fn GdaCase::normalized_operation(Self) -> String

GdaCase::operands, GdaCase::expected, GdaCase::conditions

これらのメソッドはオペランドのトークン、期待結果のトークン、条件のトークンを返します。引用符は外されますが、それ以外は記述どおりです。

pub fn GdaCase::operands(Self) -> Array[String]
pub fn GdaCase::expected(Self) -> String
pub fn GdaCase::conditions(Self) -> Array[String]

配列はコピーです。

GdaCase::context, GdaCase::span, GdaCase::expression

これらのメソッドは行のディレクティブコンテキスト、そのスパン(ソース、行、列 1)、および式としての行を返します。

pub fn GdaCase::context(Self) -> GdaContext
pub fn GdaCase::span(Self) -> @numeric_expr.SourceSpan
pub fn GdaCase::expression(Self) -> @numeric_expr.Expr

式は、オペランドごとに 1 つの Expr::literal に適用された Expr::invoke(Operation::new(normalized_operation), …) で、すべて行のスパンを持ちます。

GdaContext

GdaContext は、ある行における文書のディレクティブ状態です。

pub struct GdaContext {
  // private fields
} derive(Eq, @debug.Debug)

これは記述どおりのディレクティブ値のレコードであり、@decimal_gda.GdaContext ではありません。実行時に変換されます。

GdaContext::default

GdaContext::default() は最初のディレクティブより前のコンテキストです。

pub fn GdaContext::default() -> Self

精度 34、丸め "half_even"、min_exponent −999999999-999999999、max_exponent 999999999999999999、clamp 無効、extended 有効、dectest は空。

GdaContext::precision, rounding, min_exponent, max_exponent, clamp, extended, dectest

これらのアクセサはディレクティブの値を返します。

pub fn GdaContext::precision(Self) -> Int
pub fn GdaContext::rounding(Self) -> String
pub fn GdaContext::min_exponent(Self) -> Int
pub fn GdaContext::max_exponent(Self) -> Int
pub fn GdaContext::clamp(Self) -> Bool
pub fn GdaContext::extended(Self) -> Bool
pub fn GdaContext::dectest(Self) -> String

rounding は記述どおりのディレクティブのテキスト(例えば "half_up" や "05up")であり、実行時にのみ解釈されます。

ParseDiagnostic

ParseDiagnostic は位置情報付きの 1 つの構文解析エラーです。

pub struct ParseDiagnostic {
  // private fields
} derive(Eq, @debug.Debug)

ParseDiagnostic::span, ParseDiagnostic::message

これらのメソッドは位置(ソース、行、列 1)とメッセージを返します。メッセージは例えば "unterminated quoted token"、"malformed testcase row"、"expected directive or testcase row"、"invalid precision directive" です。

pub fn ParseDiagnostic::span(Self) -> @numeric_expr.SourceSpan
pub fn ParseDiagnostic::message(Self) -> String
///|
test "parse diagnostics" {
  let text =
    #|precision: nine
    #|t1 add 1 1 -> 2
    #|t2 add 1 1 -> '2
    #|t3 add 1 1
    #|
  match @gda_expr.parse_dectest("bad.decTest", text) {
    Err(diagnostics) => {
      let lines = diagnostics.map(d => {
        d.span().line().to_string() + " " + d.message()
      })
      inspect(
        lines.join("; "),
        content="1 invalid precision directive; 3 unterminated quoted token; 4 expected directive or testcase row",
      )
    }
    Ok(_) => fail("expected diagnostics")
  }
}

実行

execute_documents

execute_documents(documents, options?) は文書の選択された行を順に実行し、結果を要約します。

pub fn execute_documents(Array[GdaDocument], options? : RunOptions) -> RunSummary

行は文書ごとにファイル順に処理されます。id が options.case_filter() に一致しない行は完全に無視されます。残りの行には 0,1,2,…0, 1, 2, \dots と番号が付けられ、シャード数 nn、シャードインデックス ii に対して k mod n=ik \bmod n = i のとき行 kk が実行されます。実行される各行には、まず処置区分が与えられます。

  • オペランドがちょうど # か ? である場合、または期待結果がちょうど # である場合は Diagnostic。
  • 条件が 13 個の GDA 条件のいずれでもない場合、演算が実装されていない場合、または丸めディレクティブが認識されない場合は Unsupported。
  • それ以外は Executable。

評価されるのは Executable の行だけです。結果が期待トークンに一致し、発生した条件が列挙されたものとちょうど一致するとき、その行は合格です。完全な規則は設計ページにあります。実行対象でない行は passed() == false とメッセージ "skipped" を受け取り、失敗ではなくスキップとして数えられます。この関数は行の内容によって異常終了することはありません。

RunOptions

RunOptions は execute_documents が実行する行を選択します。

pub struct RunOptions {
  // private fields
} derive(Eq, @debug.Debug)

RunOptions::new

RunOptions::new(shard_count?, shard_index?, strict_supported?, case_filter?) はオプションを構築します。

pub fn RunOptions::new(shard_count? : Int, shard_index? : Int, strict_supported? : Bool, case_filter? : String) -> Self

デフォルト:シャード 1 つ、インデックス 0、strict_supported 無効、空のフィルタ(全行)。shard_count > 0 かつ 0 <= shard_index < shard_count でなければ異常終了します。

case_filter はカンマ区切りのセレクタのリストで、各セレクタは前後の空白が除去されます。セレクタ id はその id にちょうど一致します。セレクタ first..last は、first および last と同じ長さを持ち、文字列順でその間にあるすべての id に一致します。そのため、add001..add099 は番号付きのブロックを選択します。

strict_supported は呼び出し側のために保持されるもので、execute_documents 自体はこれを読みません。CLI はこれを使って、サポートされない行を失敗の終了コードに変えます。

RunOptions::shard_count, shard_index, strict_supported, case_filter

これらのアクセサはオプションの値を返します。

pub fn RunOptions::shard_count(Self) -> Int
pub fn RunOptions::shard_index(Self) -> Int
pub fn RunOptions::strict_supported(Self) -> Bool
pub fn RunOptions::case_filter(Self) -> String

結果

CaseDisposition

CaseDisposition は行が実行されたかどうか、また実行されなかった場合はその理由を示します。

pub(all) enum CaseDisposition {
  Executable
  Diagnostic(String)
  Legacy(String)
  Unsupported(String)
}

文字列は "diagnostic interchange/non-scalar row" や "unsupported operation frobnicate" のような短い理由です。Legacy は共通の結果モデルのために存在しますが、現在のエグゼキュータがこれを割り当てることはありません。

CaseResult

CaseResult は選択された 1 つの行の結果です。

pub struct CaseResult {
  // private fields
}

CaseResult::id, disposition, passed, message

これらのメソッドは行 id、その処置区分、合格したかどうか、およびメッセージを返します。メッセージは、合格なら空、実行対象でない行なら "skipped"、オペランドをデコードできなかったか演算がオペランドを拒否した場合は "evaluation failed"、それ以外は "result mismatch: expected …, actual …" または "status flags mismatch: expected …, actual …" というテキストです。

pub fn CaseResult::id(Self) -> String
pub fn CaseResult::disposition(Self) -> CaseDisposition
pub fn CaseResult::passed(Self) -> Bool
pub fn CaseResult::message(Self) -> String

RunSummary

RunSummary は 1 回の実行の結果を集計します。

pub struct RunSummary {
  // private fields
}

RunSummary のカウンタ

これらのメソッドは 1 回の実行の件数を返します。

pub fn RunSummary::total_cases(Self) -> Int
pub fn RunSummary::selected_cases(Self) -> Int
pub fn RunSummary::executable_cases(Self) -> Int
pub fn RunSummary::passed_cases(Self) -> Int
pub fn RunSummary::failed_cases(Self) -> Int
pub fn RunSummary::skipped_cases(Self) -> Int
pub fn RunSummary::diagnostic_cases(Self) -> Int
pub fn RunSummary::legacy_cases(Self) -> Int
pub fn RunSummary::unsupported_cases(Self) -> Int

total_cases はフィルタに一致する行(全シャード)を数え、selected_cases はこのシャードで実行された行を数えます。これらは次を満たします。

selected=executable+skipped,executable=passed+failed,skipped=diagnostic+legacy+unsupported.\begin{aligned} \text{selected} &= \text{executable} + \text{skipped}, & \text{executable} &= \text{passed} + \text{failed}, \\ \text{skipped} &= \text{diagnostic} + \text{legacy} + \text{unsupported}. && \end{aligned}

RunSummary::results, RunSummary::success

results は行ごとの結果のコピーを実行順に返します。success は実行対象の行が 1 つも失敗しなかったとき真です。

pub fn RunSummary::results(Self) -> Array[CaseResult]
pub fn RunSummary::success(Self) -> Bool

スキップされた行は success に影響しません。

RunSummary::merge

RunSummary::merge(parts) は 1 回の実行の各シャードの要約を結合します。

pub fn RunSummary::merge(Array[Self]) -> Self

total_cases 以外のすべてのカウンタは加算されます。total_cases は各部分の最大値です(どのシャードも同じ合計を報告します)。結果は parts の順に連結されます。1 回の実行の nn 個のシャードをマージすると、シャード分割しない実行と同じカウンタが得られます。

///|
test "merge shards" {
  let text =
    #|s1 add 1 1 -> 2
    #|s2 add 1 2 -> 3
    #|s3 add 1 3 -> 5
    #|
  let document = @gda_expr.parse_dectest("s.decTest", text).unwrap()
  let parts = [0, 1].map(i => {
    @gda_expr.execute_documents(
      [document],
      options=@gda_expr.RunOptions::new(shard_count=2, shard_index=i),
    )
  })
  let merged = @gda_expr.RunSummary::merge(parts)
  inspect(merged.total_cases(), content="3")
  inspect(merged.passed_cases(), content="2")
  inspect(merged.failed_cases(), content="1")
}

トレイト実装

GdaContext、ParseDiagnostic、RunOptions の等価性と Debug

これらのメソッドはすべてのフィールドを比較し、値を Debug 用に描画します。新しいコードでは ==、!=、debug_inspect を使ってください。

pub fn GdaContext::equal(Self, Self) -> Bool
pub fn GdaContext::not_equal(Self, Self) -> Bool
pub fn GdaContext::to_repr(Self) -> @debug.Repr
pub fn ParseDiagnostic::equal(Self, Self) -> Bool
pub fn ParseDiagnostic::not_equal(Self, Self) -> Bool
pub fn ParseDiagnostic::to_repr(Self) -> @debug.Repr
pub fn RunOptions::equal(Self, Self) -> Bool
pub fn RunOptions::not_equal(Self, Self) -> Bool
pub fn RunOptions::to_repr(Self) -> @debug.Repr

公開インターフェース全体

このスナップショットは、パッケージの生成された pkg.generated.mbti です。説明文とインターフェースが食い違う場合は、こちらが正となります。

// Generated using `moon info`, DON'T EDIT IT
package "Luna-Flow/floating/frontend/gda_expr"

import {
  "Luna-Flow/floating/numeric_expr",
  "moonbitlang/core/debug",
}

// Values
pub fn execute_documents(Array[GdaDocument], options? : RunOptions) -> RunSummary

pub fn parse_dectest(String, String) -> Result[GdaDocument, Array[ParseDiagnostic]]

// Errors

// Types and methods
pub(all) enum CaseDisposition {
  Executable
  Diagnostic(String)
  Legacy(String)
  Unsupported(String)
}

pub struct CaseResult {
  // private fields
}
pub fn CaseResult::disposition(Self) -> CaseDisposition
pub fn CaseResult::id(Self) -> String
pub fn CaseResult::message(Self) -> String
pub fn CaseResult::passed(Self) -> Bool

pub struct GdaCase {
  // private fields
}
pub fn GdaCase::conditions(Self) -> Array[String]
pub fn GdaCase::context(Self) -> GdaContext
pub fn GdaCase::expected(Self) -> String
pub fn GdaCase::expression(Self) -> @numeric_expr.Expr
pub fn GdaCase::id(Self) -> String
pub fn GdaCase::normalized_operation(Self) -> String
pub fn GdaCase::operands(Self) -> Array[String]
pub fn GdaCase::operation(Self) -> String
pub fn GdaCase::span(Self) -> @numeric_expr.SourceSpan

pub struct GdaContext {
  // private fields
} derive(Eq, @debug.Debug)
pub fn GdaContext::clamp(Self) -> Bool
pub fn GdaContext::dectest(Self) -> String
pub fn GdaContext::default() -> Self
pub fn GdaContext::equal(Self, Self) -> Bool
pub fn GdaContext::extended(Self) -> Bool
pub fn GdaContext::max_exponent(Self) -> Int
pub fn GdaContext::min_exponent(Self) -> Int
pub fn GdaContext::not_equal(Self, Self) -> Bool
pub fn GdaContext::precision(Self) -> Int
pub fn GdaContext::rounding(Self) -> String
pub fn GdaContext::to_repr(Self) -> @debug.Repr

pub struct GdaDocument {
  // private fields
}
pub fn GdaDocument::case_count(Self) -> Int
pub fn GdaDocument::cases(Self) -> Array[GdaCase]
pub fn GdaDocument::source(Self) -> String

pub struct ParseDiagnostic {
  // private fields
} derive(Eq, @debug.Debug)
pub fn ParseDiagnostic::equal(Self, Self) -> Bool
pub fn ParseDiagnostic::message(Self) -> String
pub fn ParseDiagnostic::not_equal(Self, Self) -> Bool
pub fn ParseDiagnostic::span(Self) -> @numeric_expr.SourceSpan
pub fn ParseDiagnostic::to_repr(Self) -> @debug.Repr

pub struct RunOptions {
  // private fields
} derive(Eq, @debug.Debug)
pub fn RunOptions::case_filter(Self) -> String
pub fn RunOptions::equal(Self, Self) -> Bool
pub fn RunOptions::new(shard_count? : Int, shard_index? : Int, strict_supported? : Bool, case_filter? : String) -> Self
pub fn RunOptions::not_equal(Self, Self) -> Bool
pub fn RunOptions::shard_count(Self) -> Int
pub fn RunOptions::shard_index(Self) -> Int
pub fn RunOptions::strict_supported(Self) -> Bool
pub fn RunOptions::to_repr(Self) -> @debug.Repr

pub struct RunSummary {
  // private fields
}
pub fn RunSummary::diagnostic_cases(Self) -> Int
pub fn RunSummary::executable_cases(Self) -> Int
pub fn RunSummary::failed_cases(Self) -> Int
pub fn RunSummary::legacy_cases(Self) -> Int
pub fn RunSummary::merge(Array[Self]) -> Self
pub fn RunSummary::passed_cases(Self) -> Int
pub fn RunSummary::results(Self) -> Array[CaseResult]
pub fn RunSummary::selected_cases(Self) -> Int
pub fn RunSummary::skipped_cases(Self) -> Int
pub fn RunSummary::success(Self) -> Bool
pub fn RunSummary::total_cases(Self) -> Int
pub fn RunSummary::unsupported_cases(Self) -> Int

// Type aliases

// Traits