synthphonia/backward/
mod.rs

1use std::{cmp::{max, min}, future::Future, usize};
2
3use crate::{debg, expr::{cfg::{Cfg, NonTerminal, ProdRule}, context::Context, Expr}, forward::executor::Executor, info, parser::problem, utils::select_all, value::{Type, Value}};
4
5
6use futures::{future::Either, select, FutureExt};
7use itertools::Itertools;
8
9use self::{liststr::ListDeducer, simple::SimpleDeducer, str::StrDeducer, int::IntDeducer};
10use derive_more::DebugCustom;
11/// Deduction for string
12pub mod str;
13
14/// Basic Deduction
15pub mod simple;
16
17pub mod int;
18
19/// Deduction for list of strings
20pub mod liststr;
21
22use derive_more::Constructor;
23#[derive(Constructor, Clone, Debug, Copy)]
24/// A struct represents a synthesis problem within the backward deduction process of the string synthesis algorithm. 
25/// This structure contains three primary fields: `nt`, which holds the non-terminal symbol represented as an index, `value`, which associates a `Value` to the synthesis task, and `used_cost`, which tracks the cost incurred during the deduction process for this particular problem instance.
26pub struct Problem {
27    pub nt: usize,
28    pub value: Value,
29    pub used_cost: usize
30}
31
32impl Problem {
33    /// Updates the associated value in a synthesis subproblem and returns the modified instance. 
34    /// This method consumes the current problem instance, replacing its stored value with the provided one, and returns the updated problem for further synthesis processing.
35    pub fn with_value(mut self, v: Value) -> Problem {
36        self.value = v;
37        self
38    }
39    /// Updates an existing synthesis subproblem instance with a new non-terminal index and associated value. 
40    /// This method takes ownership of the current instance, sets its non-terminal field to the supplied index and updates its associated value, and then returns the modified instance for chaining or further use.
41    pub fn with_nt(mut self, nt: usize, v: Value) -> Problem {
42        self.nt = nt;
43        self.value = v;
44        self
45    }
46    /// Creates a new synthesis subproblem with a specified non-terminal index and associated value, initializing the accumulated cost to zero.
47    /// 
48    /// Initializes a Problem instance intended to represent the root of a synthesis task in the backward deduction process, ensuring that the deduction cost starts at zero.
49    pub fn root(nt: usize, value: Value) -> Problem {
50        Problem { nt, value, used_cost: 0 }
51    }
52    /// Increments the accrued cost associated with a synthesis subproblem and returns the updated instance. 
53    /// 
54    /// 
55    /// This method updates the internal cost metric by adding one to it, allowing the overall synthesis process to keep track of the computational expense incurred while performing backward deduction. 
56    /// The function consumes the current instance, modifies its cost field, and returns the modified version for further processing.
57    pub fn inccost(mut self) -> Problem {
58        self.used_cost += 1;
59        self
60    }
61}
62
63/// Provides an asynchronous interface to synthesize an expression by resolving a synthesis subproblem using an executor context. 
64/// 
65/// This method takes a static reference to an executor and a synthesis problem as inputs and returns a static reference to the synthesized expression. 
66/// The interface is designed for asynchronous deduction, allowing varied deduction strategies to be implemented and executed concurrently while ensuring that each subproblem is processed efficiently.
67/// 
68pub trait Deducer {
69    async fn deduce(&'static self, exec: &'static Executor, value: Problem) -> &'static Expr;
70}
71
72#[derive(DebugCustom)]
73/// Represents different deduction strategy implementations for string synthesis problems. 
74/// 
75/// 
76/// Encapsulates three variants to handle distinct deduction approaches: one variant specializes in string-specific deduction, another provides a baseline for basic deduction, and the third addresses deduction tasks for lists of strings. 
77/// Each variant is annotated to facilitate formatted debugging output.
78pub enum DeducerEnum {
79    #[debug(fmt = "{:?}", _0)]
80    Str(StrDeducer),
81    #[debug(fmt = "{:?}", _0)]
82    Simple(SimpleDeducer),
83    #[debug(fmt = "{:?}", _0)]
84    List(ListDeducer),
85    #[debug(fmt = "{:?}", _0)]
86    Int(IntDeducer),
87}
88
89impl DeducerEnum {
90    /// Creates an instance of a deduction strategy based on the grammar configuration, context, and non-terminal index. 
91    /// 
92    /// 
93    /// Selects a deduction approach by first checking whether deduction is disabled in the configuration and then matching on the non-terminal's type. 
94    /// For string types, it initializes a strategy that fine-tunes parameters such as splitting operations, conditional (ite) concatenation, and join operations based on specific production rules; it also sets a decay rate and appends formatters retrieved from the grammar. 
95    /// For list-of-string types, it configures an alternative strategy that conditionally leverages a modified grammar when a list mapping operation is present. 
96    /// In all other cases, it falls back to a simple deduction strategy.
97    pub fn from_nt(cfg: &Cfg, ctx: &Context, nt: usize) -> Self {
98        if cfg.config.no_deduction {
99            return Self::Simple(SimpleDeducer{ nt });
100        }
101        match cfg[nt].ty {
102            crate::value::Type::Str => {
103                let mut result = StrDeducer::new(nt);
104                if let Some(ProdRule::Op2(_, n1, n2)) = cfg[nt].get_op2("str.++") {
105                    if n1 == n2 && n1 == nt {
106                        result.split_once = (ctx.len() / 3, 5);
107                        if let Some(ProdRule::Op3(_, n1, n2, n3)) = cfg[nt].get_op3("ite") {
108                            if n3 == n2 && n2 == nt{
109                                result.ite_concat = (ctx.len(), n1)
110                            }
111                        }
112                    }
113                }
114                if let Some(ProdRule::Op2(_, n1, n2)) = cfg[nt].get_op2("str.join") {
115                    if n2 == nt && (cfg[n1].get_op1("list.map").is_some() || cfg[n1].get_op1("list.filter").is_some()) {
116                        result.join = (2, n1)
117                    } 
118                }
119                if let Some(ProdRule::Op2(_, n1, n2)) = cfg[nt].get_op2("list.at") {
120                    result.index = (n1 , n2)
121                }
122                result.decay_rate = cfg[nt].config.get_usize("str.decay_rate").unwrap_or(900);
123                result.formatter.append(&mut cfg[nt].get_all_formatter());
124                info!("Deduction: {result:?}");
125                Self::Str(result)
126            }
127            crate::value::Type::ListStr => {
128                let mut result = ListDeducer { nt, map: None, filter: None};
129                if cfg[nt].get_op1("list.map").is_some() {
130                    let mut cfg2 = cfg.clone();
131                    for nt in cfg2.iter_mut() {
132                        nt.rules.retain(|x| !matches!(x, ProdRule::Var(a) if *a > 0))
133                    }
134                    info!("Map Cfg {:?}", cfg2);
135                    result.map = Some(cfg2);
136                }
137                if cfg[nt].get_op1("list.filter").is_some() {
138                    if let Some(bool_nt) = cfg.iter().position(|a| a.ty == Type::Bool) {
139                        let mut cfg2 = cfg.change_start(bool_nt);
140                        for nt in cfg2.iter_mut() {
141                            nt.rules.retain(|x| !matches!(x, ProdRule::Var(a) if *a > 0))
142                        }
143                        info!("Filter Cfg {:?}", cfg2);
144                        result.filter = Some(cfg2);
145                    }
146                }
147                Self::List(result)
148            }
149            crate::value::Type::Int => {
150                let mut result = IntDeducer{nt, len: usize::MAX};
151                if let Some(ProdRule::Op1(_, nt)) = cfg[nt].get_op1("list.len") {
152                    result.len = nt;
153                }
154                Self::Int(result)
155            }
156            _ => Self::Simple(SimpleDeducer{ nt }),
157        }
158    }
159}
160
161impl Deducer for DeducerEnum {
162    /// Asynchronously deduces an expression for a given synthesis subproblem using the appropriate deduction strategy based on the problem type. 
163    /// 
164    /// 
165    /// This method first checks if the solution for the subproblem is pending in the executor's cache. 
166    /// If it is, the method awaits and returns the pending result; otherwise, it delegates the deduction task to the underlying strategy implementation corresponding to the subproblem's type. 
167    /// After obtaining the result, it logs the solved subproblem and records the expression back into the executor's cache for future reuse.
168    async fn deduce(&'static self, exec: &'static Executor, problem: Problem) -> &'static Expr {
169        let is_pending = exec.data[problem.nt].all_eq.is_pending(problem.value);
170        if is_pending { return exec.data[problem.nt].all_eq.acquire(problem.value).await; }
171
172        let result = match self {
173            DeducerEnum::Str(a) => a.deduce(exec, problem).await,
174            DeducerEnum::Simple(a) => a.deduce(exec, problem).await,
175            DeducerEnum::List(a) => a.deduce(exec, problem).await,
176            DeducerEnum::Int(a) => a.deduce(exec, problem).await,
177        };
178        debg!("Subproblem {:?} solved", problem.value);
179        exec.data[problem.nt].add_ev(result, problem.value);
180        result
181    }
182}
183
184
185// #[derive(Clone, PartialEq, Eq, Hash)]
186// pub struct Problem {
187//     pub value: Value,
188//     pub nt: usize,
189// }
190
191// impl Problem {
192
193//     pub async fn deduce(self, exec: &'static Executor) -> &'static Expr {
194//         let (is_first, task) = exec.data[self.nt].all_eq.acquire_is_first(self.value);
195//         if !is_first {
196//             return task.await;
197//         }
198//         debg!("TASK#{} Deducing subproblem: {:?}", currect_task_id(), self.value);
199//         let subtasks = exec.dcfg.deduce(self.nt, self.clone(), exec);
200//         let e = select! {
201//             e = task.fuse() => e,
202//             e = select_all(subtasks).fuse() => {
203//                 let _ = exec.data[self.nt].all_eq.set(self.value, e.clone());
204//                 e
205//             }
206//         };
207//         debg!("TASK#{} Subproblem Solved: {:?} {:?}", currect_task_id(), self.value, e);
208//         e
209//     }
210// }
211