///|
/// moonmatch demo CLI. Market profiles are passed as command-line
/// arguments and every answer is printed to stdout, so the
/// file-free/stdin-free core promise holds on every backend
/// (this toolchain has no file API and no exit-code API either -
/// the `certificate:` line is the machine-readable verdict).
///
/// Profile syntax: rows separated by `|`, integers by `,`, e.g.
/// `1,0,2|0,1,2|0,1,2` is three agents ranking receivers 0..2.
suberror CliError {
Usage(String)
} derive(Debug)
///|
impl Show for CliError with fn output(self : CliError, logger) {
match self {
Usage(msg) => logger.write_string("usage error: \{msg}")
}
}
///|
let usage : String = "moonmatch [args...]\n (no args) or `demo` hand-traced 3x3 market\n da \n hr \n ttc \n check \n cert \n verify \nrows separated by '|', integers by ','; -1 in a claim means unmatched.\n`check` never runs an algorithm: it re-verifies someone else's answer.\n`cert` exports a JSON certificate; `verify` checks one without running anything."
///|
fn parse_ints(s : String) -> Array[Int] raise CliError {
let out = Array(capacity=4)
for cell in s.split(",") {
let v = Some(@string.FromStr::from_str(cell)) catch { _ => None }
match v {
None => raise Usage("bad integer '\{cell.to_owned()}' in '\{s}'")
Some(x) => out.push(x)
}
}
if out.length() == 0 {
raise Usage("empty number list")
}
out
}
///|
fn parse_table(s : String) -> Array[Array[Int]] raise CliError {
let rows = Array(capacity=2)
for row in s.split("|") {
let r = row.to_owned()
if r.length() == 0 {
raise Usage("empty row in '\{s}' (rows are separated by '|')")
}
rows.push(parse_ints(r))
}
rows
}
///|
fn render_p2p(p2p : Array[Int]) -> String {
let mut out = ""
for p in 0..= 0 { "P\{p} -> R\{r}\n" } else { "P\{p} -> unmatched\n" }
out = out + line
}
out
}
///|
fn verdict_line(blocks : Array[(Int, Int)]) -> String {
if blocks.length() == 0 {
"certificate: STABLE (no blocking pair)"
} else {
let mut out = "certificate: UNSTABLE, \{blocks.length()} blocking pair(s):"
for (p, r) in blocks {
out = out + " (P\{p}, R\{r})"
}
out
}
}
///|
/// Verdict on a delivered JSON blob. Errors are printed, never
/// propagated: a rejected certificate is an ANSWER, not a crash.
fn verify_verdict(blob : String) -> String {
let maybe_cert = Some(@moonmatch.StabilityCert::from_json_string(blob)) catch {
_ => None
}
match maybe_cert {
None => "verify: REJECTED, certificate is not parseable JSON"
Some(cert) => {
let mut verdict = "verify: ACCEPTED (every tempting pair has an honest witness)"
cert.verify() catch {
e => verdict = "verify: REJECTED: \{e}"
}
verdict
}
}
}
///|
fn run(argv : Array[String]) -> String raise {
match argv.length() {
0 => run(["demo"])
1 if argv[0] == "demo" || argv[0] == "help" =>
run(["da", "1,0,2|0,1,2|0,1,2", "2,1,0|0,2,1|0,1,2"])
n if n >= 3 && argv[0] == "da" => {
let prefs = @moonmatch.Preferences::new(
parse_table(argv[1]),
parse_table(argv[2]),
)
let m = prefs.deferred_acceptance()
render_p2p(m.proposer_to_receiver) + verdict_line(m.blocking_pairs())
}
n if n >= 4 && argv[0] == "hr" => {
let prefs = @moonmatch.Preferences::new(
parse_table(argv[1]),
parse_table(argv[2]),
)
let a = prefs.hospital_residents(parse_ints(argv[3]))
let mut out = render_p2p(a.proposer_to_receiver)
for h in 0.. 0 { ", " } else { "" }) + "P\{holders[i]}"
}
let line = if holders.length() == 0 {
"H\{h} holds: nobody\n"
} else {
"H\{h} holds: \{names}\n"
}
out = out + line
}
out + verdict_line(a.blocking_pairs())
}
n if n >= 3 && argv[0] == "ttc" => {
let market = @moonmatch.Market::new(
parse_table(argv[1]),
parse_ints(argv[2]),
)
let alloc = market.top_trading_cycles()
let size = alloc.length()
let mut out = ""
for a in 0.. 7 (exhaustive, exponential)"
}
out + "audit: individually rational = \{ir}\n" + core + "\n"
}
n if n >= 4 && argv[0] == "check" => {
let prefs = @moonmatch.Preferences::new(
parse_table(argv[1]),
parse_table(argv[2]),
)
let m = prefs.matching_from(parse_ints(argv[3]))
render_p2p(m.proposer_to_receiver) + verdict_line(m.blocking_pairs())
}
n if n >= 3 && argv[0] == "cert" => {
let prefs = @moonmatch.Preferences::new(
parse_table(argv[1]),
parse_table(argv[2]),
)
"certificate: \{prefs.deferred_acceptance().certificate().to_json_string()}"
}
n if n >= 2 && argv[0] == "verify" => verify_verdict(argv[1])
_ => raise Usage("unrecognized arguments; here is the help:\n\{usage}")
}
}
///|
fn main {
let argv = @env.args()[1:].to_owned()
println(
run(argv) catch {
Usage(msg) => "error: \{msg}\n\n\{usage}"
e => "error: \{e}"
},
)
}