55-/
66
77import Strata.DL.SMT.SMT
8+ import Strata.DL.SMT.DDMTransform.Parse
9+ import Strata.DL.SMT.DDMTransform.Translate
10+ import Strata.DDM.Elab
11+ import Strata.DDM.Format
812import Strata.DL.Imperative.PureExpr
913import Strata.DL.Imperative.EvalContext
1014
@@ -16,16 +20,22 @@ namespace SMT
1620
1721/--
1822A counterexample derived from an SMT solver is a map from an identifier
19- to a string .
23+ to an `SMT.Term` .
2024-/
21- abbrev CounterEx (Ident : Type ) := Map Ident String
25+ abbrev CounterEx (Ident : Type ) := Map Ident Strata.SMT.Term
26+
27+ /-- Render an `SMT.Term` to a string via the SMTDDM translation. -/
28+ private def termToString (t : Strata.SMT.Term) : String :=
29+ match Strata.SMTDDM.termToString t with
30+ | .ok s => s
31+ | .error _ => repr t |>.pretty
2232
2333def CounterEx.format {Ident} [ToFormat Ident] (cex : CounterEx Ident) : Format :=
2434 match cex with
2535 | [] => ""
26- | [(id, v)] => f!"({id}, {v})"
36+ | [(id, v)] => f!"({id}, {termToString v})"
2737 | (id, v) :: rest =>
28- (f!"({id}, {v}) " ) ++ CounterEx.format rest
38+ (f!"({id}, {termToString v}) " ) ++ CounterEx.format rest
2939
3040instance {Ident} [ToFormat Ident] : ToFormat (CounterEx Ident) where
3141 format := CounterEx.format
@@ -83,27 +93,6 @@ def getSMTId {Ident Ty} [ToFormat Ident]
8393 let key : Strata.SMT.UF := { id := var', args := [], out := ty' }
8494 .ok (E.ufs[key]!)
8595
86- def getModel (m : String) : Except Format (List Strata.SMT.CExParser.KeyValue) := do
87- let cex ← Strata.SMT.CExParser.parseCEx m
88- return cex.pairs
89-
90- def processModel {P : PureExpr} [ToFormat P.Ident]
91- (typedVarToSMTFn : P.Ident → P.Ty → Except Format (String × Strata.SMT.TermType))
92- (vars : List P.TypedIdent) (cexs : List Strata.SMT.CExParser.KeyValue)
93- (E : Strata.SMT.EncoderState) : Except Format (CounterEx P.Ident) := do
94- match vars with
95- | [] => return []
96- | (var, ty) :: vrest =>
97- let id ← @getSMTId P.Ident P.Ty _ typedVarToSMTFn var ty E
98- let value ← findCExValue id cexs
99- let pair := (var, value)
100- let rest ← processModel typedVarToSMTFn vrest cexs E
101- .ok (pair :: rest)
102- where findCExValue id cexs : Except Format String :=
103- match cexs.find? (fun p => p.key == id) with
104- | none => .error f!"Cannot find model for id: {id}"
105- | some p => .ok p.value
106-
10796def runSolver (solver : String) (args : Array String) : IO IO.Process.Output := do
10897 let output ← IO.Process.output {
10998 cmd := solver
@@ -114,9 +103,90 @@ def runSolver (solver : String) (args : Array String) : IO IO.Process.Output :=
114103 -- stdout: {repr output.stdout}"
115104 return output
116105
106+ ---------------------------------------------------------------------
107+ -- SMTDDM-based parsing
108+ ---------------------------------------------------------------------
109+
110+ /--
111+ Parse a verdict line ("sat", "unsat", "unknown") via the SMTResponse DDM
112+ dialect. Returns `some .sat`, `some .unsat`, `some .unknown`, or `none`
113+ on parse/conversion failure.
114+ -/
115+ private def parseVerdict (line : String) : IO (Option (Result PUnit)) := do
116+ let inputCtx := Strata.Parser.stringInputContext "solver" (line ++ "\n " )
117+ let prg ←
118+ try Strata.Elab.parseStrataProgramFromDialect
119+ Strata.SMTResponseDDM.smtResponseDialects "SMTResponse" inputCtx
120+ catch _ => return none
121+ if prg.commands.isEmpty then return none
122+ let op := prg.commands[0 ]!
123+ match Strata.SMTResponseDDM.Command.ofAst op with
124+ | .ok (.specific_success_response _ (.ssr_check_sat _ (.csr_sat _))) => return some (.sat [])
125+ | .ok (.specific_success_response _ (.ssr_check_sat _ (.csr_unsat _))) => return some .unsat
126+ | .ok (.specific_success_response _ (.ssr_check_sat _ (.csr_unknown _))) => return some .unknown
127+ | _ => return none
128+
129+ /--
130+ Parse a `(get-value ...)` model response using the SMTResponse DDM dialect.
131+ Uses `parseCategoryFromDialect` targeting `SMTResponse.GetValueResponse`
132+ directly, which avoids the ambiguity that arises when parsing at the
133+ `Command` level.
134+
135+ Returns a list of (key-string, value-Term) pairs on success.
136+ -/
137+ private def parseModelDDM (modelStr : String) : IO (List (String × Strata.SMT.Term)) := do
138+ let inputCtx := Strata.Parser.stringInputContext "solver-model" modelStr
139+ let op ←
140+ try Strata.Elab.parseCategoryFromDialect
141+ Strata.SMTResponseDDM.smtResponseDialects q`SMTResponse.GetValueResponse inputCtx
142+ catch _ => return []
143+ match Strata.SMTResponseDDM.GetValueResponse.ofAst op with
144+ | .ok (.get_value_response _ vps) =>
145+ let pairs ← vps.val.toList.filterMapM fun vp =>
146+ match vp with
147+ | .valuation_pair _ t1 t2 => do
148+ match Strata.SMTResponseDDM.translateFromDDMTermToUntyped t2 with
149+ | .ok t2' =>
150+ return .some (Strata.SMTResponseDDM.formatArg (.op (Strata.SMTResponseDDM.Term.toAst t1)),
151+ t2')
152+ | .error _ =>
153+ -- The model has an SMT expression (e.g., (lambda ...)) which cannot
154+ -- be represented in Strata.SMT.Term. Filter out this variable from
155+ -- the model.
156+ return .none
157+ return pairs
158+ | .error _ => return []
159+
160+ /--
161+ Process a parsed model (list of key-string / value-Term pairs) against the
162+ expected variables, matching each variable's SMT-encoded name to its
163+ value in the model.
164+ -/
165+ private def processModel {P : PureExpr} [ToFormat P.Ident]
166+ (typedVarToSMTFn : P.Ident → P.Ty → Except Format (String × Strata.SMT.TermType))
167+ (vars : List P.TypedIdent) (pairs : List (String × Strata.SMT.Term))
168+ (E : Strata.SMT.EncoderState) : Except Format (CounterEx P.Ident) := do
169+ match vars with
170+ | [] => return []
171+ | (var, ty) :: vrest =>
172+ let id ← @getSMTId P.Ident P.Ty _ typedVarToSMTFn var ty E
173+ let value ← findValue id pairs
174+ let rest ← processModel typedVarToSMTFn vrest pairs E
175+ .ok ((var, value) :: rest)
176+ where findValue id pairs : Except Format Strata.SMT.Term :=
177+ match pairs.find? (fun p => p.fst == id) with
178+ | none => .error f!"Cannot find model for id: {id}"
179+ | some p => .ok p.snd
180+
117181/--
118182Interprets the output of SMT solver.
119183
184+ Both the verdict line (sat/unsat/unknown) and the model (when sat) are
185+ parsed using the SMTResponse DDM dialect. The verdict is parsed as a full
186+ `Command`, while the model is parsed by targeting the
187+ `SMTResponse.GetValueResponse` category directly via
188+ `parseCategoryFromDialect`.
189+
120190When `reachCheck` is `true`, the solver output contains two verdict lines:
121191the first is the reachability check result (are the path-condition assumptions
122192satisfiable?), and the second is the proof check result. The reachability
@@ -128,46 +198,45 @@ def solverResult {P : PureExpr} [ToFormat P.Ident]
128198 (vars : List P.TypedIdent) (output : IO.Process.Output)
129199 (E : Strata.SMT.EncoderState) (smtsolver : String)
130200 (reachCheck : Bool := false )
131- : Except Format (Option (Result P.Ident) × Result P.Ident) := do
201+ : IO ( Except Format (Option (Result P.Ident) × Result P.Ident) ) := do
132202 let stdout := output.stdout
133- -- When reachCheck is true, the first line of stdout is the reachability
134- -- verdict; strip it and parse it separately.
135- let (reachResult, proofStdout) := if reachCheck then
136- let pos := stdout.find (· == '\n ' )
137- let reachVerdictStr := (stdout.extract stdout.startPos pos).trimAscii
138- let reachResult : Result P.Ident := match reachVerdictStr with
139- | "sat" => .sat []
140- | "unsat" => .unsat
141- | _ => .unknown
142- let remaining := (stdout.extract pos stdout.endPos).drop 1
143- (some reachResult, remaining)
203+ -- Split the next line from the remaining stdout, returning (line, rest).
204+ let splitLine (s : String) : String × String :=
205+ let pos := s.find (· == '\n ' )
206+ let line := (s.extract s.startPos pos).trimAscii.toString
207+ let rest := s.extract pos s.endPos
208+ (line, rest)
209+ -- When reachCheck is true, the first line is the reachability verdict.
210+ let (reachResult, proofStdout) ← if reachCheck then do
211+ let (reachLine, remaining) := splitLine stdout
212+ let reachResult : Result P.Ident ← do
213+ match ← parseVerdict reachLine with
214+ | some (.sat _) => pure (.sat [])
215+ | some .unsat => pure .unsat
216+ | _ => pure .unknown
217+ pure (some reachResult, remaining.drop 1 |>.toString)
144218 else
145- (none, stdout)
146- -- Parse the proof verdict from the (possibly trimmed) stdout
147- let pos := proofStdout.find (· == '\n ' )
148- let verdict := proofStdout.extract proofStdout.startPos pos |>.trimAscii
149- let rest := proofStdout.extract pos proofStdout.endPos
150- match verdict with
151- | "sat" =>
152- let rawModel ← getModel rest
153- -- We suppress any model processing errors.
154- -- Likely, these would be because of the suboptimal implementation
155- -- of the model parser, which shouldn't hold back useful
156- -- feedback (i.e., problem was `sat`) from the user.
157- match (processModel typedVarToSMTFn vars rawModel E) with
158- | .ok model => .ok (reachResult, .sat model)
159- | .error _model_err => .ok (reachResult, .sat [])
160- | "unsat" => .ok (reachResult, .unsat)
161- | "unknown" => .ok (reachResult, .unknown)
162- | _ =>
219+ pure (none, stdout)
220+ -- Parse the proof verdict from the (possibly trimmed) stdout.
221+ let (verdictStr, rest) := splitLine proofStdout
222+ match ← parseVerdict verdictStr with
223+ | some (.sat _) =>
224+ -- Parse model via SMTDDM targeting GetValueResponse category directly.
225+ let pairs ← parseModelDDM rest
226+ match processModel typedVarToSMTFn vars pairs E with
227+ | .ok model => return .ok (reachResult, .sat model)
228+ | .error _ => return .ok (reachResult, .sat [])
229+ | some .unsat => return .ok (reachResult, .unsat)
230+ | some .unknown => return .ok (reachResult, .unknown)
231+ | _ =>
163232 let stderr := output.stderr
164233 let hasExecError := stderr.contains "could not execute external process"
165234 let hasFileError := stderr.contains "No such file or directory"
166235 let suggestion :=
167236 if (hasExecError || hasFileError) && smtsolver == Core.defaultSolver then
168237 s! " \n Ensure { Core.defaultSolver} is on your PATH or use --solver to specify another SMT solver."
169238 else ""
170- .error s! "stderr:{ stderr}{ suggestion} \n solver stdout: { output.stdout} \n "
239+ return .error s! "stderr:{ stderr}{ suggestion} \n solver stdout: { output.stdout} \n "
171240
172241def addLocationInfo {P : PureExpr} [BEq P.Ident]
173242 (md : Imperative.MetaData P) (message : String × String)
@@ -211,7 +280,7 @@ def dischargeObligation {P : PureExpr} [ToFormat P.Ident] [BEq P.Ident]
211280 if printFilename then IO.println s! "Wrote problem to { filename} ."
212281
213282 let solver_output ← runSolver smtsolver (#[filename] ++ solver_options)
214- match solverResult typedVarToSMTFn vars solver_output estate smtsolver (reachCheck := reachCheck) with
283+ match ← solverResult typedVarToSMTFn vars solver_output estate smtsolver (reachCheck := reachCheck) with
215284 | .error e => return .error e
216285 | .ok (reachDecision, result) => return .ok (reachDecision, result, estate)
217286
0 commit comments