pbn/lib.rs
1//! Types and traits for [Programming by Navigation](https://dl.acm.org/doi/10.1145/3729264)
2//!
3//! Programming by Navigation is an interactive program synthesis problem in
4//! which a [`StepProvider`] provides a list of next [`Step`]s that a step
5//! _decider_ can choose between.
6//!
7//! To solve the Programming by Navigation Synthesis Problem, these steps are
8//! required to satisfy properties called Strong Soundness and Strong
9//! Completeness, which roughly say that all provided steps can lead to a valid
10//! solution and that all possibly-reachable valid solutions are reachable among
11//! just the provided steps (respectively). Validity is an arbitrary notion
12//! defined by a [`ValidityChecker`].
13//!
14//! # Usage
15//!
16//! The [`Controller`] struct can be used to conveniently manage a Programming
17//! by Navigation interactive session. Its API (and implementation) is a good
18//! starting point to see how all the components hook together.
19
20/// A cooperative timer used for early cutoff when synthesizing
21pub trait Timer {
22 /// The possible reasons for early cutoff (e.g., out of time, out of memory)
23 type EarlyCutoff: std::error::Error;
24
25 /// A cooperative "tick" of the timer
26 fn tick(&self) -> Result<(), Self::EarlyCutoff>;
27}
28
29/// The interface for steps (also defines the notion of expression)
30///
31/// Steps transform one expression into another and must satisfy the
32/// *navigation relation* properties in Section 3.1 of
33/// [Programming by Navigation](https://dl.acm.org/doi/10.1145/3729264).
34pub trait Step {
35 /// The notion of expressions to use for Programming by Navigation
36 type Exp: Clone;
37
38 /// Returns the result of applying a step to an expression (which may fail)
39 fn apply(&self, e: &Self::Exp) -> Option<Self::Exp>;
40}
41
42/// The interface for validity checking
43pub trait ValidityChecker {
44 /// The notion of expressions to use for Programming by Navigation
45 type Exp;
46
47 /// Returns whether or not the expression is valid
48 fn check(&mut self, e: &Self::Exp) -> bool;
49}
50
51/// The interface for step providers
52///
53/// To be a valid solution to the Programming By Navigation Synthesis Problem,
54/// step providers must satisfy the Validity, Strong Completeness, and Strong
55/// Soundness properties in Section 3.2 of
56/// [Programming by Navigation](https://dl.acm.org/doi/10.1145/3729264).
57pub trait StepProvider<T: Timer> {
58 /// The notion of steps that the step provider provides
59 type Step: Step;
60
61 /// Returns a set of provided steps given a current working expression
62 fn provide(
63 &mut self,
64 timer: &T,
65 e: &<Self::Step as Step>::Exp,
66 ) -> Result<Vec<Self::Step>, T::EarlyCutoff>;
67}
68
69/// A composition of other step providers (all provided steps are concatenated)
70pub struct CompoundProvider<T: Timer, S: Step> {
71 providers: Vec<Box<dyn StepProvider<T, Step = S>>>,
72}
73
74impl<T: Timer, S: Step> CompoundProvider<T, S> {
75 /// Creates a new [`CompoundProvider`] from a list of existing providers
76 pub fn new(providers: Vec<Box<dyn StepProvider<T, Step = S>>>) -> Self {
77 Self { providers }
78 }
79}
80
81impl<T: Timer, S: Step> StepProvider<T> for CompoundProvider<T, S> {
82 type Step = S;
83
84 fn provide(
85 &mut self,
86 timer: &T,
87 e: &<Self::Step as Step>::Exp,
88 ) -> Result<Vec<Self::Step>, T::EarlyCutoff> {
89 let mut steps = vec![];
90 for p in &mut self.providers {
91 steps.extend(p.provide(timer, e)?);
92 }
93 Ok(steps)
94 }
95}
96
97/// A provider that returns the first provided step set that is nonempty (or
98/// an empty set if there is none)
99pub struct FallbackProvider<T: Timer, S: Step> {
100 providers: Vec<Box<dyn StepProvider<T, Step = S>>>,
101}
102
103impl<T: Timer, S: Step> FallbackProvider<T, S> {
104 /// Creates a new [`FallbackProvider`] from a list of existing providers
105 pub fn new(providers: Vec<Box<dyn StepProvider<T, Step = S>>>) -> Self {
106 Self { providers }
107 }
108}
109
110impl<T: Timer, S: Step> StepProvider<T> for FallbackProvider<T, S> {
111 type Step = S;
112
113 fn provide(
114 &mut self,
115 timer: &T,
116 e: &<Self::Step as Step>::Exp,
117 ) -> Result<Vec<Self::Step>, T::EarlyCutoff> {
118 for p in &mut self.providers {
119 let steps = p.provide(timer, e)?;
120 if !steps.is_empty() {
121 return Ok(steps);
122 }
123 }
124 Ok(vec![])
125 }
126}
127
128/// A Programming by Navigation "controller" that abstracts away the underlying
129/// step provider and validity checker to manage a Programming by Navigation
130/// interactive session
131pub struct Controller<T: Timer, S: Step> {
132 timer: T,
133 provider: Box<dyn StepProvider<T, Step = S> + 'static>,
134 checker: Box<dyn ValidityChecker<Exp = S::Exp> + 'static>,
135 state: S::Exp,
136 history: Option<Vec<S::Exp>>,
137}
138
139impl<T: Timer, S: Step> Controller<T, S> {
140 /// Create a new controller (history can be saved to enable meta-level
141 /// "undo" operations in the interactive process)
142 pub fn new(
143 timer: T,
144 provider: impl StepProvider<T, Step = S> + 'static,
145 checker: impl ValidityChecker<Exp = S::Exp> + 'static,
146 start: S::Exp,
147 save_history: bool,
148 ) -> Self {
149 Self {
150 timer,
151 provider: Box::new(provider),
152 checker: Box::new(checker),
153 state: start,
154 history: if save_history { Some(vec![]) } else { None },
155 }
156 }
157
158 /// Ask the synthesizer to provide a list of possible next steps
159 pub fn provide(&mut self) -> Result<Vec<S>, T::EarlyCutoff> {
160 self.provider.provide(&self.timer, &self.state)
161 }
162
163 /// Decide which step to take (**must** be selected from among the ones that
164 /// are provided by the [`provide`] function)
165 pub fn decide(&mut self, step: S) {
166 match &mut self.history {
167 None => (),
168 Some(his) => his.push(self.state.clone()),
169 };
170 self.decide_without_history(step);
171 }
172
173 /// Decide which step to take *without* adding to the history of steps,
174 /// even if the controller was instantiated with `save_history` to `false`
175 /// (**must** be selected from among the ones that are provided by the
176 /// [`provide`] function)
177 pub fn decide_without_history(&mut self, step: S) {
178 self.state = step.apply(&self.state).unwrap();
179 }
180
181 /// Returns a reference to the current working expression
182 pub fn working_expression(&self) -> &S::Exp {
183 &self.state
184 }
185
186 /// Returns the current working expression and drops self
187 pub fn end(self) -> S::Exp {
188 self.state
189 }
190
191 /// Returns whether or not the current working expression is valid
192 pub fn valid(&mut self) -> bool {
193 self.checker.check(&self.state)
194 }
195
196 /// Returns whether or not meta-level "undo" is applicable
197 pub fn can_undo(&self) -> bool {
198 match &self.history {
199 None => false,
200 Some(xs) => !xs.is_empty(),
201 }
202 }
203
204 /// Perform a meta-level "undo" operation
205 ///
206 /// # Panics
207 ///
208 /// Panics if "undo" is not applicable (can be checked with
209 /// [`Self::can_undo`])
210 pub fn undo(&mut self) {
211 self.state = self.history.as_mut().unwrap().pop().unwrap();
212 }
213}