nx_tstp_emit.nx source
↩ module page · 110 lines · 4510 B
1// nx_tstp_emit.nx -- TSTP-format proof serializer.
2//
3// Walks the ProofLog + clause set and emits the proof in TSTP format:
4//
5// cnf(c0, axiom, p(a)).
6// cnf(c1, axiom, ~p(a)).
7// cnf(c2, plain, $false, inference(resolution, [], [c0, c1])).
8//
9// Required for CASC submission output. Closes the substrate-side of
10// the BLOCKED_ON_PROOF_HISTORY_TRACKING axis once integrated with the
11// saturation loop (this commit ships the standalone serializer; loop
12// integration is a follow-up).
13//
14// Output discipline: each clause emitted exactly once in proof order
15// (transitive ancestors of the empty clause first, then the empty
16// derivation last). The caller can pre-walk via nx_proof_walk_ancestors
17// to determine the minimal proof support.
18
19// nx_safety_envelope:
20// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
21// sil_target: SIL1
22// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
23// verdict: NOT_YET_EVALUATED
24
25import "nx_syscalls.nx"
26import "nx_runtime.nx"
27import "nx_tier.nx"
28import "nx_str.nx"
29import "nx_result.nx"
30import "nx_unify.nx"
31import "nx_resolution.nx"
32import "nx_tptp_symtab.nx"
33import "nx_tptp_emit.nx"
34import "nx_proof_log.nx"
35
36// Forward decl for the int->decimal helper from nx_tptp_emit (used
37// for clause name generation "c0", "c1", ...). Defined there.
38func nx_int_to_decimal(n: nx_int, out: *u8) -> nx_int;
39
40// Emit one clause in TSTP form, with role + inference annotation.
41// Returns 1 on success, -1 on capacity overflow.
42func nx_tstp_emit_clause(c: *Clause, idx: nx_int, log: *ProofLog,
43 symtab: *TptpSymtab, eq_sym: nx_int,
44 buf: *u8, pos: *nx_int, cap: nx_int) -> nx_int {
45 // "cnf(c<idx>, "
46 if nx_emit_str("cnf(c" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
47 let idx_buf: *u8 = sys_mmap(32)
48 let _il: nx_int = nx_int_to_decimal(idx, idx_buf)
49 if nx_emit_str(idx_buf, buf, pos, cap) < 0 { return 0 - 1 }
50 if nx_emit_str(", " as *u8, buf, pos, cap) < 0 { return 0 - 1 }
51
52 // Role: "axiom" for INPUT, "plain" otherwise. $false body for
53 // empty clauses.
54 let entry: *ProofEntry = nx_proof_log_get(log, idx)
55 var is_input: nx_int = 0
56 if (entry as nx_int) != 0 {
57 if entry.rule == NX_PROOF_RULE_INPUT { is_input = 1 }
58 }
59 if is_input == 1 {
60 if nx_emit_str("axiom, " as *u8, buf, pos, cap) < 0 { return 0 - 1 }
61 }
62 if is_input == 0 {
63 if nx_emit_str("plain, " as *u8, buf, pos, cap) < 0 { return 0 - 1 }
64 }
65
66 // Body
67 if nx_emit_clause_body(c, symtab, eq_sym, buf, pos, cap) < 0 { return 0 - 1 }
68
69 // Inference annotation (only for non-input)
70 if is_input == 0 {
71 if nx_emit_str(", inference(" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
72 let rule_name: *u8 = nx_proof_rule_name(entry.rule)
73 if nx_emit_str(rule_name, buf, pos, cap) < 0 { return 0 - 1 }
74 if nx_emit_str(", [], [" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
75 // First parent
76 if entry.parent_a >= 0 {
77 if nx_emit_str("c" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
78 let pa_buf: *u8 = sys_mmap(32)
79 let _pl: nx_int = nx_int_to_decimal(entry.parent_a, pa_buf)
80 if nx_emit_str(pa_buf, buf, pos, cap) < 0 { return 0 - 1 }
81 }
82 if entry.parent_b >= 0 {
83 if nx_emit_str(", c" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
84 let pb_buf: *u8 = sys_mmap(32)
85 let _pl2: nx_int = nx_int_to_decimal(entry.parent_b, pb_buf)
86 if nx_emit_str(pb_buf, buf, pos, cap) < 0 { return 0 - 1 }
87 }
88 if nx_emit_str("])" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
89 }
90
91 if nx_emit_str(").\n" as *u8, buf, pos, cap) < 0 { return 0 - 1 }
92 return 1
93}
94
95// Emit the full proof: walk every entry in the log up to + including
96// the empty-clause derivation, in order. `clauses` is the parallel
97// clause array; clauses[i] is the clause produced by inference log[i].
98func nx_tstp_emit_proof(clauses: *Clause, n_clauses: nx_int, log: *ProofLog,
99 symtab: *TptpSymtab, eq_sym: nx_int,
100 buf: *u8, pos: *nx_int, cap: nx_int) -> nx_int {
101 var i: nx_int = 0
102 while i < n_clauses {
103 let c: *Clause = ((clauses as nx_int) + (i * NX_CLAUSE_BYTES)) as *Clause
104 if nx_tstp_emit_clause(c, i, log, symtab, eq_sym, buf, pos, cap) < 0 {
105 return 0 - 1
106 }
107 i = i + 1
108 }
109 return 1
110}