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