frontend/gda_expr API

frontend/gda_expr 读取 General Decimal Arithmetic 测试文件(Cowlishaw 十进制规范的 .decTest 格式),并针对 decimal_gda 执行其中的行。解析将每一行转换为一个 GdaCase,附带其上下文和一个 numeric_expr 表达式;执行时对表达式求值,并将结果和状态标志与该行进行比较。该包不进行任何文件或进程 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 是用于跨度(span)中的名称(通常为文件路径),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…(至少两个记号),右侧被切分为 expected condition…(至少一个记号)。记号以空格、制表符或换行分隔;用 '…' 或 "…" 括起来的记号可以包含空格,并会去掉引号。-> 之后未闭合的引号产生诊断 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 是一个测试行,连同其所在行生效的上下文。

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

该表达式为 Expr::invoke(Operation::new(normalized_operation), …),作用于每个操作数各一个 Expr::literal,它们都带有该行的跨度。

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 是一条带位置的解析错误。

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;
  • 若某个条件不属于十三种 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

默认值:一个分片,索引 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 是一个被选中行的结果。

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 汇总一次运行的结果。

pub struct RunSummary {
  // private fields
}

RunSummary 计数器

这些方法返回一次运行的各项计数。

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 为真。

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

跳过的行不影响 success。

RunSummary::merge

RunSummary::merge(parts) 合并一次运行中各分片的摘要。

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

除 total_cases 之外的所有计数器都相加;total_cases 取各部分的最大值(每个分片报告的总数相同)。结果按 parts 的顺序拼接。合并一次运行的 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")
}

trait 实现

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