rail_lang/v1/
rail_machine.rs

1use im::{HashMap, Vector};
2use std::fmt::Display;
3use std::sync::Arc;
4
5use crate::tokens::Token;
6use crate::v1::log;
7
8#[derive(Clone)]
9pub struct RunConventions<'a> {
10    pub exe_name: &'a str,
11    pub exe_version: &'a str,
12    pub info_prefix: &'a str,
13    pub warn_prefix: &'a str,
14    pub error_prefix: &'a str,
15    pub fatal_prefix: &'a str,
16}
17
18#[derive(Clone)]
19pub enum RailError {
20    UnknownCommand(String),
21    StackUnderflow(RailState, String, Vec<RailType>),
22    TypeMismatch(Vec<RailType>, Vec<RailVal>),
23    CantEscape(Context),
24}
25
26impl std::fmt::Debug for RailError {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Self::CantEscape(ctx) => write!(
30                f,
31                "Can't escape {}. This usually means there are too many closing brackets.",
32                match ctx {
33                    Context::Main => "main context",
34                    Context::None => "contextless scope",
35                    Context::Quotation { parent_state: _ } => "quotation",
36                }
37            ),
38            Self::StackUnderflow(state, name, consumes) => write!(
39                f,
40                "Stack underflow. Stack had {} elements, but {} wanted {}",
41                state.len(),
42                name,
43                consumes.len()
44            ),
45            Self::TypeMismatch(types, values) => {
46                let values: Vec<RailType> = values.iter().map(|v| v.get_type()).collect();
47                write!(f, "Type mismatch. Wanted {:?} but had {:?}", types, values)
48            }
49            Self::UnknownCommand(cmd) => write!(f, "Unknown command: {}", cmd),
50        }
51    }
52}
53
54pub type RailRunResult = Result<RailState, (RailState, RailError)>;
55
56#[derive(Clone)]
57pub struct RailState {
58    // TODO: Provide update functions and make these private
59    pub stack: Stack,
60    pub definitions: Dictionary,
61    // TODO: Save parents at time of definition and at runtime
62    pub context: Context,
63    pub conventions: &'static RunConventions<'static>,
64}
65
66impl RailState {
67    pub fn new(
68        context: Context,
69        definitions: Dictionary,
70        conventions: &'static RunConventions,
71    ) -> RailState {
72        let stack = Stack::default();
73        RailState {
74            stack,
75            definitions,
76            context,
77            conventions,
78        }
79    }
80
81    pub fn new_main(definitions: Dictionary, conventions: &'static RunConventions) -> RailState {
82        RailState::new(Context::Main, definitions, conventions)
83    }
84
85    pub fn in_main(&self) -> bool {
86        matches!(self.context, Context::Main)
87    }
88
89    pub fn get_def(&self, name: &str) -> Option<RailDef> {
90        self.definitions.get(name).cloned()
91    }
92
93    pub fn child(&self) -> Self {
94        RailState {
95            stack: Stack::default(),
96            definitions: self.definitions.clone(),
97            context: Context::None,
98            conventions: self.conventions,
99        }
100    }
101
102    pub fn run_tokens(self, tokens: Vec<Token>) -> RailRunResult {
103        tokens.iter().fold(Ok(self), |state, term| {
104            state.and_then(|state| state.run_token(term.clone()))
105        })
106    }
107
108    pub fn run_token(self, token: Token) -> RailRunResult {
109        let res = match token {
110            Token::None => self,
111            Token::LeftBracket => self.deeper(),
112            Token::RightBracket => return self.higher(),
113            Token::String(s) => self.push_string(s),
114            Token::Boolean(b) => self.push_bool(b),
115            Token::I64(i) => self.push_i64(i),
116            Token::F64(f) => self.push_f64(f),
117            Token::DeferredTerm(term) => self.push_deferred_command(&term),
118            Token::Term(term) => match (self.clone().get_def(&term), self.in_main()) {
119                (Some(op), true) => {
120                    return op.act(self);
121                }
122                (Some(op), false) => self.push_command(&op.name),
123                (None, false) => self.push_command(&term),
124                (None, true) => {
125                    return Err((self, RailError::UnknownCommand(term.replace('\n', "\\n"))));
126                }
127            },
128        };
129
130        Ok(res)
131    }
132
133    pub fn run_val(self, value: RailVal, local_state: RailState) -> RailRunResult {
134        match value {
135            RailVal::Command(name) => {
136                let state = self.clone();
137                let cmd = state.get_def(&name).or_else(|| local_state.get_def(&name));
138
139                match cmd {
140                    None => Err((self, RailError::UnknownCommand(name))),
141                    Some(cmd) => cmd.act(self),
142                }
143            }
144            value => Ok(self.push(value)),
145        }
146    }
147
148    pub fn run_in_state(self, other_state: RailState) -> RailRunResult {
149        let values = self.stack.clone().values;
150        values
151            .into_iter()
152            .fold(Ok(other_state), |state, value| match state {
153                Ok(state) => state.run_val(value, self.child()),
154                err => err,
155            })
156    }
157
158    pub fn jailed_run_in_state(self, other_state: RailState) -> RailRunResult {
159        let jailed = |state: RailState| other_state.clone().replace_stack(state.stack);
160        self.run_in_state(other_state.clone())
161            .map(jailed)
162            .map_err(|(state, e)| (jailed(state), e))
163    }
164
165    pub fn update_stack(self, update: impl Fn(Stack) -> Stack) -> RailState {
166        RailState {
167            stack: update(self.stack),
168            definitions: self.definitions,
169            context: self.context,
170            conventions: self.conventions,
171        }
172    }
173
174    pub fn update_stack_and_defs(
175        self,
176        update: impl Fn(Stack, Dictionary) -> (Stack, Dictionary),
177    ) -> RailState {
178        let (stack, definitions) = update(self.stack, self.definitions);
179        RailState {
180            stack,
181            definitions,
182            context: self.context,
183            conventions: self.conventions,
184        }
185    }
186
187    pub fn replace_stack(self, stack: Stack) -> RailState {
188        RailState {
189            stack,
190            definitions: self.definitions,
191            context: self.context,
192            conventions: self.conventions,
193        }
194    }
195
196    pub fn replace_definitions(self, definitions: Dictionary) -> RailState {
197        RailState {
198            stack: self.stack,
199            definitions,
200            context: self.context,
201            conventions: self.conventions,
202        }
203    }
204
205    pub fn replace_context(self, context: Context) -> RailState {
206        RailState {
207            stack: self.stack,
208            definitions: self.definitions,
209            context,
210            conventions: self.conventions,
211        }
212    }
213
214    pub fn deeper(self) -> Self {
215        let conventions = self.conventions;
216        RailState {
217            stack: Stack::default(),
218            definitions: self.definitions.clone(),
219            context: Context::Quotation {
220                parent_state: Box::new(self),
221            },
222            conventions,
223        }
224    }
225
226    pub fn higher(self) -> RailRunResult {
227        match self.context.clone() {
228            Context::Quotation { parent_state } => Ok(parent_state.push_quote(self)),
229            context => Err((self, RailError::CantEscape(context))),
230        }
231    }
232
233    pub fn len(&self) -> usize {
234        self.stack.len()
235    }
236
237    pub fn is_empty(&self) -> bool {
238        self.stack.is_empty()
239    }
240
241    pub fn reverse(self) -> Self {
242        self.update_stack(|stack| stack.reverse())
243    }
244
245    pub fn push(self, term: RailVal) -> Self {
246        self.update_stack(|stack| stack.push(term.clone()))
247    }
248
249    pub fn push_bool(self, b: bool) -> Self {
250        self.push(RailVal::Boolean(b))
251    }
252
253    pub fn push_i64(self, i: i64) -> Self {
254        self.push(RailVal::I64(i))
255    }
256
257    pub fn push_f64(self, n: f64) -> Self {
258        self.push(RailVal::F64(n))
259    }
260
261    pub fn push_command(self, op_name: &str) -> Self {
262        self.push(RailVal::Command(op_name.to_owned()))
263    }
264
265    pub fn push_deferred_command(self, op_name: &str) -> Self {
266        self.push(RailVal::DeferredCommand(op_name.to_owned()))
267    }
268
269    pub fn push_quote(self, quote: RailState) -> Self {
270        self.push(RailVal::Quote(quote))
271    }
272
273    pub fn push_stab(self, st: Stab) -> Self {
274        self.push(RailVal::Stab(st))
275    }
276
277    pub fn push_string(self, s: String) -> Self {
278        self.push(RailVal::String(s))
279    }
280
281    pub fn push_str(self, s: &str) -> Self {
282        self.push(RailVal::String(s.to_owned()))
283    }
284
285    pub fn pop(self) -> (RailVal, Self) {
286        let (value, stack) = self.stack.clone().pop();
287        (value, self.replace_stack(stack))
288    }
289
290    pub fn pop_bool(self, context: &str) -> (bool, Self) {
291        let (value, quote) = self.pop();
292        match value {
293            RailVal::Boolean(b) => (b, quote),
294            _ => panic!("{}", log::type_panic_msg(context, "bool", value)),
295        }
296    }
297
298    pub fn pop_i64(self, context: &str) -> (i64, Self) {
299        let (value, quote) = self.pop();
300        match value {
301            RailVal::I64(n) => (n, quote),
302            rail_val => panic!("{}", log::type_panic_msg(context, "i64", rail_val)),
303        }
304    }
305
306    pub fn pop_f64(self, context: &str) -> (f64, Self) {
307        let (value, quote) = self.pop();
308        match value {
309            RailVal::F64(n) => (n, quote),
310            rail_val => panic!("{}", log::type_panic_msg(context, "f64", rail_val)),
311        }
312    }
313
314    fn _pop_command(self, context: &str) -> (String, Self) {
315        let (value, quote) = self.pop();
316        match value {
317            RailVal::Command(op) => (op, quote),
318            RailVal::DeferredCommand(op) => (op, quote),
319            rail_val => panic!("{}", log::type_panic_msg(context, "command", rail_val)),
320        }
321    }
322
323    pub fn pop_quote(self, context: &str) -> (RailState, Self) {
324        let (value, quote) = self.pop();
325        match value {
326            RailVal::Quote(subquote) => (subquote, quote),
327            // TODO: Can we coerce somehow?
328            // RailVal::Stab(s) => (stab_to_quote(s), quote),
329            rail_val => panic!("{}", log::type_panic_msg(context, "quote", rail_val)),
330        }
331    }
332
333    pub fn pop_stab(self, context: &str) -> (Stab, Self) {
334        let (value, quote) = self.pop();
335        match value {
336            RailVal::Stab(s) => (s, quote),
337            // TODO: Can we coerce somehow?
338            // RailVal::Quote(q) => (quote_to_stab(q.stack), quote),
339            rail_val => panic!("{}", log::type_panic_msg(context, "string", rail_val)),
340        }
341    }
342
343    pub fn pop_stab_entry(self, context: &str) -> (String, RailVal, Self) {
344        let (original_entry, quote) = self.pop_quote(context);
345        let (value, entry) = original_entry.clone().stack.pop();
346        let (key, entry) = entry.pop_string(context);
347
348        if !entry.is_empty() {
349            panic!(
350                "{}",
351                log::type_panic_msg(context, "[ string a ]", RailVal::Quote(original_entry))
352            );
353        }
354
355        (key, value, quote)
356    }
357
358    pub fn pop_string(self, context: &str) -> (String, Self) {
359        let (value, quote) = self.pop();
360        match value {
361            RailVal::String(s) => (s, quote),
362            rail_val => panic!("{}", log::type_panic_msg(context, "string", rail_val)),
363        }
364    }
365
366    pub fn enqueue(self, value: RailVal) -> Self {
367        let stack = self.stack.clone().enqueue(value);
368        self.replace_stack(stack)
369    }
370
371    pub fn dequeue(self) -> (RailVal, Self) {
372        let (value, stack) = self.stack.clone().dequeue();
373        (value, self.replace_stack(stack))
374    }
375}
376
377#[derive(Clone)]
378pub enum Context {
379    Main,
380    Quotation { parent_state: Box<RailState> },
381    None,
382}
383
384#[derive(Clone, Debug)]
385pub enum RailType {
386    A,
387    B,
388    C,
389    /// Zero or many unknown types.
390    Unknown,
391    Boolean,
392    Number,
393    I64,
394    F64,
395    Command,
396    // TODO: have quotes with typed contents
397    // Examples: Quote<String...> for split
398    //           Quote<String, Unknown> for stab entries
399    Quote,
400    QuoteOrCommand,
401    QuoteOrString,
402    String,
403    Stab,
404}
405
406impl Display for RailType {
407    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408        use RailType::*;
409        let my_type = match self {
410            A => "a",
411            B => "b",
412            C => "c",
413            Unknown => "...",
414            Boolean => "bool",
415            Number => "num",
416            I64 => "i64",
417            F64 => "f64",
418            Command => "command",
419            Quote => "quote",
420            QuoteOrCommand => "quote|command",
421            QuoteOrString => "quote|string",
422            String => "string",
423            Stab => "stab",
424        };
425
426        write!(fmt, "{}", my_type)
427    }
428}
429
430#[derive(Clone)]
431pub enum RailVal {
432    Boolean(bool),
433    // TODO: Make a "Numeric" typeclass. (And floating-point/rational numbers)
434    I64(i64),
435    F64(f64),
436    Command(String),
437    DeferredCommand(String),
438    Quote(RailState),
439    String(String),
440    Stab(Stab),
441}
442
443impl PartialEq for RailVal {
444    fn eq(&self, other: &Self) -> bool {
445        use RailVal::*;
446        match (self, other) {
447            (Boolean(a), Boolean(b)) => a == b,
448            (I64(a), I64(b)) => a == b,
449            (I64(a), F64(b)) => *a as f64 == *b,
450            (F64(a), I64(b)) => *a == *b as f64,
451            (F64(a), F64(b)) => a == b,
452            (String(a), String(b)) => a == b,
453            (Command(a), Command(b)) => a == b,
454            (DeferredCommand(a), DeferredCommand(b)) => a == b,
455            // TODO: For quotes, what about differing dictionaries? For simple lists they don't matter, for closures they do.
456            (Quote(a), Quote(b)) => a.stack == b.stack,
457            (Stab(a), Stab(b)) => a == b,
458            _ => false,
459        }
460    }
461}
462
463impl RailVal {
464    pub fn type_name(&self) -> String {
465        self.get_type().to_string()
466    }
467
468    fn get_type(&self) -> RailType {
469        match self {
470            RailVal::Boolean(_) => RailType::Boolean,
471            RailVal::I64(_) => RailType::I64,
472            RailVal::F64(_) => RailType::F64,
473            RailVal::Command(_) => RailType::Command,
474            RailVal::DeferredCommand(_) => RailType::Command,
475            RailVal::Quote(_) => RailType::Quote,
476            RailVal::String(_) => RailType::String,
477            RailVal::Stab(_) => RailType::Stab,
478        }
479    }
480
481    pub fn into_command_list(self) -> Vec<RailVal> {
482        match &self {
483            RailVal::Command(_) => vec![self],
484            RailVal::DeferredCommand(_) => vec![self],
485            RailVal::String(s) => vec![RailVal::Command(s.into())],
486            RailVal::Quote(q) => q
487                .clone()
488                .stack
489                .values
490                .into_iter()
491                .flat_map(|v| v.into_command_list())
492                .collect(),
493            _ => unimplemented!(),
494        }
495    }
496
497    pub fn into_state(self, state: &RailState) -> RailState {
498        match &self {
499            RailVal::Quote(q) => q.clone(),
500            _ => state.child().push(self),
501        }
502    }
503}
504
505impl std::fmt::Display for RailVal {
506    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
507        use RailVal::*;
508        match self {
509            Boolean(b) => write!(fmt, "{}", if *b { "true" } else { "false" }),
510            I64(n) => write!(fmt, "{}", n),
511            F64(n) => write!(fmt, "{}", n),
512            Command(cmd) => write!(fmt, "{}", cmd),
513            DeferredCommand(cmd) => write!(fmt, "\\{}", cmd),
514            Quote(q) => write!(fmt, "{}", q.stack),
515            String(s) => write!(fmt, "\"{}\"", s.replace('\n', "\\n")),
516            Stab(t) => {
517                write!(fmt, "[ ").unwrap();
518
519                for (k, v) in t.iter() {
520                    write!(fmt, "[ \"{}\" {} ] ", k, v).unwrap();
521                }
522
523                write!(fmt, "]")
524            }
525        }
526    }
527}
528
529#[derive(Clone)]
530pub struct Stack {
531    pub values: Vector<RailVal>,
532}
533
534impl PartialEq for Stack {
535    // FIXME: Not equal if inequal shadows (same name, diff binding) exist in the values
536    fn eq(&self, other: &Self) -> bool {
537        self.values
538            .clone()
539            .into_iter()
540            .zip(other.values.clone())
541            .all(|(a, b)| a == b)
542    }
543}
544
545impl Stack {
546    pub fn new(values: Vector<RailVal>) -> Self {
547        Stack { values }
548    }
549
550    pub fn of(value: RailVal) -> Self {
551        let mut values = Vector::default();
552        values.push_back(value);
553        Stack { values }
554    }
555
556    pub fn len(&self) -> usize {
557        self.values.len()
558    }
559
560    pub fn is_empty(&self) -> bool {
561        self.values.is_empty()
562    }
563
564    pub fn reverse(&self) -> Stack {
565        let values = self.values.iter().rev().cloned().collect();
566        Stack::new(values)
567    }
568
569    pub fn push(mut self, term: RailVal) -> Stack {
570        self.values.push_back(term);
571        self
572    }
573
574    pub fn pop(mut self) -> (RailVal, Stack) {
575        let term = self.values.pop_back().unwrap();
576        (term, self)
577    }
578
579    pub fn pop_bool(self, context: &str) -> (bool, Stack) {
580        let (value, quote) = self.pop();
581        match value {
582            RailVal::Boolean(b) => (b, quote),
583            _ => panic!("{}", log::type_panic_msg(context, "bool", value)),
584        }
585    }
586
587    pub fn pop_i64(self, context: &str) -> (i64, Stack) {
588        let (value, quote) = self.pop();
589        match value {
590            RailVal::I64(n) => (n, quote),
591            rail_val => panic!("{}", log::type_panic_msg(context, "i64", rail_val)),
592        }
593    }
594
595    pub fn pop_f64(self, context: &str) -> (f64, Stack) {
596        let (value, quote) = self.pop();
597        match value {
598            RailVal::F64(n) => (n, quote),
599            rail_val => panic!("{}", log::type_panic_msg(context, "f64", rail_val)),
600        }
601    }
602
603    fn _pop_command(self, context: &str) -> (String, Stack) {
604        let (value, quote) = self.pop();
605        match value {
606            RailVal::Command(op) => (op, quote),
607            RailVal::DeferredCommand(op) => (op, quote),
608            rail_val => panic!("{}", log::type_panic_msg(context, "command", rail_val)),
609        }
610    }
611
612    pub fn pop_quote(self, context: &str) -> (RailState, Stack) {
613        let (value, quote) = self.pop();
614        match value {
615            RailVal::Quote(subquote) => (subquote, quote),
616            // TODO: Can we coerce somehow?
617            // RailVal::Stab(s) => (stab_to_quote(s), quote),
618            rail_val => panic!("{}", log::type_panic_msg(context, "quote", rail_val)),
619        }
620    }
621
622    pub fn pop_stab(self, context: &str) -> (Stab, Stack) {
623        let (value, quote) = self.pop();
624        match value {
625            RailVal::Stab(s) => (s, quote),
626            // TODO: Can we coerce somehow?
627            // RailVal::Quote(q) => (quote_to_stab(q.values), quote),
628            rail_val => panic!("{}", log::type_panic_msg(context, "string", rail_val)),
629        }
630    }
631
632    pub fn pop_stab_entry(self, context: &str) -> (String, RailVal, Stack) {
633        let (original_entry, quote) = self.pop_quote(context);
634        let (value, entry) = original_entry.clone().stack.pop();
635        let (key, entry) = entry.pop_string(context);
636
637        if !entry.is_empty() {
638            panic!(
639                "{}",
640                log::type_panic_msg(context, "[ string a ]", RailVal::Quote(original_entry))
641            );
642        }
643
644        (key, value, quote)
645    }
646
647    pub fn pop_string(self, context: &str) -> (String, Stack) {
648        let (value, quote) = self.pop();
649        match value {
650            RailVal::String(s) => (s, quote),
651            rail_val => panic!("{}", log::type_panic_msg(context, "string", rail_val)),
652        }
653    }
654
655    pub fn enqueue(mut self, value: RailVal) -> Stack {
656        self.values.push_front(value);
657        self
658    }
659
660    pub fn dequeue(mut self) -> (RailVal, Stack) {
661        let value = self.values.pop_front().unwrap();
662        (value, self)
663    }
664}
665
666impl Default for Stack {
667    fn default() -> Self {
668        Self::new(Vector::default())
669    }
670}
671
672impl std::fmt::Display for Stack {
673    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
674        write!(f, "[ ").unwrap();
675
676        for term in &self.values {
677            write!(f, "{} ", term).unwrap();
678        }
679
680        write!(f, "]").unwrap();
681
682        Ok(())
683    }
684}
685
686pub type Dictionary = HashMap<String, RailDef<'static>>;
687
688pub fn dictionary_of<Entries>(entries: Entries) -> Dictionary
689where
690    Entries: IntoIterator<Item = RailDef<'static>>,
691{
692    let entries = entries.into_iter().map(|def| (def.name.clone(), def));
693    HashMap::from_iter(entries)
694}
695
696pub type Stab = HashMap<String, RailVal>;
697
698pub fn new_stab() -> Stab {
699    HashMap::new()
700}
701
702#[derive(Clone)]
703pub struct RailDef<'a> {
704    pub name: String,
705    pub description: String,
706    consumes: &'a [RailType],
707    produces: &'a [RailType],
708    action: RailAction<'a>,
709}
710
711#[derive(Clone)]
712pub enum RailAction<'a> {
713    Builtin(Arc<dyn Fn(RailState) -> RailRunResult + 'a>),
714    BuiltinSafe(Arc<dyn Fn(RailState) -> RailState + 'a>),
715    Quotation(RailState),
716}
717
718impl<'a> RailDef<'a> {
719    pub fn on_state<F>(
720        name: &str,
721        description: &str,
722        consumes: &'a [RailType],
723        produces: &'a [RailType],
724        state_action: F,
725    ) -> RailDef<'a>
726    where
727        F: Fn(RailState) -> RailRunResult + 'a,
728    {
729        RailDef {
730            name: name.to_string(),
731            description: description.to_string(),
732            consumes,
733            produces,
734            action: RailAction::Builtin(Arc::new(state_action)),
735        }
736    }
737
738    // TODO: Make this fn stop existing, or at least being so widely used. Like `on_state` but does not return RailRunResult type.
739    pub fn on_state_noerr<F>(
740        name: &str,
741        description: &str,
742        consumes: &'a [RailType],
743        produces: &'a [RailType],
744        state_action: F,
745    ) -> RailDef<'a>
746    where
747        F: Fn(RailState) -> RailState + 'a,
748    {
749        RailDef {
750            name: name.to_string(),
751            description: description.to_string(),
752            consumes,
753            produces,
754            action: RailAction::BuiltinSafe(Arc::new(state_action)),
755        }
756    }
757
758    pub fn on_jailed_state<F>(
759        name: &str,
760        description: &str,
761        consumes: &'a [RailType],
762        produces: &'a [RailType],
763        state_action: F,
764    ) -> RailDef<'a>
765    where
766        F: Fn(RailState) -> RailRunResult + 'a,
767    {
768        RailDef {
769            name: name.to_string(),
770            description: description.to_string(),
771            consumes,
772            produces,
773            action: RailAction::Builtin(Arc::new(move |state| {
774                let definitions = state.definitions.clone();
775                let substate = state_action(state)?;
776                Ok(substate.replace_definitions(definitions))
777            })),
778        }
779    }
780
781    pub fn contextless<F>(
782        name: &str,
783        description: &str,
784        consumes: &'a [RailType],
785        produces: &'a [RailType],
786        contextless_action: F,
787    ) -> RailDef<'a>
788    where
789        F: Fn() + 'a,
790    {
791        RailDef::on_state(name, description, consumes, produces, move |state| {
792            contextless_action();
793            Ok(state)
794        })
795    }
796
797    pub fn from_quote(name: &str, description: &str, quote: RailState) -> RailDef<'a> {
798        // TODO: Infer quote effects
799        RailDef {
800            name: name.to_string(),
801            description: description.to_string(),
802            consumes: &[],
803            produces: &[],
804            action: RailAction::Quotation(quote),
805        }
806    }
807
808    pub fn act(self, state: RailState) -> RailRunResult {
809        if state.stack.len() < self.consumes.len() {
810            // TODO: At some point will want source context here like line/column number.
811            return Err((
812                state.clone(),
813                RailError::StackUnderflow(state, self.name, self.consumes.to_vec()),
814            ));
815        }
816
817        // TODO: Type checks?
818
819        match self.action {
820            RailAction::Builtin(action) => action(state),
821            RailAction::BuiltinSafe(action) => Ok(action(state)),
822            RailAction::Quotation(quote) => quote.run_in_state(state),
823        }
824    }
825
826    pub fn rename<F>(self, f: F) -> RailDef<'a>
827    where
828        F: Fn(String) -> String,
829    {
830        RailDef {
831            name: f(self.name),
832            description: self.description,
833            consumes: self.consumes,
834            produces: self.produces,
835            action: self.action,
836        }
837    }
838
839    pub fn redescribe<F>(self, f: F) -> RailDef<'a>
840    where
841        F: Fn(String) -> String,
842    {
843        RailDef {
844            name: self.name,
845            description: f(self.description),
846            consumes: self.consumes,
847            produces: self.produces,
848            action: self.action,
849        }
850    }
851}