///|
fn safe_markdown(s : String) -> String {
let b = StringBuilder()
for c in s.iter() {
match c {
'&' => b.write_string("&")
'<' => b.write_string("<")
'>' => b.write_string(">")
'\\' | '`' | '[' | ']' | '*' | '_' | '|' | '#' => {
b.write_char('\\')
b.write_char(c)
}
'\n' | '\r' => b.write_char(' ')
_ => b.write_char(c)
}
}
b.to_string()
}
///|
fn outcome_cn(o : Outcome) -> String {
match o {
Consistent =>
"符合所选模型(仅针对提供的历史与版本约束)"
Violation => "发现确定的事务一致性异常"
Inconclusive => "信息或分析预算不足,无法判定"
Invalid => "输入不符合要求,未进行有效分析"
}
}
///|
/// Render local text, Markdown, JSON or Graphviz DOT. No external renderer is required.
pub fn render_report(r : Report, format : String) -> String {
if format == "json" {
return report_string(r) + "\n"
}
let b = StringBuilder()
if format == "dot" {
b.write_string(
"digraph MoonTxnWitness {\n graph [rankdir=LR];\n node [shape=box];\n",
)
for e in r.edges {
let label = dependency_label(e.kind) +
(match e.key {
Some(k) => " · " + k
None => ""
})
b.write_string(
" " +
e.from.to_json().stringify() +
" -> " +
e.to.to_json().stringify() +
" [label=" +
label.to_json().stringify(),
)
if r.witness.contains(e) {
b.write_string(", color=\"#b42318\", penwidth=3")
}
b.write_string("];\n")
}
b.write_string("}\n")
return b.to_string()
}
let markdown = format == "markdown"
b.write_string(
if markdown {
"# MoonTxnWitness v"
} else {
"MoonTxnWitness v"
},
)
b.write_string(version() + "\n\n" + outcome_cn(r.outcome) + "\n")
b.write_string("模型:" + model_label(r.model) + "\n")
b.write_string(
"事务:" +
r.transaction_count.to_string() +
";已提交:" +
r.committed_count.to_string() +
";操作:" +
r.operation_count.to_string() +
";依赖:" +
r.edges.length().to_string() +
"\n",
)
if !r.witness.is_empty() {
b.write_string(
if markdown {
"\n## 异常环证据\n\n"
} else {
"\n异常环证据:\n"
},
)
for i, e in r.witness {
let from = if markdown { safe_markdown(e.from) } else { e.from }
let target = if markdown { safe_markdown(e.to) } else { e.to }
let key = match e.key {
Some(k) => if markdown { safe_markdown(k) } else { k }
None => "共同观察时钟"
}
b.write_string(
(i + 1).to_string() +
". " +
from +
" → " +
target +
" [" +
dependency_label(e.kind) +
"," +
key +
"]\n",
)
b.write_string(" " + dependency_cn(e.kind) + "\n")
match (e.from_op, e.to_op, e.from_version, e.to_version) {
(Some(a), Some(z), Some(v), Some(w)) => {
let sv = if markdown { safe_markdown(v) } else { v }
let sw = if markdown { safe_markdown(w) } else { w }
b.write_string(
" 操作下标 " +
a.to_string() +
" → " +
z.to_string() +
",版本 " +
sv +
" → " +
sw +
"\n",
)
}
_ => ()
}
}
b.write_string(
"\n证据包含构建图中事务数量最少的有向环;不声称整份历史已被全局最小化。\n",
)
}
if !r.findings.is_empty() {
b.write_string(
if markdown {
"\n## 发现与缺失信息\n\n"
} else {
"\n发现与缺失信息:\n"
},
)
for f in r.findings {
let message = if markdown {
safe_markdown(finding_cn(f))
} else {
finding_cn(f)
}
b.write_string("- [" + f.severity + "] " + f.code + ": " + message)
if !f.transactions.is_empty() {
b.write_string(
";事务=" +
(if markdown {
f.transactions.map(safe_markdown)
} else {
f.transactions
}).join(", "),
)
}
b.write_string("\n")
}
}
if !r.serial_order.is_empty() {
b.write_string(
"\n一个可行的串行顺序:" +
(if markdown {
r.serial_order.map(safe_markdown)
} else {
r.serial_order
}).join(" → ") +
"\n",
)
}
if !r.coverage.is_empty() {
b.write_string(
if markdown {
"\n## 版本来源\n\n"
} else {
"\n版本来源:\n"
},
)
for i, k in r.coverage {
if i >= 64 {
b.write_string("其余键的统计请查看 JSON coverage。\n")
break
}
let key = if markdown { safe_markdown(k.key) } else { k.key }
b.write_string(
"- " +
key +
":已提交读 " +
k.committed_reads.to_string() +
",最终写 " +
k.committed_final_writes.to_string() +
"," +
order_source_cn(r.order_sources.at(k.key), k.committed_final_writes) +
",来源未确定读 " +
k.unresolved_reads.to_string() +
"\n",
)
}
}
if r.search.status != "not-needed" {
let label = match r.search.status {
"found" => "找到可行串行执行(只证明存在)"
"exhausted" => "穷尽候选,无法重现所有读取"
"budget" => "计算预算用尽,无法判定"
_ => "规模超出补全预算,无法判定"
}
b.write_string(
"\n精确补全:" +
label +
";尝试 " +
r.search.attempts.to_string() +
" 次。\n",
)
}
let steps = next_steps(r)
if !steps.is_empty() {
b.write_string(
if markdown {
"\n## 下一步\n\n"
} else {
"\n下一步:\n"
},
)
for step in steps {
b.write_string("- " + step + "\n")
}
}
b.write_string(
"\n范围:唯一版本标记的点读写,可跨多个键组成事务。结论针对所提供历史;搜索得到的补全不是实际观测顺序,不能直接证明整个数据库可靠。\n",
)
b.to_string()
}
///|
fn exit_code(o : Outcome) -> Int {
match o {
Consistent => 0
Violation => 1
Invalid => 2
Inconclusive => 3
}
}
///|
/// Host transport combines a structured result and its MoonBit-rendered presentation.
pub fn run_json(text : String, format : String) -> String {
let r = match parse_history(text) {
Ok(h) => analyze(h)
Err(message) => {
let empty : History = {
model: Serializable,
initial: Map([]),
version_orders: Map([]),
transactions: [],
}
base_report(
empty,
Invalid,
[issue("invalid_input", "error", message)],
Map([]),
)
}
}
let transport : Json = {
"report": report_json(r),
"output": render_report(r, format).to_json(),
"exitCode": exit_code(r.outcome).to_json(),
}
transport.stringify()
}
///|
/// Explicit history export is useful to library consumers and retains all recorded operations.
pub fn history_json(h : History) -> Json {
let transactions = h.transactions.map(tx => {
let ops = tx.ops.map(op => {
(
{
"type": op_label(op.kind).to_json(),
"key": op.key.to_json(),
"version": op.version.to_json(),
} : Json)
})
let start = match tx.start {
Some(v) => v.to_double().to_json()
None => Json::null()
}
let end = match tx.end {
Some(v) => v.to_double().to_json()
None => Json::null()
}
(
{
"id": tx.id.to_json(),
"status": status_label(tx.status).to_json(),
"ops": ops.to_json(),
"start": start,
"end": end,
} : Json)
})
{
"schemaVersion": 1,
"model": model_label(h.model).to_json(),
"initial": h.initial.to_json(),
"versionOrders": h.version_orders.to_json(),
"transactions": transactions.to_json(),
}
}