anachro_forth_core/
lib.rs

1//! # Anachro Forth (core)
2//!
3//! Anachro Forth is a forth-inspired, bytecode-compiled
4//! scripting language for Anachro Powerbus platform.
5//!
6//! ## Use Case
7//!
8//! The intended use case is to write and compile scripts on
9//! a Host PC, and to load and execute these scripts in a
10//! constrained, no_std environment, such as on embedded systems
11//! or WASM targets.
12//!
13//! ## Contents
14//!
15//! This crate contains the core components of the language,
16//! including:
17//!
18//! * **The compiler** - which converts text-based source code
19//!   into a bytecode representation. The compiler is only
20//!   compatible with "std" platforms
21//! * **The runtime** - which executes the compiled bytecode.
22//!   Additionally, the runtime has two implementations:
23//!   * The "std" runtime, which uses heap allocations for convenience
24//!   * The "no_std" runtime, which is suitable for constrained
25//!     environments, and does not require heap allocations
26//! * **The Builtins** - Which are functions available to be used from the
27//!   scripts, but are implemented in Rust
28//! * **The Wire Format** - which is used to serialize and deserialize
29//!   the compiled bytecode, allowing it to be sent or stored for execution
30//!   on another device
31//!
32//! ## Stability
33//!
34//! This project is in early, active, development. Frequent breaking changes
35//! are expected in the near future. Please [contact me](mailto:james@onevariable.com)
36//! if you would like to use Anachro Forth for your project or product
37//!
38//! ## License
39//!
40//! Licensed under either of
41//!
42//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
43//!   http://www.apache.org/licenses/LICENSE-2.0)
44//!
45//! - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
46//!
47//! at your option.
48//!
49//! ### Contribution
50//!
51//! Unless you explicitly state otherwise, any contribution intentionally submitted
52//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
53//! dual licensed as above, without any additional terms or conditions.
54
55
56#![cfg_attr(not(any(test, feature = "std")), no_std)]
57
58use core::{fmt::Write, marker::PhantomData};
59
60pub mod builtins;
61pub mod ser_de;
62
63#[cfg(any(test, feature = "std"))]
64pub mod std_rt;
65
66#[cfg(any(test, feature = "std"))]
67pub mod compiler;
68
69pub mod nostd_rt;
70
71#[derive(Debug, Clone)]
72pub enum Error {
73    /// Failed to write to the "stdout" style output
74    OutputFormat,
75
76    /// Failed to read from the "stdin" style input
77    Input,
78
79    /// Data stack underflowed
80    DataStackUnderflow,
81
82    /// Data stack was empty
83    DataStackEmpty,
84
85    /// Return stack was empty
86    RetStackEmpty,
87
88    /// Flow/Execution stack was empty
89    FlowStackEmpty,
90
91    /// Some kind of checked math failed
92    BadMath,
93
94    /// We found an "if" without an appropriate pair
95    MissingIfPair,
96
97    /// We found an "else" without an appropriate pair
98    MissingElsePair,
99
100    /// We found a "loop" without an appropriate pair
101    MissingLoopPair,
102
103    /// We found a "do" without an appropriate pair
104    MissingDoPair,
105
106    /// Something has gone *terribly* wrong
107    InternalError,
108}
109
110impl From<core::fmt::Error> for Error {
111    fn from(_other: core::fmt::Error) -> Self {
112        Self::OutputFormat
113    }
114}
115
116pub enum WhichToken<BuiltinTok, SeqTok>
117where
118    SeqTok: Clone,
119    BuiltinTok: Clone,
120{
121    Single(BuiltinTok),
122    Ref(VerbSeqInner<SeqTok>),
123}
124
125#[derive(Debug, Clone, Eq, PartialEq)]
126pub struct VerbSeqInner<SeqTok>
127where
128    SeqTok: Clone,
129{
130    pub tok: SeqTok,
131    pub idx: usize,
132}
133
134impl<SeqTok> VerbSeqInner<SeqTok>
135where
136    SeqTok: Clone,
137{
138    pub fn from_word(tok: SeqTok) -> Self {
139        Self { tok, idx: 0 }
140    }
141}
142
143#[derive(Debug, Clone)]
144pub enum RuntimeWord<BuiltinTok, SeqTok>
145where
146    SeqTok: Clone,
147    BuiltinTok: Clone,
148{
149    LiteralVal(i32),
150
151    // TODO: Blend these somehow?
152    Verb(BuiltinTok),
153    VerbSeq(VerbSeqInner<SeqTok>),
154
155    UncondRelativeJump { offset: i32 },
156    CondRelativeJump { offset: i32, jump_on: bool },
157}
158
159impl<BuiltinTok, SeqTok> RuntimeWord<BuiltinTok, SeqTok>
160where
161    SeqTok: Clone,
162    BuiltinTok: Clone,
163{
164    pub fn as_seq_inner(&mut self) -> Result<&mut VerbSeqInner<SeqTok>, Error> {
165        match self {
166            RuntimeWord::VerbSeq(ref mut seq) => Ok(seq),
167            _ => Err(Error::InternalError),
168        }
169    }
170}
171
172pub struct Runtime<BuiltinTok, SeqTok, Sdata, Sexec, O>
173where
174    Sdata: Stack<Item = i32>,
175    Sexec: ExecutionStack<BuiltinTok, SeqTok>,
176    SeqTok: Clone,
177    BuiltinTok: Clone,
178    O: Write,
179{
180    pub data_stk: Sdata,
181    pub ret_stk: Sdata,
182    pub flow_stk: Sexec,
183    pub _pd_ty_t_f: PhantomData<(BuiltinTok, SeqTok)>,
184    cur_output: O,
185}
186
187impl<Sdata, Sexec, BuiltinTok, SeqTok, O> Runtime<BuiltinTok, SeqTok, Sdata, Sexec, O>
188where
189    Sdata: Stack<Item = i32>,
190    Sexec: ExecutionStack<BuiltinTok, SeqTok>,
191    SeqTok: Clone,
192    BuiltinTok: Clone,
193    O: Write,
194{
195    pub fn step(&mut self) -> Result<StepResult<BuiltinTok, SeqTok>, Error> {
196        match self.step_inner() {
197            Ok(r) => Ok(r),
198            Err(e) => {
199                while self.flow_stk.pop().is_ok() {}
200                while self.data_stk.pop().is_ok() {}
201                while self.ret_stk.pop().is_ok() {}
202                Err(e)
203            }
204        }
205    }
206
207    fn step_inner(&mut self) -> Result<StepResult<BuiltinTok, SeqTok>, Error> {
208        let ret = 'oloop: loop {
209            // TODO: I should set a limit to the max number of loop
210            // iterations that are made here! Or maybe go back to
211            // yielding at each step
212            let cur = match self.flow_stk.last_mut() {
213                Ok(frame) => frame,
214                Err(_) => return Ok(StepResult::Done),
215            };
216
217            let mut jump = None;
218
219            let to_push = match cur {
220                RuntimeWord::LiteralVal(lit) => {
221                    self.data_stk.push(*lit);
222                    None
223                }
224                RuntimeWord::Verb(ft) => {
225                    Some(WhichToken::Single(ft.clone()))
226                }
227                RuntimeWord::VerbSeq(ref mut seq) => {
228                    // TODO: I should probably check for a difference
229                    // between exactly one over-bounds (jump to end of seq),
230                    // and overshooting (probably an engine error)
231                    let ret = Some(WhichToken::Ref(seq.clone()));
232                    seq.idx += 1;
233                    ret
234                }
235                RuntimeWord::UncondRelativeJump { offset } => {
236                    jump = Some(*offset);
237                    None
238                }
239                RuntimeWord::CondRelativeJump { offset, jump_on } => {
240                    let topvar = self.data_stk.pop()?;
241
242                    // Truth table:
243                    // tv == 0 | jump_on | jump
244                    // ========|=========|=======
245                    // false   | false   | no
246                    // true    | false   | yes
247                    // false   | true    | yes
248                    // true    | true    | no
249                    let do_jump = (topvar == 0) ^ *jump_on;
250                    if do_jump {
251                        jump = Some(*offset);
252                    }
253
254                    None
255                }
256            };
257
258            match to_push {
259                Some(WhichToken::Single(ft)) => {
260                    self.flow_stk.pop()?;
261                    break 'oloop WhichToken::Single(ft);
262                }
263                Some(WhichToken::Ref(rf)) => {
264                    break 'oloop WhichToken::Ref(rf);
265                }
266                None => {
267                    self.flow_stk.pop()?;
268                }
269            }
270
271            if let Some(jump) = jump {
272                // We just popped off the jump command, so now we are back in
273                // the "parent" frame.
274
275                let new_cur = self.flow_stk.last_mut()?.as_seq_inner()?;
276
277                if jump < 0 {
278                    let abs = jump.abs() as usize;
279
280                    assert!(abs <= new_cur.idx);
281
282                    new_cur.idx -= abs;
283                } else {
284                    let abs = jump as usize;
285                    assert_ne!(abs, 0);
286                    new_cur.idx = new_cur.idx.checked_add(abs).ok_or(Error::BadMath)?;
287                }
288            }
289        };
290
291        Ok(StepResult::Working(ret))
292    }
293
294    pub fn provide_seq_tok(
295        &mut self,
296        seq: Option<RuntimeWord<BuiltinTok, SeqTok>>,
297    ) -> Result<(), Error> {
298        if let Some(mut word) = seq {
299            if let Ok(wd) = word.as_seq_inner() {
300                assert_eq!(wd.idx, 0);
301                wd.idx = 0;
302            }
303            self.flow_stk.push(word);
304        } else {
305            self.flow_stk.pop()?;
306        }
307        Ok(())
308    }
309
310    pub fn push_exec(&mut self, mut word: RuntimeWord<BuiltinTok, SeqTok>) {
311        if let Ok(wd) = word.as_seq_inner() {
312            assert_eq!(wd.idx, 0);
313            wd.idx = 0;
314        }
315        self.flow_stk.push(word);
316    }
317}
318
319impl<Sdata, Sexec, BuiltinTok, SeqTok, O> Runtime<BuiltinTok, SeqTok, Sdata, Sexec, O>
320where
321    Sdata: Stack<Item = i32>,
322    Sexec: ExecutionStack<BuiltinTok, SeqTok>,
323    SeqTok: Clone,
324    BuiltinTok: Clone,
325    O: Write + Default,
326{
327    pub fn exchange_output(&mut self) -> O {
328        let mut new = O::default();
329        core::mem::swap(&mut new, &mut self.cur_output);
330        new
331    }
332}
333
334pub trait Stack {
335    type Item;
336
337    fn push(&mut self, data: Self::Item);
338    fn pop(&mut self) -> Result<Self::Item, Error>;
339
340    // Needed for builtins
341    fn last(&self) -> Result<&Self::Item, Error>;
342}
343
344pub trait ExecutionStack<BuiltinTok, SeqTok>
345where
346    SeqTok: Clone,
347    BuiltinTok: Clone,
348{
349    fn push(&mut self, data: RuntimeWord<BuiltinTok, SeqTok>);
350    fn pop(&mut self) -> Result<RuntimeWord<BuiltinTok, SeqTok>, Error>;
351    fn last_mut(&mut self) -> Result<&mut RuntimeWord<BuiltinTok, SeqTok>, Error>;
352}
353
354pub enum StepResult<BuiltinTok, SeqTok>
355where
356    SeqTok: Clone,
357    BuiltinTok: Clone,
358{
359    Done,
360    Working(WhichToken<BuiltinTok, SeqTok>),
361}
362
363#[cfg(test)]
364mod std_test {
365    use super::*;
366    use crate::std_rt::*;
367    use std::collections::BTreeMap;
368    use std::sync::Arc;
369
370    #[test]
371    fn foo() {
372        let mut x = new_runtime();
373
374        let mut fs_map: BTreeMap<String, StdFuncSeq> = BTreeMap::new();
375
376        // Manually craft a word, roughly:
377        // : star 42 emit ;
378        fs_map.insert(
379            "star".into(),
380            StdFuncSeq {
381                inner: Arc::new(vec![
382                    NamedStdRuntimeWord {
383                        word: RuntimeWord::LiteralVal(42),
384                        name: "42".into(),
385                    },
386                    NamedStdRuntimeWord {
387                        word: RuntimeWord::Verb(BuiltinToken::new(builtins::bi_emit)),
388                        name: "emit".into(),
389                    },
390                ]),
391            },
392        );
393
394        // Manually craft another word, roughly:
395        // : mstar star -1 if star star then ;
396        fs_map.insert(
397            "mstar".into(),
398            StdFuncSeq {
399                inner: Arc::new(vec![
400                    NamedStdRuntimeWord {
401                        word: RuntimeWord::VerbSeq(VerbSeqInner::from_word("star".to_string())),
402                        name: "star".into(),
403                    },
404                    NamedStdRuntimeWord {
405                        word: RuntimeWord::LiteralVal(-1),
406                        name: "-1".into(),
407                    },
408                    NamedStdRuntimeWord {
409                        word: RuntimeWord::CondRelativeJump {
410                            offset: 2,
411                            jump_on: false,
412                        },
413                        name: "UCRJ".into(),
414                    },
415                    NamedStdRuntimeWord {
416                        word: RuntimeWord::VerbSeq(VerbSeqInner::from_word("star".to_string())),
417                        name: "star".into(),
418                    },
419                    NamedStdRuntimeWord {
420                        word: RuntimeWord::VerbSeq(VerbSeqInner::from_word("star".to_string())),
421                        name: "star".into(),
422                    },
423                ]),
424            },
425        );
426
427        // // In the future, these words will be obtained from deserialized output,
428        // // rather than being crafted manually. I'll probably need GhostCell for
429        // // the self-referential parts
430
431        // // Push `mstar` into the execution context, basically
432        // // treating it as an "entry point"
433        x.push_exec(RuntimeWord::VerbSeq(VerbSeqInner::from_word(
434            "mstar".to_string(),
435        )));
436
437        loop {
438            match x.step() {
439                Ok(StepResult::Done) => break,
440                Ok(StepResult::Working(WhichToken::Single(ft))) => {
441                    // The runtime yields back at every call to a "builtin". Here, I
442                    // call the builtin immediately, but I could also yield further up,
443                    // to be resumed at a later time
444                    ft.exec(&mut x).unwrap();
445                }
446                Ok(StepResult::Working(WhichToken::Ref(rtw))) => {
447                    // The runtime yields back at every call to a "builtin". Here, I
448                    // call the builtin immediately, but I could also yield further up,
449                    // to be resumed at a later time
450
451                    let c = fs_map
452                        .get(&rtw.tok)
453                        .and_then(|n| n.inner.get(rtw.idx))
454                        .map(|n| n.clone().word);
455
456                    x.provide_seq_tok(c).unwrap();
457                }
458                Err(_e) => todo!(),
459            }
460        }
461
462        let output = x.exchange_output();
463
464        assert_eq!("***", &output);
465    }
466}
467
468#[cfg(test)]
469mod nostd_test {
470    use super::*;
471    use crate::nostd_rt::*;
472    use heapless::{String, Vec};
473
474    #[test]
475    fn foo() {
476        let mut deser_dict: Vec<Vec<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 8>, 8> =
477            Vec::new();
478
479        // Manually craft a word, roughly:
480        // : star 42 emit ;
481        deser_dict
482            .push({
483                let mut new: Vec<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 8> = Vec::new();
484                new.push(RuntimeWord::LiteralVal(42)).ok();
485                new.push(RuntimeWord::Verb(BuiltinToken::new(builtins::bi_emit)))
486                    .ok();
487                new
488            })
489            .ok();
490
491        // Manually craft another word, roughly:
492        // : mstar star -1 if star star then ;
493        deser_dict
494            .push({
495                let mut new: Vec<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 8> = Vec::new();
496                new.push(RuntimeWord::VerbSeq(VerbSeqInner::from_word(0)))
497                    .ok();
498                new.push(RuntimeWord::LiteralVal(-1)).ok();
499                new.push(RuntimeWord::CondRelativeJump {
500                    offset: 2,
501                    jump_on: false,
502                })
503                .ok();
504                new.push(RuntimeWord::VerbSeq(VerbSeqInner::from_word(0)))
505                    .ok();
506                new.push(RuntimeWord::VerbSeq(VerbSeqInner::from_word(0)))
507                    .ok();
508                new
509            })
510            .ok();
511
512        // Not mutable anymore
513        let idx = deser_dict;
514
515        let mut x = new_runtime::<32, 16, 256>();
516
517        // BuiltinToken<DATA_SZ, FLOW_SZ, OUTBUF_SZ>,
518        // usize,
519        // HVecStack<i32, DATA_SZ>,
520        // HVecStack<
521        //     RuntimeWord<
522        //         BuiltinToken<DATA_SZ, FLOW_SZ, OUTBUF_SZ>,
523        //         usize,
524        //     >,
525        //     FLOW_SZ,
526        // >,
527        // String<OUTBUF_SZ>,
528
529        let _sz = core::mem::size_of::<
530            Runtime<
531                BuiltinToken<32, 16, 256>,
532                usize,
533                HVecStack<i32, 32>,
534                HVecStack<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 16>,
535                String<256>,
536            >,
537        >();
538        // <32, 16, 256> -> 856 (on a 64-bit machine)
539        // assert_eq!(856, _sz);
540
541        // In the future, these words will be obtained from deserialized output,
542        // rather than being crafted manually. I'll probably need GhostCell for
543        // the self-referential parts
544
545        // Push `mstar` into the execution context, basically
546        // treating it as an "entry point"
547        x.push_exec(RuntimeWord::VerbSeq(
548            // Insert `mstar`, which is deser_dict[1]
549            VerbSeqInner { tok: 1, idx: 0 },
550        ));
551
552        loop {
553            match x.step() {
554                Ok(StepResult::Done) => {
555                    break;
556                }
557                Ok(StepResult::Working(WhichToken::Single(ft))) => {
558                    // The runtime yields back at every call to a "builtin". Here, I
559                    // call the builtin immediately, but I could also yield further up,
560                    // to be resumed at a later time
561                    ft.exec(&mut x).unwrap();
562                }
563                Ok(StepResult::Working(WhichToken::Ref(rtw))) => {
564                    // The runtime yields back at every call to a "builtin". Here, I
565                    // call the builtin immediately, but I could also yield further up,
566                    // to be resumed at a later time
567
568                    let c = idx
569                        .get(rtw.tok)
570                        .and_then(|n| n.get(rtw.idx))
571                        .map(|n| n.clone());
572
573                    x.provide_seq_tok(c).unwrap();
574                }
575                Err(_e) => todo!(),
576            }
577        }
578
579        let output = x.exchange_output();
580
581        assert_eq!("***", &output);
582    }
583}