Skip to main content

ogma_libs/
module.rs

1//! Script parsing utilities
2
3use super::matcher::{FuncMatcher, Match, MatchError};
4use super::vm::{Callable, Func, Script};
5use alloc::boxed::Box;
6use alloc::vec::Vec;
7use core::marker::PhantomData;
8
9/// A list of FuncMatchers for a given context. Output of `mod_list!` macro
10pub type ModuleList<'a, C> = Box<[FuncMatcher<'a, C>]>;
11
12/// A Type which represents two types H and T
13pub struct Cons<H, T> {
14    head: PhantomData<H>,
15    tail: PhantomData<T>,
16}
17
18/// A Type which represents the empty Type
19pub struct Nil;
20
21/// Types which implement `ModuleType` can compile a line into a `Func` and multiple lines into a
22/// `Script`
23pub trait ModuleType<'a, C> {
24    type Error;
25    fn compile_line(ctx: &mut C, string: &'a str) -> Result<Func<'a>, Self::Error>;
26    fn compile(ctx: &mut C, string: &'a str) -> Result<Script<'a>, (usize, Self::Error)> {
27        let mut script = Vec::new();
28        for (line_num, line) in string
29            .lines()
30            .enumerate()
31            .map(|(i, s)| (i, s.trim()))
32            .filter(|(_, s)| !s.is_empty())
33        {
34            let func = Self::compile_line(ctx, line).map_err(|e| (line_num, e))?;
35            script.push(func);
36        }
37        Ok(script.into())
38    }
39}
40
41/// Types which implement `Module` can compile a line into a `Func` and multiple lines into a
42/// `Script` through instance methods
43pub trait Module<'a, C> {
44    type Error;
45    fn compile_line(&self, ctx: &mut C, string: &'a str) -> Result<Func<'a>, Self::Error>;
46    fn compile(&self, ctx: &mut C, string: &'a str) -> Result<Script<'a>, (usize, Self::Error)> {
47        let mut script = Vec::new();
48        for (line_num, line) in string
49            .lines()
50            .enumerate()
51            .map(|(i, s)| (i, s.trim()))
52            .filter(|(_, s)| !s.is_empty())
53        {
54            let func = self.compile_line(ctx, line).map_err(|e| (line_num, e))?;
55            script.push(func);
56        }
57        Ok(script.into())
58    }
59}
60
61impl<'a, H, T, C> ModuleType<'a, C> for Cons<H, T>
62where
63    H: 'a + Match<'a, C> + Callable,
64    T: ModuleType<'a, C, Error = MatchError>,
65    <T as ModuleType<'a, C>>::Error: Into<MatchError>,
66{
67    type Error = MatchError;
68    fn compile_line(ctx: &mut C, string: &'a str) -> Result<Box<dyn Callable + 'a>, Self::Error> {
69        match H::match_str(ctx, string) {
70            Ok(matched) => Ok(Box::new(matched)),
71            Err(_) => T::compile_line(ctx, string),
72        }
73    }
74}
75
76impl<'a, C> ModuleType<'a, C> for Nil {
77    type Error = MatchError;
78    fn compile_line(_: &mut C, _: &'a str) -> Result<Box<dyn Callable + 'a>, Self::Error> {
79        Err(MatchError::UnexpectedEof)
80    }
81}
82
83impl<'a, C, T> Module<'a, C> for T
84where
85    T: AsRef<[FuncMatcher<'a, C>]>,
86{
87    type Error = MatchError;
88    fn compile_line(&self, ctx: &mut C, string: &'a str) -> Result<Func<'a>, Self::Error> {
89        for match_str in self.as_ref().into_iter() {
90            if let Ok(func) = match_str(ctx, string) {
91                return Ok(func);
92            }
93        }
94        Err(MatchError::UnexpectedEof)
95    }
96}
97
98/// Creates a ModuleType from a list of Types
99///
100/// ```skip
101/// ogma::mod_type!(A, B, C) // => Cons<A, Cons<B, Cons<C, Nil>>>
102/// ```
103///
104/// If `A`, `B` and `C` implement `Matcher` then `mod_type!(A, B, C)` should implement `Matcher`
105#[macro_export]
106macro_rules! mod_type {
107    () => {
108        $crate::module::Nil
109    };
110    ($head:ty) => {
111        $crate::module::Cons<$head, $crate::module::Nil>
112    };
113    ($head:ty, $($tail:ty),*) => {
114        $crate::module::Cons<$head, $crate::mod_type!($($tail),*)>
115    }
116}
117
118/// Creates a Module from a list of types
119///
120/// ```skip
121/// ogma::mod_list!(Ctx => A, B, C) // => ModuleList<'a¸ Ctx>
122/// ```
123///
124/// If `A`, `B` and `C` implement `Matcher` and `Callable` then `mod_list!(Ctx => A, B, C)` should implement `Module<'a, Ctx>`
125#[cfg(feature = "std")]
126#[macro_export]
127macro_rules! mod_list {
128    () => {
129        ::std::boxed::Box::new([])
130    };
131    ($ctx:ty => $($item:ty),*) => {
132        ::std::boxed::Box::new([$(<$item as $crate::matcher::MatchFunc<$ctx>>::match_func),*])
133    }
134}
135#[cfg(not(feature = "std"))]
136#[macro_export]
137macro_rules! mod_list {
138    () => {
139        ::alloc::boxed::Box::new([])
140    };
141    ($ctx:ty => $($item:ty),*) => {
142        ::alloc::boxed::Box::new([$(<$item as $crate::matcher::MatchFunc<$ctx>>::match_func),*])
143    }
144}