Skip to main content

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}