a4_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#![cfg_attr(not(any(test, feature = "std")), no_std)]
56
57use core::{fmt::Write, marker::PhantomData};
58
59pub mod builtins;
60pub mod ser_de;
61
62#[cfg(any(test, feature = "std"))]
63pub mod std_rt;
64
65#[cfg(any(test, feature = "std"))]
66pub mod compiler;
67
68pub mod nostd_rt;
69
70#[derive(Debug, Clone)]
71pub enum Error {
72    /// Failed to write to the "stdout" style output
73    OutputFormat,
74
75    /// Failed to read from the "stdin" style input
76    Input,
77
78    /// Data stack underflowed
79    DataStackUnderflow,
80
81    /// Stack Overflow
82    StackOverflow,
83
84    /// Data stack was empty
85    DataStackEmpty,
86
87    /// Return stack was empty
88    RetStackEmpty,
89
90    /// Flow/Execution stack was empty
91    FlowStackEmpty,
92
93    /// Some kind of checked math failed
94    BadMath,
95
96    /// We found an "if" without an appropriate pair
97    MissingIfPair,
98
99    /// We found an "else" without an appropriate pair
100    MissingElsePair,
101
102    /// We found a "loop" without an appropriate pair
103    MissingLoopPair,
104
105    /// We found a "do" without an appropriate pair
106    MissingDoPair,
107
108    /// Something has gone *terribly* wrong
109    InternalError,
110}
111
112impl From<core::fmt::Error> for Error {
113    fn from(_other: core::fmt::Error) -> Self {
114        Self::OutputFormat
115    }
116}
117
118pub enum WhichToken<BuiltinTok, SeqTok>
119where
120    SeqTok: Clone,
121    BuiltinTok: Clone,
122{
123    Single(BuiltinTok),
124    Ref(VerbSeqInner<SeqTok>),
125}
126
127#[derive(Debug, Clone, Eq, PartialEq)]
128pub struct VerbSeqInner<SeqTok>
129where
130    SeqTok: Clone,
131{
132    pub tok: SeqTok,
133    pub idx: usize,
134}
135
136impl<SeqTok> VerbSeqInner<SeqTok>
137where
138    SeqTok: Clone,
139{
140    pub fn from_word(tok: SeqTok) -> Self {
141        Self { tok, idx: 0 }
142    }
143}
144
145#[derive(Debug, Clone)]
146pub enum RuntimeWord<BuiltinTok, SeqTok>
147where
148    SeqTok: Clone,
149    BuiltinTok: Clone,
150{
151    LiteralVal(i32),
152
153    // TODO: Blend these somehow?
154    Verb(BuiltinTok),
155    VerbSeq(VerbSeqInner<SeqTok>),
156
157    UncondRelativeJump { offset: i32 },
158    CondRelativeJump { offset: i32, jump_on: bool },
159}
160
161impl<BuiltinTok, SeqTok> RuntimeWord<BuiltinTok, SeqTok>
162where
163    SeqTok: Clone,
164    BuiltinTok: Clone,
165{
166    pub fn as_seq_inner(&mut self) -> Result<&mut VerbSeqInner<SeqTok>, Error> {
167        match self {
168            RuntimeWord::VerbSeq(ref mut seq) => Ok(seq),
169            _ => Err(Error::InternalError),
170        }
171    }
172}
173
174pub struct Runtime<BuiltinTok, SeqTok, Sdata, Sexec, O>
175where
176    Sdata: Stack<Item = i32>,
177    Sexec: ExecutionStack<BuiltinTok, SeqTok>,
178    SeqTok: Clone,
179    BuiltinTok: Clone,
180    O: Write,
181{
182    pub data_stk: Sdata,
183    pub ret_stk: Sdata,
184    pub flow_stk: Sexec,
185    pub _pd_ty_t_f: PhantomData<(BuiltinTok, SeqTok)>,
186    cur_output: O,
187}
188
189impl<Sdata, Sexec, BuiltinTok, SeqTok, O> Runtime<BuiltinTok, SeqTok, Sdata, Sexec, O>
190where
191    Sdata: Stack<Item = i32>,
192    Sexec: ExecutionStack<BuiltinTok, SeqTok>,
193    SeqTok: Clone,
194    BuiltinTok: Clone,
195    O: Write,
196{
197    pub fn step(&mut self) -> Result<StepResult<BuiltinTok, SeqTok>, Error> {
198        match self.step_inner() {
199            Ok(r) => Ok(r),
200            Err(e) => {
201                while self.flow_stk.pop().is_ok() {}
202                while self.data_stk.pop().is_ok() {}
203                while self.ret_stk.pop().is_ok() {}
204                Err(e)
205            }
206        }
207    }
208
209    fn step_inner(&mut self) -> Result<StepResult<BuiltinTok, SeqTok>, Error> {
210        let ret = 'oloop: loop {
211            // TODO: I should set a limit to the max number of loop
212            // iterations that are made here! Or maybe go back to
213            // yielding at each step
214            let cur = match self.flow_stk.last_mut() {
215                Ok(frame) => frame,
216                Err(_) => return Ok(StepResult::Done),
217            };
218
219            let mut jump = None;
220
221            let to_push = match cur {
222                RuntimeWord::LiteralVal(lit) => {
223                    self.data_stk.push(*lit)?;
224                    None
225                }
226                RuntimeWord::Verb(ft) => Some(WhichToken::Single(ft.clone())),
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) -> Result<(), Error>;
338    fn pop(&mut self) -> Result<Self::Item, Error>;
339    fn peek_back(&self, back: usize) -> Result<&Self::Item, Error>;
340    fn pop_back(&mut self, back: usize) -> Result<Self::Item, Error>;
341
342    // Needed for builtins
343    fn last(&self) -> Result<&Self::Item, Error>;
344}
345
346pub trait ExecutionStack<BuiltinTok, SeqTok>
347where
348    SeqTok: Clone,
349    BuiltinTok: Clone,
350{
351    fn push(&mut self, data: RuntimeWord<BuiltinTok, SeqTok>);
352    fn pop(&mut self) -> Result<RuntimeWord<BuiltinTok, SeqTok>, Error>;
353    fn last_mut(&mut self) -> Result<&mut RuntimeWord<BuiltinTok, SeqTok>, Error>;
354}
355
356pub enum StepResult<BuiltinTok, SeqTok>
357where
358    SeqTok: Clone,
359    BuiltinTok: Clone,
360{
361    Done,
362    Working(WhichToken<BuiltinTok, SeqTok>),
363}
364
365#[cfg(test)]
366mod std_test {
367    use super::*;
368    use crate::std_rt::*;
369    use std::collections::BTreeMap;
370    use std::sync::Arc;
371
372    #[test]
373    fn foo() {
374        let mut x = new_runtime();
375
376        let mut fs_map: BTreeMap<String, StdFuncSeq> = BTreeMap::new();
377
378        // Manually craft a word, roughly:
379        // : star 42 emit ;
380        fs_map.insert(
381            "star".into(),
382            StdFuncSeq {
383                inner: Arc::new(vec![
384                    NamedStdRuntimeWord {
385                        word: RuntimeWord::LiteralVal(42),
386                        name: "42".into(),
387                    },
388                    NamedStdRuntimeWord {
389                        word: RuntimeWord::Verb(BuiltinToken::new(builtins::bi_emit)),
390                        name: "emit".into(),
391                    },
392                ]),
393            },
394        );
395
396        // Manually craft another word, roughly:
397        // : mstar star -1 if star star then ;
398        fs_map.insert(
399            "mstar".into(),
400            StdFuncSeq {
401                inner: Arc::new(vec![
402                    NamedStdRuntimeWord {
403                        word: RuntimeWord::VerbSeq(VerbSeqInner::from_word("star".to_string())),
404                        name: "star".into(),
405                    },
406                    NamedStdRuntimeWord {
407                        word: RuntimeWord::LiteralVal(-1),
408                        name: "-1".into(),
409                    },
410                    NamedStdRuntimeWord {
411                        word: RuntimeWord::CondRelativeJump {
412                            offset: 2,
413                            jump_on: false,
414                        },
415                        name: "UCRJ".into(),
416                    },
417                    NamedStdRuntimeWord {
418                        word: RuntimeWord::VerbSeq(VerbSeqInner::from_word("star".to_string())),
419                        name: "star".into(),
420                    },
421                    NamedStdRuntimeWord {
422                        word: RuntimeWord::VerbSeq(VerbSeqInner::from_word("star".to_string())),
423                        name: "star".into(),
424                    },
425                ]),
426            },
427        );
428
429        // // In the future, these words will be obtained from deserialized output,
430        // // rather than being crafted manually. I'll probably need GhostCell for
431        // // the self-referential parts
432
433        // // Push `mstar` into the execution context, basically
434        // // treating it as an "entry point"
435        x.push_exec(RuntimeWord::VerbSeq(VerbSeqInner::from_word(
436            "mstar".to_string(),
437        )));
438
439        loop {
440            match x.step() {
441                Ok(StepResult::Done) => break,
442                Ok(StepResult::Working(WhichToken::Single(ft))) => {
443                    // The runtime yields back at every call to a "builtin". Here, I
444                    // call the builtin immediately, but I could also yield further up,
445                    // to be resumed at a later time
446                    ft.exec(&mut x).unwrap();
447                }
448                Ok(StepResult::Working(WhichToken::Ref(rtw))) => {
449                    // The runtime yields back at every call to a "builtin". Here, I
450                    // call the builtin immediately, but I could also yield further up,
451                    // to be resumed at a later time
452
453                    let c = fs_map
454                        .get(&rtw.tok)
455                        .and_then(|n| n.inner.get(rtw.idx))
456                        .map(|n| n.clone().word);
457
458                    x.provide_seq_tok(c).unwrap();
459                }
460                Err(_e) => todo!(),
461            }
462        }
463
464        let output = x.exchange_output();
465
466        assert_eq!("***", &output);
467    }
468}
469
470#[cfg(test)]
471mod nostd_test {
472    use super::*;
473    use crate::nostd_rt::*;
474    use heapless::{String, Vec};
475
476    #[test]
477    fn foo() {
478        let mut deser_dict: Vec<Vec<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 8>, 8> =
479            Vec::new();
480
481        // Manually craft a word, roughly:
482        // : star 42 emit ;
483        deser_dict
484            .push({
485                let mut new: Vec<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 8> = Vec::new();
486                new.push(RuntimeWord::LiteralVal(42)).ok();
487                new.push(RuntimeWord::Verb(BuiltinToken::new(builtins::bi_emit)))
488                    .ok();
489                new
490            })
491            .ok();
492
493        // Manually craft another word, roughly:
494        // : mstar star -1 if star star then ;
495        deser_dict
496            .push({
497                let mut new: Vec<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 8> = Vec::new();
498                new.push(RuntimeWord::VerbSeq(VerbSeqInner::from_word(0)))
499                    .ok();
500                new.push(RuntimeWord::LiteralVal(-1)).ok();
501                new.push(RuntimeWord::CondRelativeJump {
502                    offset: 2,
503                    jump_on: false,
504                })
505                .ok();
506                new.push(RuntimeWord::VerbSeq(VerbSeqInner::from_word(0)))
507                    .ok();
508                new.push(RuntimeWord::VerbSeq(VerbSeqInner::from_word(0)))
509                    .ok();
510                new
511            })
512            .ok();
513
514        // Not mutable anymore
515        let idx = deser_dict;
516
517        let mut x = new_runtime::<32, 16, 256>();
518
519        // BuiltinToken<DATA_SZ, FLOW_SZ, OUTBUF_SZ>,
520        // usize,
521        // HVecStack<i32, DATA_SZ>,
522        // HVecStack<
523        //     RuntimeWord<
524        //         BuiltinToken<DATA_SZ, FLOW_SZ, OUTBUF_SZ>,
525        //         usize,
526        //     >,
527        //     FLOW_SZ,
528        // >,
529        // String<OUTBUF_SZ>,
530
531        let _sz = core::mem::size_of::<
532            Runtime<
533                BuiltinToken<32, 16, 256>,
534                usize,
535                HVecStack<i32, 32>,
536                HVecStack<RuntimeWord<BuiltinToken<32, 16, 256>, usize>, 16>,
537                String<256>,
538            >,
539        >();
540        // <32, 16, 256> -> 856 (on a 64-bit machine)
541        // assert_eq!(856, _sz);
542
543        // In the future, these words will be obtained from deserialized output,
544        // rather than being crafted manually. I'll probably need GhostCell for
545        // the self-referential parts
546
547        // Push `mstar` into the execution context, basically
548        // treating it as an "entry point"
549        x.push_exec(RuntimeWord::VerbSeq(
550            // Insert `mstar`, which is deser_dict[1]
551            VerbSeqInner { tok: 1, idx: 0 },
552        ));
553
554        loop {
555            match x.step() {
556                Ok(StepResult::Done) => {
557                    break;
558                }
559                Ok(StepResult::Working(WhichToken::Single(ft))) => {
560                    // The runtime yields back at every call to a "builtin". Here, I
561                    // call the builtin immediately, but I could also yield further up,
562                    // to be resumed at a later time
563                    ft.exec(&mut x).unwrap();
564                }
565                Ok(StepResult::Working(WhichToken::Ref(rtw))) => {
566                    // The runtime yields back at every call to a "builtin". Here, I
567                    // call the builtin immediately, but I could also yield further up,
568                    // to be resumed at a later time
569
570                    let c = idx
571                        .get(rtw.tok)
572                        .and_then(|n| n.get(rtw.idx))
573                        .map(|n| n.clone());
574
575                    x.provide_seq_tok(c).unwrap();
576                }
577                Err(_e) => todo!(),
578            }
579        }
580
581        let output = x.exchange_output();
582
583        assert_eq!("***", &output);
584    }
585}