Skip to main content

oxilean_std/program_synthesis/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6use std::collections::HashMap;
7
8use super::functions::Oracle;
9use super::functions::*;
10
11/// A refinement type used in liquid type synthesis.
12#[allow(dead_code)]
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RefinementType {
15    /// The base type (e.g., "Int", "Bool").
16    pub base: String,
17    /// The refinement predicate as a string (e.g., "v > 0").
18    pub predicate: String,
19}
20#[allow(dead_code)]
21impl RefinementType {
22    /// Construct a new refinement type.
23    ///
24    /// ```
25    /// use oxilean_std::program_synthesis::RefinementType;
26    /// let rt = RefinementType::new("Int", "v >= 0");
27    /// assert_eq!(rt.base, "Int");
28    /// ```
29    pub fn new(base: impl Into<String>, predicate: impl Into<String>) -> Self {
30        Self {
31            base: base.into(),
32            predicate: predicate.into(),
33        }
34    }
35    /// Check whether the predicate is trivially true (empty string).
36    pub fn is_trivial(&self) -> bool {
37        self.predicate.trim().is_empty()
38    }
39    /// Return a strengthened type by conjoining an extra predicate.
40    pub fn strengthen(&self, extra: impl Into<String>) -> Self {
41        let new_pred = format!("({}) && ({})", self.predicate, extra.into());
42        Self {
43            base: self.base.clone(),
44            predicate: new_pred,
45        }
46    }
47    /// Return a weakened type by disjoining an extra predicate.
48    pub fn weaken(&self, extra: impl Into<String>) -> Self {
49        let new_pred = format!("({}) || ({})", self.predicate, extra.into());
50        Self {
51            base: self.base.clone(),
52            predicate: new_pred,
53        }
54    }
55}
56/// A constraint-based synthesis engine that encodes the problem as SMT.
57#[allow(dead_code)]
58#[derive(Debug, Clone, Default)]
59pub struct ConstraintSynthEngine {
60    /// Hard constraints that the synthesised program must satisfy.
61    pub hard_constraints: Vec<String>,
62    /// Soft constraints (ranked by weight).
63    pub soft_constraints: Vec<(String, u32)>,
64    /// The program template (sketch).
65    pub template: Option<String>,
66}
67#[allow(dead_code)]
68impl ConstraintSynthEngine {
69    /// Create a new empty constraint synthesis engine.
70    pub fn new() -> Self {
71        Self::default()
72    }
73    /// Add a hard constraint.
74    pub fn add_hard(&mut self, constraint: impl Into<String>) {
75        self.hard_constraints.push(constraint.into());
76    }
77    /// Add a soft constraint with a weight.
78    pub fn add_soft(&mut self, constraint: impl Into<String>, weight: u32) {
79        self.soft_constraints.push((constraint.into(), weight));
80    }
81    /// Set the program template (sketch with holes).
82    pub fn set_template(&mut self, template: impl Into<String>) {
83        self.template = Some(template.into());
84    }
85    /// Count total constraints.
86    pub fn num_constraints(&self) -> usize {
87        self.hard_constraints.len() + self.soft_constraints.len()
88    }
89    /// Encode as a pseudo-Boolean optimisation problem (placeholder).
90    pub fn encode_pbo(&self) -> String {
91        let hard: Vec<String> = self
92            .hard_constraints
93            .iter()
94            .map(|c| format!("HARD: {}", c))
95            .collect();
96        let soft: Vec<String> = self
97            .soft_constraints
98            .iter()
99            .map(|(c, w)| format!("SOFT[{}]: {}", w, c))
100            .collect();
101        [hard, soft].concat().join("\n")
102    }
103    /// Attempt to synthesise a program satisfying all hard constraints.
104    /// Returns the filled template or `None` if unsatisfiable.
105    pub fn solve(&self) -> Option<String> {
106        if self.hard_constraints.is_empty() {
107            self.template.clone()
108        } else {
109            None
110        }
111    }
112}
113/// A logical specification for a synthesis problem.
114///
115/// Specifications range from purely logical (pre/post conditions) to
116/// example-based (input/output pairs) to grammar-constrained.
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum Spec {
119    /// A logical predicate `P(x, y)` relating inputs `x` to output `y`.
120    Logic(String),
121    /// A finite set of concrete input/output examples.
122    Examples(Vec<(Vec<String>, String)>),
123    /// A context-free grammar restricting the syntactic form of solutions.
124    Grammar(CFG),
125    /// Conjunction of multiple specifications.
126    Conjunction(Box<Spec>, Box<Spec>),
127    /// Disjunction: any one of the specs suffices.
128    Disjunction(Box<Spec>, Box<Spec>),
129}
130impl Spec {
131    /// Build a logic spec from a predicate string.
132    ///
133    /// ```
134    /// use oxilean_std::program_synthesis::Spec;
135    /// let s = Spec::logic("output = input * 2");
136    /// assert!(matches!(s, Spec::Logic(_)));
137    /// ```
138    pub fn logic(pred: impl Into<String>) -> Self {
139        Spec::Logic(pred.into())
140    }
141    /// Build a spec from a list of (inputs, output) example pairs.
142    ///
143    /// ```
144    /// use oxilean_std::program_synthesis::Spec;
145    /// let s = Spec::from_examples(vec![(vec!["0".into()], "0".into()),
146    ///                                   (vec!["1".into()], "2".into())]);
147    /// assert!(matches!(s, Spec::Examples(_)));
148    /// ```
149    pub fn from_examples(ex: Vec<(Vec<String>, String)>) -> Self {
150        Spec::Examples(ex)
151    }
152    /// Return the number of constraints (examples or logical clauses) in this spec.
153    pub fn constraint_count(&self) -> usize {
154        match self {
155            Spec::Logic(_) => 1,
156            Spec::Examples(ex) => ex.len(),
157            Spec::Grammar(_) => 1,
158            Spec::Conjunction(a, b) => a.constraint_count() + b.constraint_count(),
159            Spec::Disjunction(a, b) => a.constraint_count().max(b.constraint_count()),
160        }
161    }
162}
163/// The CEGIS synthesis loop state.
164#[derive(Debug, Clone)]
165pub struct CegisState {
166    /// The specification to satisfy.
167    pub spec: Spec,
168    /// Counterexamples accumulated across iterations.
169    pub counterexamples: Vec<Vec<String>>,
170    /// Current candidate (if any).
171    pub candidate: Option<Candidate>,
172    /// Number of CEGIS iterations performed.
173    pub iterations: usize,
174    /// Maximum allowed iterations before giving up.
175    pub max_iterations: usize,
176}
177impl CegisState {
178    /// Initialise a CEGIS loop for the given spec.
179    ///
180    /// ```
181    /// use oxilean_std::program_synthesis::{CegisState, Spec};
182    /// let state = CegisState::new(Spec::logic("y = x + 1"), 100);
183    /// assert_eq!(state.iterations, 0);
184    /// ```
185    pub fn new(spec: Spec, max_iterations: usize) -> Self {
186        Self {
187            spec,
188            counterexamples: Vec::new(),
189            candidate: None,
190            iterations: 0,
191            max_iterations,
192        }
193    }
194    /// Propose a candidate (synthesiser step).
195    pub fn propose(&mut self, candidate: Candidate) {
196        self.candidate = Some(candidate);
197        self.iterations += 1;
198    }
199    /// Record a counterexample returned by the verifier.
200    pub fn add_counterexample(&mut self, ce: Vec<String>) {
201        self.counterexamples.push(ce);
202        self.candidate = None;
203    }
204    /// Check whether the synthesis loop has terminated successfully.
205    pub fn is_solved(&self) -> bool {
206        self.candidate.is_some()
207    }
208    /// Check whether the loop has exceeded its iteration budget.
209    pub fn is_exhausted(&self) -> bool {
210        self.iterations >= self.max_iterations
211    }
212}
213/// A Manna–Waldinger synthesis goal: (precondition, postcondition, output variable).
214#[allow(dead_code)]
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct MWSynthGoal {
217    /// The precondition predicate.
218    pub pre: String,
219    /// The postcondition predicate relating inputs and output.
220    pub post: String,
221    /// The output variable name.
222    pub output_var: String,
223    /// Sub-goals generated by applying a rule.
224    pub subgoals: Vec<MWSynthGoal>,
225}
226#[allow(dead_code)]
227impl MWSynthGoal {
228    /// Create a leaf synthesis goal.
229    ///
230    /// ```
231    /// use oxilean_std::program_synthesis::MWSynthGoal;
232    /// let g = MWSynthGoal::leaf("true", "y = x + 1", "y");
233    /// assert!(g.subgoals.is_empty());
234    /// ```
235    pub fn leaf(
236        pre: impl Into<String>,
237        post: impl Into<String>,
238        output_var: impl Into<String>,
239    ) -> Self {
240        Self {
241            pre: pre.into(),
242            post: post.into(),
243            output_var: output_var.into(),
244            subgoals: Vec::new(),
245        }
246    }
247    /// Create a goal with sub-goals (internal node).
248    pub fn with_subgoals(
249        pre: impl Into<String>,
250        post: impl Into<String>,
251        output_var: impl Into<String>,
252        subgoals: Vec<MWSynthGoal>,
253    ) -> Self {
254        Self {
255            pre: pre.into(),
256            post: post.into(),
257            output_var: output_var.into(),
258            subgoals,
259        }
260    }
261    /// Depth of the goal tree.
262    pub fn depth(&self) -> usize {
263        if self.subgoals.is_empty() {
264            0
265        } else {
266            1 + self.subgoals.iter().map(|sg| sg.depth()).max().unwrap_or(0)
267        }
268    }
269    /// Check whether this is an atomic (leaf) goal.
270    pub fn is_leaf(&self) -> bool {
271        self.subgoals.is_empty()
272    }
273}
274/// An input/output example for programming by example.
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub struct IOExample {
277    /// Input values (as strings for generality).
278    pub inputs: Vec<String>,
279    /// Expected output.
280    pub output: String,
281}
282impl IOExample {
283    /// Create a new I/O example.
284    pub fn new(inputs: Vec<String>, output: impl Into<String>) -> Self {
285        Self {
286            inputs,
287            output: output.into(),
288        }
289    }
290}
291/// An enumerative SyGuS solver that exhaustively searches the grammar.
292#[derive(Debug, Clone)]
293pub struct EnumerativeSolver {
294    /// Depth limit for grammar expansion.
295    pub depth_limit: usize,
296    /// Number of programs enumerated so far.
297    pub enumerated: usize,
298}
299impl EnumerativeSolver {
300    /// Create a new enumerative solver with the given depth limit.
301    pub fn new(depth_limit: usize) -> Self {
302        Self {
303            depth_limit,
304            enumerated: 0,
305        }
306    }
307    /// Enumerate all programs up to `depth` from `start` symbol.
308    pub fn enumerate(&mut self, grammar: &CFG, depth: usize) -> Vec<String> {
309        if depth == 0 {
310            let terms: Vec<String> = grammar.terminals.clone();
311            self.enumerated += terms.len();
312            terms
313        } else {
314            let mut result = Vec::new();
315            for prod in &grammar.productions.clone() {
316                if prod.rhs.iter().all(|s| grammar.terminals.contains(s)) {
317                    let term = prod.rhs.join(" ");
318                    result.push(term);
319                    self.enumerated += 1;
320                }
321            }
322            result
323        }
324    }
325    /// Attempt to solve a SyGuS problem (simplified placeholder).
326    pub fn solve(&mut self, _problem: &SyGuSProblem) -> SyGuSResult {
327        SyGuSResult::Timeout
328    }
329}
330/// A hole in a program sketch, parameterised by expected type.
331#[derive(Debug, Clone, PartialEq, Eq)]
332pub struct Hole {
333    /// Unique identifier for this hole.
334    pub id: usize,
335    /// Expected type of the expression filling this hole.
336    pub expected_type: String,
337    /// Optional constraint on the filler.
338    pub constraint: Option<String>,
339}
340impl Hole {
341    /// Create a new unconstrained hole.
342    pub fn new(id: usize, expected_type: impl Into<String>) -> Self {
343        Self {
344            id,
345            expected_type: expected_type.into(),
346            constraint: None,
347        }
348    }
349    /// Create a constrained hole.
350    pub fn constrained(
351        id: usize,
352        expected_type: impl Into<String>,
353        constraint: impl Into<String>,
354    ) -> Self {
355        Self {
356            id,
357            expected_type: expected_type.into(),
358            constraint: Some(constraint.into()),
359        }
360    }
361}
362/// A finite-table oracle backed by a lookup table.
363#[derive(Debug, Clone, Default)]
364pub struct TableOracle {
365    /// The lookup table.
366    pub table: HashMap<Vec<String>, String>,
367}
368impl TableOracle {
369    /// Build a new table oracle.
370    pub fn new() -> Self {
371        Self::default()
372    }
373    /// Insert an entry.
374    pub fn insert(&mut self, input: Vec<String>, output: impl Into<String>) {
375        self.table.insert(input, output.into());
376    }
377}
378/// A program sketch: a program template with "holes" to be filled.
379#[allow(dead_code)]
380#[derive(Debug, Clone)]
381pub struct ProgramSketch {
382    /// The template string with {hole_N} placeholders.
383    pub template: String,
384    /// The number of holes.
385    pub num_holes: usize,
386    /// The type constraints for each hole (simplified).
387    pub hole_types: Vec<String>,
388}
389#[allow(dead_code)]
390impl ProgramSketch {
391    /// Create a new program sketch.
392    pub fn new(template: &str, num_holes: usize) -> Self {
393        ProgramSketch {
394            template: template.to_string(),
395            num_holes,
396            hole_types: vec!["Any".to_string(); num_holes],
397        }
398    }
399    /// Set the type of hole i.
400    pub fn set_hole_type(&mut self, i: usize, ty: &str) {
401        if i < self.hole_types.len() {
402            self.hole_types[i] = ty.to_string();
403        }
404    }
405    /// Fill holes with given completions to get a concrete program.
406    pub fn fill(&self, completions: &[&str]) -> String {
407        let mut result = self.template.clone();
408        for (i, completion) in completions.iter().enumerate() {
409            result = result.replace(&format!("{{hole_{}}}", i), completion);
410        }
411        result
412    }
413    /// Check if all holes are filled in a given program string.
414    pub fn is_complete(&self, program: &str) -> bool {
415        !(0..self.num_holes).any(|i| program.contains(&format!("{{hole_{}}}", i)))
416    }
417    /// Generate all sketches of programs up to depth `d` with `k` holes.
418    /// Returns simplified count: (operations^holes).
419    pub fn sketch_space_size(&self, num_operations: u64) -> u64 {
420        num_operations.saturating_pow(self.num_holes as u32)
421    }
422}
423/// A version space: the set of programs consistent with all examples seen.
424#[derive(Debug, Clone)]
425pub struct VersionSpace {
426    /// Candidate programs still consistent with all examples.
427    pub candidates: Vec<String>,
428    /// Examples used to prune the version space.
429    pub examples: Vec<IOExample>,
430}
431impl VersionSpace {
432    /// Create a version space from an initial candidate set.
433    pub fn new(candidates: Vec<String>) -> Self {
434        Self {
435            candidates,
436            examples: Vec::new(),
437        }
438    }
439    /// Refine the version space with a new example.
440    pub fn refine(&mut self, example: IOExample) {
441        self.examples.push(example);
442    }
443    /// Return the unique candidate if the version space is a singleton.
444    pub fn unique_solution(&self) -> Option<&str> {
445        if self.candidates.len() == 1 {
446            Some(&self.candidates[0])
447        } else {
448            None
449        }
450    }
451    /// Number of consistent candidates.
452    pub fn size(&self) -> usize {
453        self.candidates.len()
454    }
455}
456/// A sketch-based synthesiser that fills holes using constraint solving.
457#[derive(Debug, Clone)]
458pub struct SketchSolver {
459    /// Maximum number of candidate values tried per hole.
460    pub candidates_per_hole: usize,
461}
462impl SketchSolver {
463    /// Create a new sketch solver.
464    pub fn new(candidates_per_hole: usize) -> Self {
465        Self {
466            candidates_per_hole,
467        }
468    }
469    /// Attempt to complete a sketch given a specification.
470    ///
471    /// Returns the completed program or `None` if no solution is found.
472    pub fn complete(&self, sketch: &Sketch, _spec: &Spec) -> Option<String> {
473        if sketch.is_complete() {
474            return Some(sketch.source.clone());
475        }
476        None
477    }
478}
479/// An inductive loop invariant synthesiser using Houdini-style fixpoint.
480#[allow(dead_code)]
481#[derive(Debug, Clone)]
482pub struct HoudiniInvariantSynth {
483    /// Candidate invariant predicates to try.
484    pub candidates: Vec<String>,
485    /// Iterations of the Houdini fixpoint computation.
486    pub iterations: usize,
487}
488#[allow(dead_code)]
489impl HoudiniInvariantSynth {
490    /// Create a Houdini synthesiser with an initial set of candidates.
491    ///
492    /// ```
493    /// use oxilean_std::program_synthesis::HoudiniInvariantSynth;
494    /// let h = HoudiniInvariantSynth::new(vec!["x >= 0".into(), "x <= n".into()]);
495    /// assert_eq!(h.candidates.len(), 2);
496    /// ```
497    pub fn new(candidates: Vec<String>) -> Self {
498        Self {
499            candidates,
500            iterations: 0,
501        }
502    }
503    /// Add a candidate invariant predicate.
504    pub fn add_candidate(&mut self, pred: impl Into<String>) {
505        self.candidates.push(pred.into());
506    }
507    /// Simulate one Houdini iteration: remove candidates falsified by pre-image.
508    ///
509    /// In a real implementation, each candidate would be checked against the
510    /// loop body; here we just count the iteration.
511    pub fn step(&mut self) -> usize {
512        self.iterations += 1;
513        self.candidates.len()
514    }
515    /// Return the current invariant candidate set.
516    pub fn current_invariants(&self) -> &[String] {
517        &self.candidates
518    }
519    /// Check whether a fixpoint has been reached (placeholder heuristic).
520    pub fn is_fixpoint(&self) -> bool {
521        self.iterations > 0
522    }
523}
524/// A component library used in component-based synthesis.
525#[derive(Debug, Clone, Default)]
526pub struct ComponentLibrary {
527    /// All available components.
528    pub components: Vec<Component>,
529}
530impl ComponentLibrary {
531    /// Create an empty library.
532    pub fn new() -> Self {
533        Self::default()
534    }
535    /// Register a component.
536    pub fn register(&mut self, comp: Component) {
537        self.components.push(comp);
538    }
539    /// Find components whose output sort matches `target`.
540    pub fn candidates_for(&self, target: &str) -> Vec<&Component> {
541        self.components
542            .iter()
543            .filter(|c| c.output_matches(target))
544            .collect()
545    }
546}
547/// The result of a CEGIS verification query.
548#[derive(Debug, Clone, PartialEq, Eq)]
549pub enum VerifierResult {
550    /// The candidate program satisfies the specification for all inputs.
551    Correct,
552    /// A counterexample was found: the given input violates the spec.
553    Counterexample(Vec<String>),
554    /// Verification was inconclusive (timeout or undecidable fragment).
555    Unknown,
556}
557/// A deductive synthesis derivation tree node.
558#[derive(Debug, Clone)]
559pub struct DerivationNode {
560    /// The synthesis goal at this node: (precondition, postcondition).
561    pub goal: (String, String),
562    /// The rule applied to discharge this goal.
563    pub rule: Option<DeductiveRule>,
564    /// Child derivations.
565    pub children: Vec<DerivationNode>,
566    /// The synthesised program fragment (leaf nodes only).
567    pub program: Option<String>,
568}
569impl DerivationNode {
570    /// Create a leaf node with a concrete program.
571    pub fn leaf(
572        pre: impl Into<String>,
573        post: impl Into<String>,
574        program: impl Into<String>,
575    ) -> Self {
576        Self {
577            goal: (pre.into(), post.into()),
578            rule: None,
579            children: Vec::new(),
580            program: Some(program.into()),
581        }
582    }
583    /// Create an internal node with a rule and sub-goals.
584    pub fn internal(
585        pre: impl Into<String>,
586        post: impl Into<String>,
587        rule: DeductiveRule,
588        children: Vec<DerivationNode>,
589    ) -> Self {
590        Self {
591            goal: (pre.into(), post.into()),
592            rule: Some(rule),
593            children,
594            program: None,
595        }
596    }
597    /// Extract the full synthesised program by recursively assembling sub-trees.
598    pub fn extract_program(&self) -> String {
599        if let Some(ref p) = self.program {
600            return p.clone();
601        }
602        if let Some(ref rule) = self.rule {
603            match rule.name.as_str() {
604                "seq" => {
605                    let parts: Vec<String> =
606                        self.children.iter().map(|c| c.extract_program()).collect();
607                    parts.join("; ")
608                }
609                "if-then-else" => {
610                    let cond = self
611                        .children
612                        .first()
613                        .map(|c| c.extract_program())
614                        .unwrap_or_default();
615                    let then_b = self
616                        .children
617                        .get(1)
618                        .map(|c| c.extract_program())
619                        .unwrap_or_default();
620                    let else_b = self
621                        .children
622                        .get(2)
623                        .map(|c| c.extract_program())
624                        .unwrap_or_default();
625                    format!("if {} then {} else {}", cond, then_b, else_b)
626                }
627                _ => self
628                    .children
629                    .iter()
630                    .map(|c| c.extract_program())
631                    .collect::<Vec<_>>()
632                    .join(" "),
633            }
634        } else {
635            String::new()
636        }
637    }
638    /// Depth of the derivation tree.
639    pub fn depth(&self) -> usize {
640        if self.children.is_empty() {
641            0
642        } else {
643            1 + self.children.iter().map(|c| c.depth()).max().unwrap_or(0)
644        }
645    }
646}
647/// A program sketch: a partial program with holes to fill.
648#[derive(Debug, Clone)]
649pub struct Sketch {
650    /// The sketch source text (holes written as `??<id>`).
651    pub source: String,
652    /// The holes present in this sketch.
653    pub holes: Vec<Hole>,
654}
655impl Sketch {
656    /// Create a new sketch from source text and holes.
657    pub fn new(source: impl Into<String>, holes: Vec<Hole>) -> Self {
658        Self {
659            source: source.into(),
660            holes,
661        }
662    }
663    /// Count the number of holes.
664    pub fn num_holes(&self) -> usize {
665        self.holes.len()
666    }
667    /// Fill a hole with the given expression, returning a new sketch.
668    pub fn fill_hole(&self, hole_id: usize, expr: &str) -> Sketch {
669        let new_source = self.source.replace(&format!("??{}", hole_id), expr);
670        let new_holes = self
671            .holes
672            .iter()
673            .filter(|h| h.id != hole_id)
674            .cloned()
675            .collect();
676        Sketch::new(new_source, new_holes)
677    }
678    /// Check if the sketch is complete (no holes remaining).
679    pub fn is_complete(&self) -> bool {
680        self.holes.is_empty()
681    }
682}
683/// FOIL (First Order Inductive Learner) algorithm scaffolding.
684#[allow(dead_code)]
685pub struct FoilLearner {
686    /// Maximum clause depth.
687    pub max_depth: usize,
688    /// Minimum information gain threshold.
689    pub min_gain: f64,
690}
691#[allow(dead_code)]
692impl FoilLearner {
693    /// Create a new FOIL learner.
694    pub fn new(max_depth: usize, min_gain: f64) -> Self {
695        FoilLearner {
696            max_depth,
697            min_gain,
698        }
699    }
700    /// FOIL information gain for a literal L.
701    /// Gain(L) = t * (log2(p1/(p1+n1)) - log2(p0/(p0+n0)))
702    /// where p0/n0 are positive/negative before adding L, p1/n1 after.
703    pub fn foil_gain(&self, t: f64, p0: f64, n0: f64, p1: f64, n1: f64) -> f64 {
704        if p0 + n0 == 0.0 || p1 + n1 == 0.0 {
705            return 0.0;
706        }
707        let before = (p0 / (p0 + n0)).log2();
708        let after = (p1 / (p1 + n1)).log2();
709        t * (after - before)
710    }
711    /// Greedy covering algorithm: add clauses until all positives are covered.
712    /// Returns the number of clauses needed (simplified).
713    pub fn covering_clauses_needed(&self, num_positives: usize) -> usize {
714        let mut remaining = num_positives;
715        let mut clauses = 0;
716        while remaining > 0 {
717            remaining /= 2;
718            clauses += 1;
719            if clauses > self.max_depth {
720                break;
721            }
722        }
723        clauses
724    }
725}
726/// A recursive program structure for inductive synthesis.
727#[derive(Debug, Clone)]
728pub enum FuncProgram {
729    /// A literal (constant) value.
730    Lit(String),
731    /// A variable reference.
732    Var(String),
733    /// Lambda abstraction.
734    Lam(String, Box<FuncProgram>),
735    /// Application.
736    App(Box<FuncProgram>, Box<FuncProgram>),
737    /// Pattern match on a list.
738    ListCase {
739        scrutinee: Box<FuncProgram>,
740        nil_branch: Box<FuncProgram>,
741        cons_head: String,
742        cons_tail: String,
743        cons_branch: Box<FuncProgram>,
744    },
745    /// Recursive call (for structurally recursive programs).
746    Rec(Box<FuncProgram>),
747}
748impl FuncProgram {
749    /// Pretty-print the program.
750    pub fn pretty(&self) -> String {
751        match self {
752            FuncProgram::Lit(v) => v.clone(),
753            FuncProgram::Var(v) => v.clone(),
754            FuncProgram::Lam(x, body) => format!("fun {} -> {}", x, body.pretty()),
755            FuncProgram::App(f, a) => format!("({} {})", f.pretty(), a.pretty()),
756            FuncProgram::ListCase {
757                scrutinee,
758                nil_branch,
759                cons_head,
760                cons_tail,
761                cons_branch,
762            } => {
763                format!(
764                    "match {} with | [] -> {} | {}::{} -> {}",
765                    scrutinee.pretty(),
766                    nil_branch.pretty(),
767                    cons_head,
768                    cons_tail,
769                    cons_branch.pretty()
770                )
771            }
772            FuncProgram::Rec(p) => format!("rec({})", p.pretty()),
773        }
774    }
775    /// Count AST nodes.
776    pub fn size(&self) -> usize {
777        match self {
778            FuncProgram::Lit(_) | FuncProgram::Var(_) => 1,
779            FuncProgram::Lam(_, b) => 1 + b.size(),
780            FuncProgram::App(f, a) => 1 + f.size() + a.size(),
781            FuncProgram::ListCase {
782                scrutinee,
783                nil_branch,
784                cons_branch,
785                ..
786            } => 1 + scrutinee.size() + nil_branch.size() + cons_branch.size(),
787            FuncProgram::Rec(p) => 1 + p.size(),
788        }
789    }
790}
791/// Oracle-guided synthesis loop.
792#[derive(Debug, Clone)]
793pub struct OracleSynthLoop {
794    /// Queries made so far.
795    pub queries: Vec<Vec<String>>,
796    /// Answers received.
797    pub answers: Vec<Option<String>>,
798}
799impl OracleSynthLoop {
800    /// Create a new oracle-guided synthesis loop.
801    pub fn new() -> Self {
802        Self {
803            queries: Vec::new(),
804            answers: Vec::new(),
805        }
806    }
807    /// Make an oracle query and record the answer.
808    pub fn query(&mut self, oracle: &dyn Oracle, input: Vec<String>) -> Option<String> {
809        let ans = oracle.query(&input);
810        self.queries.push(input);
811        self.answers.push(ans.clone());
812        ans
813    }
814    /// Number of oracle queries made.
815    pub fn num_queries(&self) -> usize {
816        self.queries.len()
817    }
818}
819/// Represents a learning problem in Inductive Logic Programming.
820/// ILP learns logic programs (Horn clauses) from examples and background knowledge.
821#[allow(dead_code)]
822#[derive(Debug, Clone)]
823pub struct ILPProblem {
824    /// Positive examples (facts that should be entailed).
825    pub positive_examples: Vec<String>,
826    /// Negative examples (facts that should NOT be entailed).
827    pub negative_examples: Vec<String>,
828    /// Background knowledge (predefined facts and rules).
829    pub background: Vec<String>,
830    /// Target predicate to learn.
831    pub target: String,
832}
833#[allow(dead_code)]
834impl ILPProblem {
835    /// Create a new ILP problem.
836    pub fn new(target: &str) -> Self {
837        ILPProblem {
838            positive_examples: vec![],
839            negative_examples: vec![],
840            background: vec![],
841            target: target.to_string(),
842        }
843    }
844    /// Add a positive example.
845    pub fn add_positive(&mut self, example: &str) {
846        self.positive_examples.push(example.to_string());
847    }
848    /// Add a negative example.
849    pub fn add_negative(&mut self, example: &str) {
850        self.negative_examples.push(example.to_string());
851    }
852    /// Add background knowledge.
853    pub fn add_background(&mut self, fact: &str) {
854        self.background.push(fact.to_string());
855    }
856    /// Count the total number of training examples.
857    pub fn total_examples(&self) -> usize {
858        self.positive_examples.len() + self.negative_examples.len()
859    }
860    /// Accuracy of a candidate hypothesis on this problem.
861    /// The hypothesis is a list of clauses (strings).
862    /// Simplified: count how many positive examples are in the hypothesis "model".
863    pub fn accuracy(&self, hypothesis: &[String]) -> f64 {
864        if self.total_examples() == 0 {
865            return 1.0;
866        }
867        let _hyp_set: std::collections::HashSet<&str> =
868            hypothesis.iter().map(|s| s.as_str()).collect();
869        let covered = self
870            .positive_examples
871            .iter()
872            .filter(|e| hypothesis.iter().any(|h| h.contains(e.as_str())))
873            .count();
874        covered as f64 / self.total_examples() as f64
875    }
876}
877/// Oracle-guided inductive synthesis (OGIS): uses a teacher oracle.
878#[allow(dead_code)]
879pub struct OGISSynthesizer {
880    /// Maximum number of oracle queries.
881    pub max_queries: usize,
882    /// Counter-examples collected from oracle.
883    pub counter_examples: Vec<(Vec<i64>, i64)>,
884}
885#[allow(dead_code)]
886impl OGISSynthesizer {
887    /// Create a new OGIS synthesizer.
888    pub fn new(max_queries: usize) -> Self {
889        OGISSynthesizer {
890            max_queries,
891            counter_examples: vec![],
892        }
893    }
894    /// Add a counter-example (input, expected output) from the oracle.
895    pub fn add_counter_example(&mut self, input: Vec<i64>, output: i64) {
896        self.counter_examples.push((input, output));
897    }
898    /// Check consistency: does the current hypothesis agree with all counter-examples?
899    /// Simplified: just checks if the count of known CEs matches.
900    pub fn is_consistent(&self, _hypothesis: &str) -> bool {
901        self.counter_examples.len() <= self.max_queries
902    }
903    /// CEGIS (counterexample-guided inductive synthesis) iteration count.
904    /// Returns the number of refinements done.
905    pub fn cegis_iterations(&self) -> usize {
906        self.counter_examples.len()
907    }
908    /// Convergence criterion: no more counter-examples means synthesis succeeded.
909    pub fn has_converged(&self) -> bool {
910        self.counter_examples.is_empty() || self.counter_examples.len() >= self.max_queries
911    }
912}
913/// A production rule in a context-free grammar.
914#[derive(Debug, Clone, PartialEq, Eq)]
915pub struct Production {
916    /// Left-hand non-terminal symbol.
917    pub lhs: String,
918    /// Right-hand side symbols (terminals and non-terminals).
919    pub rhs: Vec<String>,
920}
921impl Production {
922    /// Create a new production rule.
923    pub fn new(lhs: impl Into<String>, rhs: Vec<String>) -> Self {
924        Self {
925            lhs: lhs.into(),
926            rhs,
927        }
928    }
929    /// Check whether this is an ε-production (empty right-hand side).
930    pub fn is_epsilon(&self) -> bool {
931        self.rhs.is_empty()
932    }
933}
934/// A type-directed synthesis context: typed components available.
935#[derive(Debug, Clone, Default)]
936pub struct SynthContext {
937    /// Named components with their types.
938    pub components: HashMap<String, SynthType>,
939}
940impl SynthContext {
941    /// Create an empty synthesis context.
942    pub fn new() -> Self {
943        Self::default()
944    }
945    /// Add a component with the given type.
946    pub fn add(&mut self, name: impl Into<String>, ty: SynthType) {
947        self.components.insert(name.into(), ty);
948    }
949    /// Find all components that match the goal type (syntactically).
950    pub fn matching(&self, goal: &SynthType) -> Vec<(&str, &SynthType)> {
951        self.components
952            .iter()
953            .filter(|(_, t)| *t == goal)
954            .map(|(n, t)| (n.as_str(), t))
955            .collect()
956    }
957}
958/// A simple type language for type-directed synthesis.
959#[derive(Debug, Clone, PartialEq, Eq, Hash)]
960pub enum SynthType {
961    /// Base type (e.g., "Nat", "Bool").
962    Base(String),
963    /// Function type A → B.
964    Arrow(Box<SynthType>, Box<SynthType>),
965    /// Product type A × B.
966    Product(Box<SynthType>, Box<SynthType>),
967    /// Sum type A + B.
968    Sum(Box<SynthType>, Box<SynthType>),
969    /// Type variable.
970    Var(String),
971    /// Universally quantified type ∀ α. T.
972    Forall(String, Box<SynthType>),
973    /// The unit type.
974    Unit,
975}
976impl SynthType {
977    /// Construct an arrow type.
978    pub fn arrow(a: SynthType, b: SynthType) -> Self {
979        SynthType::Arrow(Box::new(a), Box::new(b))
980    }
981    /// Construct a product type.
982    pub fn product(a: SynthType, b: SynthType) -> Self {
983        SynthType::Product(Box::new(a), Box::new(b))
984    }
985    /// Construct a sum type.
986    pub fn sum(a: SynthType, b: SynthType) -> Self {
987        SynthType::Sum(Box::new(a), Box::new(b))
988    }
989    /// Check whether `name` appears free in this type.
990    pub fn free_vars(&self) -> Vec<String> {
991        match self {
992            SynthType::Base(_) | SynthType::Unit => vec![],
993            SynthType::Var(n) => vec![n.clone()],
994            SynthType::Arrow(a, b) | SynthType::Product(a, b) | SynthType::Sum(a, b) => {
995                let mut v = a.free_vars();
996                v.extend(b.free_vars());
997                v.sort();
998                v.dedup();
999                v
1000            }
1001            SynthType::Forall(bound, body) => body
1002                .free_vars()
1003                .into_iter()
1004                .filter(|n| n != bound)
1005                .collect(),
1006        }
1007    }
1008}
1009/// A rule in a deductive synthesis calculus.
1010///
1011/// Rules transform goal triples `(pre, post, type)` into sub-goals.
1012#[derive(Debug, Clone, PartialEq, Eq)]
1013pub struct DeductiveRule {
1014    /// Name of the rule (e.g., "seq", "if-then-else", "while").
1015    pub name: String,
1016    /// Number of sub-goals this rule produces.
1017    pub arity: usize,
1018    /// Informal description.
1019    pub description: String,
1020}
1021impl DeductiveRule {
1022    /// Build a new deductive rule.
1023    pub fn new(name: impl Into<String>, arity: usize, description: impl Into<String>) -> Self {
1024        Self {
1025            name: name.into(),
1026            arity,
1027            description: description.into(),
1028        }
1029    }
1030}
1031/// A candidate program in CEGIS, represented as a string expression.
1032#[derive(Debug, Clone, PartialEq, Eq)]
1033pub struct Candidate {
1034    /// The program text.
1035    pub program: String,
1036    /// The set of inputs on which this candidate has been verified so far.
1037    pub verified_inputs: Vec<Vec<String>>,
1038}
1039impl Candidate {
1040    /// Create a new candidate from a program string.
1041    pub fn new(program: impl Into<String>) -> Self {
1042        Self {
1043            program: program.into(),
1044            verified_inputs: Vec::new(),
1045        }
1046    }
1047    /// Simulate evaluation on the given input (placeholder).
1048    pub fn evaluate(&self, _input: &[String]) -> String {
1049        "?".into()
1050    }
1051}
1052/// A library component with pre/post-conditions.
1053#[derive(Debug, Clone, PartialEq, Eq)]
1054pub struct Component {
1055    /// Name of the component (e.g., function name).
1056    pub name: String,
1057    /// Input sorts.
1058    pub input_sorts: Vec<String>,
1059    /// Output sort.
1060    pub output_sort: String,
1061    /// Formal precondition.
1062    pub precondition: String,
1063    /// Formal postcondition.
1064    pub postcondition: String,
1065}
1066impl Component {
1067    /// Build a new library component.
1068    pub fn new(
1069        name: impl Into<String>,
1070        input_sorts: Vec<String>,
1071        output_sort: impl Into<String>,
1072        precondition: impl Into<String>,
1073        postcondition: impl Into<String>,
1074    ) -> Self {
1075        Self {
1076            name: name.into(),
1077            input_sorts,
1078            output_sort: output_sort.into(),
1079            precondition: precondition.into(),
1080            postcondition: postcondition.into(),
1081        }
1082    }
1083    /// Check whether the component's output sort matches the target.
1084    pub fn output_matches(&self, target_sort: &str) -> bool {
1085        self.output_sort == target_sort
1086    }
1087}
1088/// A higher-order program template for functional synthesis.
1089#[derive(Debug, Clone, PartialEq, Eq)]
1090pub enum FuncTemplate {
1091    /// `map f xs` – apply f to each element.
1092    Map,
1093    /// `filter p xs` – keep elements satisfying p.
1094    Filter,
1095    /// `fold f init xs` – left fold.
1096    Fold,
1097    /// `unfold seed step` – anamorphism.
1098    Unfold,
1099    /// Custom combinator with a name.
1100    Custom(String),
1101}
1102impl FuncTemplate {
1103    /// Return the arity of this template.
1104    pub fn arity(&self) -> usize {
1105        match self {
1106            FuncTemplate::Map | FuncTemplate::Filter | FuncTemplate::Unfold => 2,
1107            FuncTemplate::Fold => 3,
1108            FuncTemplate::Custom(_) => 0,
1109        }
1110    }
1111}
1112/// A bottom-up enumerative synthesiser for functional programs.
1113#[derive(Debug, Clone)]
1114pub struct BottomUpSynth {
1115    /// Maximum program size (AST nodes).
1116    pub max_size: usize,
1117    /// Available variable names.
1118    pub variables: Vec<String>,
1119    /// Available literal values.
1120    pub literals: Vec<String>,
1121}
1122impl BottomUpSynth {
1123    /// Create a new bottom-up synthesiser.
1124    pub fn new(max_size: usize, variables: Vec<String>, literals: Vec<String>) -> Self {
1125        Self {
1126            max_size,
1127            variables,
1128            literals,
1129        }
1130    }
1131    /// Enumerate all programs of exactly `size` AST nodes.
1132    pub fn enumerate_size(&self, size: usize) -> Vec<FuncProgram> {
1133        if size == 1 {
1134            let mut progs: Vec<FuncProgram> = self
1135                .literals
1136                .iter()
1137                .map(|l| FuncProgram::Lit(l.clone()))
1138                .collect();
1139            progs.extend(self.variables.iter().map(|v| FuncProgram::Var(v.clone())));
1140            progs
1141        } else {
1142            let mut progs = Vec::new();
1143            for f_size in 1..size {
1144                let a_size = size - 1 - f_size;
1145                if a_size == 0 {
1146                    continue;
1147                }
1148                let f_progs = self.enumerate_size(f_size);
1149                let a_progs = self.enumerate_size(a_size);
1150                for f in &f_progs {
1151                    for a in &a_progs {
1152                        progs.push(FuncProgram::App(Box::new(f.clone()), Box::new(a.clone())));
1153                    }
1154                }
1155            }
1156            progs
1157        }
1158    }
1159}
1160/// A context-free grammar used as a syntax guide for synthesis.
1161#[derive(Debug, Clone, PartialEq, Eq)]
1162pub struct CFG {
1163    /// Start symbol.
1164    pub start: String,
1165    /// All production rules.
1166    pub productions: Vec<Production>,
1167    /// Non-terminal symbols.
1168    pub non_terminals: Vec<String>,
1169    /// Terminal symbols.
1170    pub terminals: Vec<String>,
1171}
1172impl CFG {
1173    /// Build a minimal CFG with one production from `start`.
1174    pub fn new(start: impl Into<String>) -> Self {
1175        let s = start.into();
1176        Self {
1177            start: s.clone(),
1178            productions: Vec::new(),
1179            non_terminals: vec![s],
1180            terminals: Vec::new(),
1181        }
1182    }
1183    /// Add a production rule.
1184    pub fn add_production(&mut self, lhs: impl Into<String>, rhs: Vec<String>) {
1185        let lhs_s = lhs.into();
1186        if !self.non_terminals.contains(&lhs_s) {
1187            self.non_terminals.push(lhs_s.clone());
1188        }
1189        self.productions.push(Production::new(lhs_s, rhs));
1190    }
1191    /// Add a terminal symbol.
1192    pub fn add_terminal(&mut self, t: impl Into<String>) {
1193        let t_s = t.into();
1194        if !self.terminals.contains(&t_s) {
1195            self.terminals.push(t_s);
1196        }
1197    }
1198    /// Count productions for a given non-terminal.
1199    pub fn productions_for(&self, nt: &str) -> Vec<&Production> {
1200        self.productions.iter().filter(|p| p.lhs == nt).collect()
1201    }
1202}
1203/// A superoptimiser that searches for the shortest equivalent program.
1204#[allow(dead_code)]
1205#[derive(Debug, Clone)]
1206pub struct Superoptimiser {
1207    /// Instruction set available to the target program.
1208    pub instruction_set: Vec<String>,
1209    /// Maximum program length to enumerate.
1210    pub max_length: usize,
1211    /// Number of programs tested.
1212    pub tested: usize,
1213}
1214#[allow(dead_code)]
1215impl Superoptimiser {
1216    /// Create a new superoptimiser.
1217    ///
1218    /// ```
1219    /// use oxilean_std::program_synthesis::Superoptimiser;
1220    /// let so = Superoptimiser::new(vec!["add".into(), "mul".into()], 4);
1221    /// assert_eq!(so.max_length, 4);
1222    /// ```
1223    pub fn new(instruction_set: Vec<String>, max_length: usize) -> Self {
1224        Self {
1225            instruction_set,
1226            max_length,
1227            tested: 0,
1228        }
1229    }
1230    /// Enumerate all programs of the given length.
1231    pub fn enumerate_length(&self, length: usize) -> Vec<Vec<String>> {
1232        if length == 0 {
1233            return vec![vec![]];
1234        }
1235        let mut result = Vec::new();
1236        for shorter in self.enumerate_length(length - 1) {
1237            for instr in &self.instruction_set {
1238                let mut prog = shorter.clone();
1239                prog.push(instr.clone());
1240                result.push(prog);
1241            }
1242        }
1243        result
1244    }
1245    /// Test a candidate program against a reference (placeholder).
1246    pub fn test_equivalent(&mut self, _candidate: &[String], _reference: &[String]) -> bool {
1247        self.tested += 1;
1248        false
1249    }
1250    /// Attempt to find the shortest equivalent program of length ≤ max_length.
1251    pub fn optimise(&mut self, reference: &[String]) -> Option<Vec<String>> {
1252        for length in 0..=self.max_length {
1253            for candidate in self.enumerate_length(length) {
1254                if self.test_equivalent(&candidate, reference) {
1255                    return Some(candidate);
1256                }
1257            }
1258        }
1259        None
1260    }
1261}
1262/// The FlashFill-style synthesis algorithm for string transformations.
1263#[derive(Debug, Clone)]
1264pub struct FlashFillSynth {
1265    /// Atomic string operators available.
1266    pub operators: Vec<String>,
1267}
1268impl FlashFillSynth {
1269    /// Create a FlashFill synthesiser with default string operators.
1270    pub fn new() -> Self {
1271        Self {
1272            operators: vec![
1273                "Concat".into(),
1274                "Substr".into(),
1275                "GetToken".into(),
1276                "Trim".into(),
1277                "ToUpper".into(),
1278                "ToLower".into(),
1279            ],
1280        }
1281    }
1282    /// Synthesise a string transformation program from examples.
1283    pub fn synthesise(&self, examples: &[IOExample]) -> Option<String> {
1284        if examples.is_empty() {
1285            return None;
1286        }
1287        let identity = examples
1288            .iter()
1289            .all(|ex| ex.inputs.len() == 1 && ex.inputs[0] == ex.output);
1290        if identity {
1291            Some("fun x -> x".into())
1292        } else {
1293            None
1294        }
1295    }
1296}
1297/// A SyGuS solver result.
1298#[derive(Debug, Clone, PartialEq, Eq)]
1299pub enum SyGuSResult {
1300    /// A solution expression (satisfies the grammar and the constraint).
1301    Solution(String),
1302    /// No solution exists within the grammar.
1303    Infeasible,
1304    /// Solver timed out.
1305    Timeout,
1306}
1307/// A SyGuS problem instance.
1308#[derive(Debug, Clone)]
1309pub struct SyGuSProblem {
1310    /// Name of the function to synthesise.
1311    pub function_name: String,
1312    /// Argument names and their sorts.
1313    pub arguments: Vec<(String, String)>,
1314    /// Return sort.
1315    pub return_sort: String,
1316    /// Grammar constraining syntactic form.
1317    pub grammar: CFG,
1318    /// Logical constraint (correctness spec).
1319    pub constraint: String,
1320}
1321impl SyGuSProblem {
1322    /// Build a SyGuS problem.
1323    pub fn new(
1324        function_name: impl Into<String>,
1325        arguments: Vec<(String, String)>,
1326        return_sort: impl Into<String>,
1327        grammar: CFG,
1328        constraint: impl Into<String>,
1329    ) -> Self {
1330        Self {
1331            function_name: function_name.into(),
1332            arguments,
1333            return_sort: return_sort.into(),
1334            grammar,
1335            constraint: constraint.into(),
1336        }
1337    }
1338    /// Return the arity (number of arguments).
1339    pub fn arity(&self) -> usize {
1340        self.arguments.len()
1341    }
1342}
1343/// A component-based synthesis query.
1344#[derive(Debug, Clone)]
1345pub struct ComponentSynthQuery {
1346    /// The target sort to synthesise.
1347    pub target_sort: String,
1348    /// The logical specification.
1349    pub spec: String,
1350    /// Available inputs (name, sort).
1351    pub inputs: Vec<(String, String)>,
1352}
1353impl ComponentSynthQuery {
1354    /// Build a synthesis query.
1355    pub fn new(
1356        target_sort: impl Into<String>,
1357        spec: impl Into<String>,
1358        inputs: Vec<(String, String)>,
1359    ) -> Self {
1360        Self {
1361            target_sort: target_sort.into(),
1362            spec: spec.into(),
1363            inputs,
1364        }
1365    }
1366}
1367/// Type-directed synthesiser: Djinn/Agsy-style proof search.
1368#[derive(Debug, Clone)]
1369pub struct TypeDirectedSynth {
1370    /// Depth limit for proof search.
1371    pub depth_limit: usize,
1372    /// Number of synthesis attempts made.
1373    pub attempts: usize,
1374}
1375impl TypeDirectedSynth {
1376    /// Create a new synthesiser with a depth limit.
1377    pub fn new(depth_limit: usize) -> Self {
1378        Self {
1379            depth_limit,
1380            attempts: 0,
1381        }
1382    }
1383    /// Attempt to synthesise a term of `goal_type` from `ctx`.
1384    ///
1385    /// Returns a program string on success.
1386    pub fn synthesise(
1387        &mut self,
1388        ctx: &SynthContext,
1389        goal_type: &SynthType,
1390        depth: usize,
1391    ) -> Option<String> {
1392        self.attempts += 1;
1393        if depth > self.depth_limit {
1394            return None;
1395        }
1396        let matches = ctx.matching(goal_type);
1397        if let Some((name, _)) = matches.first() {
1398            return Some(name.to_string());
1399        }
1400        match goal_type {
1401            SynthType::Unit => Some("()".into()),
1402            SynthType::Arrow(dom, cod) => {
1403                let arg_name = format!("x{}", depth);
1404                let mut new_ctx = ctx.clone();
1405                new_ctx.add(arg_name.clone(), *dom.clone());
1406                let body = self.synthesise(&new_ctx, cod, depth + 1)?;
1407                Some(format!("fun {} -> {}", arg_name, body))
1408            }
1409            SynthType::Product(a, b) => {
1410                let pa = self.synthesise(ctx, a, depth + 1)?;
1411                let pb = self.synthesise(ctx, b, depth + 1)?;
1412                Some(format!("({}, {})", pa, pb))
1413            }
1414            _ => None,
1415        }
1416    }
1417}