1use std::{cell::Cell, fmt::Display, str::FromStr};
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use strum_macros::{Display as StrumDisplay, EnumIter};
6
7use crate::bug;
8
9use crate::solver::adaptors::smt::{IntTheory, MatrixTheory, TheoryConfig};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
12pub enum Parser {
13 #[default]
14 TreeSitter,
15 ViaConjure,
16}
17
18impl Display for Parser {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 Parser::TreeSitter => write!(f, "tree-sitter"),
22 Parser::ViaConjure => write!(f, "via-conjure"),
23 }
24 }
25}
26
27impl FromStr for Parser {
28 type Err = String;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 match s.trim().to_ascii_lowercase().as_str() {
32 "tree-sitter" => Ok(Parser::TreeSitter),
33 "via-conjure" => Ok(Parser::ViaConjure),
34 other => Err(format!(
35 "unknown parser: {other}; expected one of: tree-sitter, via-conjure"
36 )),
37 }
38 }
39}
40
41thread_local! {
42 static CURRENT_PARSER: Cell<Option<Parser>> = const { Cell::new(None) };
46}
47
48pub fn set_current_parser(parser: Parser) {
49 CURRENT_PARSER.with(|current| current.set(Some(parser)));
50}
51
52pub fn current_parser() -> Parser {
53 CURRENT_PARSER.with(|current| {
54 current.get().unwrap_or_else(|| {
55 bug!("current parser not set for this thread; call set_current_parser first")
57 })
58 })
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62pub struct MorphConfig {
63 pub cache: MorphCachingStrategy,
64 pub prefilter: bool,
65 pub naive: bool,
67 pub fixedpoint: bool,
68}
69
70impl Default for MorphConfig {
71 fn default() -> Self {
72 Self {
73 cache: MorphCachingStrategy::NoCache,
74 prefilter: false,
75 naive: true,
76 fixedpoint: false,
77 }
78 }
79}
80
81impl Display for MorphConfig {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 write!(f, "morph")?;
84
85 let mut features = Vec::new();
86 if !self.naive {
87 features.push("levelson");
88 }
89 match self.cache {
90 MorphCachingStrategy::NoCache => {}
91 MorphCachingStrategy::Cache => features.push("cache"),
92 MorphCachingStrategy::IncrementalCache => features.push("inccache"),
93 }
94 if self.prefilter {
95 features.push("prefilteron");
96 }
97 if self.fixedpoint {
98 features.push("fixedpoint");
99 }
100
101 if !features.is_empty() {
102 write!(f, "-{}", features.join("-"))?;
103 }
104
105 Ok(())
106 }
107}
108
109#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
110pub enum MorphCachingStrategy {
111 NoCache,
112 Cache,
113 #[default]
114 IncrementalCache,
115}
116
117impl FromStr for MorphCachingStrategy {
118 type Err = String;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 match s.trim().to_ascii_lowercase().as_str() {
122 "no-cache" => Ok(Self::NoCache),
123 "cache" => Ok(Self::Cache),
124 "inc-cache" => Ok(Self::IncrementalCache),
125 other => Err(format!(
126 "unknown cache strategy: {other}; expected one of: no-cache, cahce, inc-cache"
127 )),
128 }
129 }
130}
131
132impl Display for MorphCachingStrategy {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 match self {
135 MorphCachingStrategy::NoCache => write!(f, "nocache"),
136 MorphCachingStrategy::Cache => write!(f, "cache"),
137 MorphCachingStrategy::IncrementalCache => write!(f, "inccache"),
138 }
139 }
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
143pub enum Rewriter {
144 Naive,
145 Morph(MorphConfig),
146}
147
148thread_local! {
149 static CURRENT_REWRITER: Cell<Option<Rewriter>> = const { Cell::new(None) };
153}
154
155impl Display for Rewriter {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 match self {
158 Rewriter::Naive => write!(f, "naive"),
159 Rewriter::Morph(config) => write!(f, "{config}"),
160 }
161 }
162}
163
164impl FromStr for Rewriter {
165 type Err = String;
166
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 let trimmed = s.trim().to_ascii_lowercase();
169 match trimmed.as_str() {
170 "naive" => Ok(Rewriter::Naive),
171 "morph" => Ok(Rewriter::Morph(MorphConfig::default())),
172 other => {
173 if !other.starts_with("morph-") {
174 return Err(format!(
175 "unknown rewriter: {other}; expected one of: naive, morph, morph-[levelson]-[cache|inccache]-[prefilteron]-[fixedpoint]"
176 ));
177 }
178
179 let parts = other.split('-').skip(1);
180 Ok(Rewriter::Morph(parse_morph_config_tokens(parts)?))
181 }
182 }
183 }
184}
185
186fn parse_morph_config_tokens<'a>(
187 tokens: impl IntoIterator<Item = &'a str>,
188) -> Result<MorphConfig, String> {
189 let mut config = MorphConfig::default();
190 let mut cache_set = false;
191 let mut levels_set = false;
192 let mut prefilter_set = false;
193
194 for token in tokens {
195 match token {
196 "" => (),
197 "levelson" => {
198 if levels_set {
199 return Err(
200 "conflicting levels options: only one levels setting is allowed"
201 .to_string(),
202 );
203 }
204 config.naive = false;
205 levels_set = true;
206 }
207 "cache" | "inccache" => {
208 if cache_set {
209 return Err(
210 "conflicting cache options: only one of cache|inccache is allowed"
211 .to_string(),
212 );
213 }
214 config.cache = match token {
215 "cache" => MorphCachingStrategy::Cache,
216 "inccache" => MorphCachingStrategy::IncrementalCache,
217 _ => unreachable!(),
218 };
219 cache_set = true;
220 }
221 "prefilteron" => {
222 if prefilter_set {
223 return Err(
224 "conflicting prefilter options: only one prefilter setting is allowed"
225 .to_string(),
226 );
227 }
228 config.prefilter = true;
229 prefilter_set = true;
230 }
231 "fixedpoint" => {
232 config.fixedpoint = true;
233 }
234 other_token => {
235 return Err(format!(
236 "unknown morph option '{other_token}', must be one of levelson|cache|inccache|prefilteron|fixedpoint"
237 ));
238 }
239 }
240 }
241
242 Ok(config)
243}
244
245pub fn set_current_rewriter(rewriter: Rewriter) {
246 CURRENT_REWRITER.with(|current| current.set(Some(rewriter)));
247}
248
249pub fn current_rewriter() -> Rewriter {
250 CURRENT_REWRITER.with(|current| {
251 current.get().unwrap_or_else(|| {
252 bug!("current rewriter not set for this thread; call set_current_rewriter first")
254 })
255 })
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
259pub enum QuantifiedExpander {
260 Native,
261 ViaSolver,
262 ViaSolverAc,
263}
264
265impl Display for QuantifiedExpander {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 match self {
268 QuantifiedExpander::Native => write!(f, "native"),
269 QuantifiedExpander::ViaSolver => write!(f, "via-solver"),
270 QuantifiedExpander::ViaSolverAc => write!(f, "via-solver-ac"),
271 }
272 }
273}
274
275impl FromStr for QuantifiedExpander {
276 type Err = String;
277
278 fn from_str(s: &str) -> Result<Self, Self::Err> {
279 match s.trim().to_ascii_lowercase().as_str() {
280 "native" => Ok(QuantifiedExpander::Native),
281 "via-solver" => Ok(QuantifiedExpander::ViaSolver),
282 "via-solver-ac" => Ok(QuantifiedExpander::ViaSolverAc),
283 _ => Err(format!(
284 "unknown comprehension expander: {s}; expected one of: \
285 native, via-solver, via-solver-ac"
286 )),
287 }
288 }
289}
290
291thread_local! {
292 static COMPREHENSION_EXPANDER: Cell<Option<QuantifiedExpander>> = const { Cell::new(None) };
296}
297
298pub fn set_comprehension_expander(expander: QuantifiedExpander) {
299 COMPREHENSION_EXPANDER.with(|current| current.set(Some(expander)));
300}
301
302pub fn comprehension_expander() -> QuantifiedExpander {
303 COMPREHENSION_EXPANDER.with(|current| {
304 current.get().unwrap_or_else(|| {
305 bug!(
307 "comprehension expander not set for this thread; call set_comprehension_expander first"
308 )
309 })
310 })
311}
312
313#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)]
314pub enum SatEncoding {
315 #[default]
316 Log,
317 Direct,
318 Order,
319}
320
321impl SatEncoding {
322 pub const fn as_str(self) -> &'static str {
323 match self {
324 SatEncoding::Log => "log",
325 SatEncoding::Direct => "direct",
326 SatEncoding::Order => "order",
327 }
328 }
329
330 pub const fn as_rule_set(self) -> &'static str {
331 match self {
332 SatEncoding::Log => "SAT_Log",
333 SatEncoding::Direct => "SAT_Direct",
334 SatEncoding::Order => "SAT_Order",
335 }
336 }
337}
338
339impl Display for SatEncoding {
340 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341 match self {
342 SatEncoding::Log => write!(f, "log"),
343 SatEncoding::Direct => write!(f, "direct"),
344 SatEncoding::Order => write!(f, "order"),
345 }
346 }
347}
348
349impl FromStr for SatEncoding {
350 type Err = String;
351
352 fn from_str(s: &str) -> Result<Self, Self::Err> {
353 match s.trim().to_ascii_lowercase().as_str() {
354 "log" => Ok(SatEncoding::Log),
355 "direct" => Ok(SatEncoding::Direct),
356 "order" => Ok(SatEncoding::Order),
357 other => Err(format!(
358 "unknown sat-encoding: {other}; expected one of: log, direct, order"
359 )),
360 }
361 }
362}
363
364#[derive(
365 Debug,
366 EnumIter,
367 StrumDisplay,
368 PartialEq,
369 Eq,
370 Hash,
371 Clone,
372 Copy,
373 Serialize,
374 Deserialize,
375 JsonSchema,
376)]
377pub enum SolverFamily {
378 Minion,
379 Sat(SatEncoding),
380 Smt(TheoryConfig),
381}
382
383thread_local! {
384 static CURRENT_SOLVER_FAMILY: Cell<Option<SolverFamily>> = const { Cell::new(None) };
388}
389
390pub const DEFAULT_MINION_DISCRETE_THRESHOLD: usize = 10;
391
392thread_local! {
393 static MINION_DISCRETE_THRESHOLD: Cell<usize> =
398 const { Cell::new(DEFAULT_MINION_DISCRETE_THRESHOLD) };
399
400 static RULE_TRACE_ENABLED: Cell<bool> = const { Cell::new(false) };
405
406 static DEFAULT_RULE_TRACE_ENABLED: Cell<bool> = const { Cell::new(false) };
408
409 static RULE_TRACE_VERBOSE_ENABLED: Cell<bool> = const { Cell::new(false) };
411
412 static RULE_TRACE_AGGREGATES_ENABLED: Cell<bool> = const { Cell::new(false) };
414}
415
416pub fn set_current_solver_family(solver_family: SolverFamily) {
417 CURRENT_SOLVER_FAMILY.with(|current| current.set(Some(solver_family)));
418}
419
420pub fn current_solver_family() -> SolverFamily {
421 CURRENT_SOLVER_FAMILY.with(|current| {
422 current.get().unwrap_or_else(|| {
423 bug!(
425 "current solver family not set for this thread; call set_current_solver_family first"
426 )
427 })
428 })
429}
430
431pub fn set_minion_discrete_threshold(threshold: usize) {
432 MINION_DISCRETE_THRESHOLD.with(|current| current.set(threshold));
433}
434
435pub fn minion_discrete_threshold() -> usize {
436 MINION_DISCRETE_THRESHOLD.with(|current| current.get())
437}
438
439pub fn set_rule_trace_enabled(enabled: bool) {
440 RULE_TRACE_ENABLED.with(|current| current.set(enabled));
441}
442
443pub fn rule_trace_enabled() -> bool {
444 RULE_TRACE_ENABLED.with(|current| current.get())
445}
446
447pub fn set_default_rule_trace_enabled(enabled: bool) {
448 DEFAULT_RULE_TRACE_ENABLED.with(|current| current.set(enabled));
449}
450
451pub fn default_rule_trace_enabled() -> bool {
452 DEFAULT_RULE_TRACE_ENABLED.with(|current| current.get())
453}
454
455pub fn set_rule_trace_verbose_enabled(enabled: bool) {
456 RULE_TRACE_VERBOSE_ENABLED.with(|current| current.set(enabled));
457}
458
459pub fn rule_trace_verbose_enabled() -> bool {
460 RULE_TRACE_VERBOSE_ENABLED.with(|current| current.get())
461}
462
463pub fn set_rule_trace_aggregates_enabled(enabled: bool) {
464 RULE_TRACE_AGGREGATES_ENABLED.with(|current| current.set(enabled));
465}
466
467pub fn rule_trace_aggregates_enabled() -> bool {
468 RULE_TRACE_AGGREGATES_ENABLED.with(|current| current.get())
469}
470
471pub fn configured_rule_trace_enabled() -> bool {
472 default_rule_trace_enabled() || rule_trace_verbose_enabled() || rule_trace_aggregates_enabled()
473}
474
475impl FromStr for SolverFamily {
476 type Err = String;
477
478 fn from_str(s: &str) -> Result<Self, Self::Err> {
479 let s = s.trim().to_ascii_lowercase();
480
481 match s.as_str() {
482 "minion" => Ok(SolverFamily::Minion),
483 "sat" | "sat-log" => Ok(SolverFamily::Sat(SatEncoding::Log)),
484 "sat-direct" => Ok(SolverFamily::Sat(SatEncoding::Direct)),
485 "sat-order" => Ok(SolverFamily::Sat(SatEncoding::Order)),
486 "smt" => Ok(SolverFamily::Smt(TheoryConfig::default())),
487 other => {
488 if other.starts_with("smt-") {
490 let parts = other.split('-').skip(1);
491 let mut ints = IntTheory::default();
492 let mut matrices = MatrixTheory::default();
493 let mut unwrap_alldiff = false;
494
495 for token in parts {
496 match token {
497 "" => {}
498 "lia" => ints = IntTheory::Lia,
499 "bv" => ints = IntTheory::Bv,
500 "arrays" => matrices = MatrixTheory::Arrays,
501 "atomic" => matrices = MatrixTheory::Atomic,
502 "nodiscrete" => unwrap_alldiff = true,
503 other_token => {
504 return Err(format!(
505 "unknown SMT theory option '{other_token}', must be one of bv|lia|arrays|atomic|nodiscrete"
506 ));
507 }
508 }
509 }
510
511 return Ok(SolverFamily::Smt(TheoryConfig {
512 ints,
513 matrices,
514 unwrap_alldiff,
515 }));
516 }
517 Err(format!(
518 "unknown solver family '{other}', expected one of: minion, sat-log, sat-direct, sat-order, smt[(bv|lia)-(arrays|atomic)][-nodiscrete]"
519 ))
520 }
521 }
522 }
523}
524
525impl SolverFamily {
526 pub fn as_str(&self) -> String {
527 match self {
528 SolverFamily::Minion => "minion".to_owned(),
529 SolverFamily::Sat(encoding) => format!("sat-{}", encoding.as_str()),
530 SolverFamily::Smt(theory_config) => format!("smt-{}", theory_config.as_str()),
531 }
532 }
533}
534
535#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
536pub struct SolverArgs {
537 pub timeout_ms: Option<u64>,
538}