embedded_commands_rs/
lib.rs

1use std::borrow::Cow;
2pub use phf::{phf_map,Map};
3
4/// An enum which can contain the i64, f64, bool or Cow<'a,str> concrete types.
5#[derive(Debug, Clone)]
6pub enum Token<'a> {
7    Int(i64),
8    Float(f64),
9    Str(&'a str),
10    Bool(bool),
11}
12
13// Commands accept tokens borrowing from any input lifetime.
14pub type CommandFn<S> = for<'a> fn(&mut S, &[Token<'a>]);
15
16pub struct Interpreter<S:'static> {
17    // Reference to a compile-time perfect hash map of commands.
18    commands: &'static phf::Map<&'static str, CommandFn<S>>,
19}
20
21impl<S> Interpreter<S> {
22    /// Creates a new interpreter bound to a static command map.
23    #[inline]
24    pub const fn new(commands: &'static phf::Map<&'static str, CommandFn<S>>) -> Self {
25        Self { commands }
26    }
27
28    /// Interprets all of the commands in a string.
29    pub fn interpret<'src>(&mut self, state: &mut S, code: &'src str) {
30        for raw_line in code.lines() {
31            let mut line = raw_line.trim();
32            if line.is_empty(){
33                continue;
34            }
35            if let Some(stripped) = line.strip_suffix(';') {
36                line = stripped.trim();
37            }
38
39            let (command, args_body) = match (line.find('('), line.rfind(')')) {
40                (Some(open), Some(close)) if close > open => {
41                    (line[..open].trim(), line[open + 1..close].trim())
42                }
43                _ => {
44                    eprintln!("syntax error: expected command(args)");
45                    continue;
46                }
47            };
48
49            let Some(&func) = self.commands.get(command) else {
50                eprintln!("unknown command by the name of '{}' detected",command);
51                continue;
52            };
53
54            // Local scratch buffer borrows from `code` for this call only.
55            let mut parsed_args: Vec<Token<'src>> = Vec::new();
56            let arg_strs = split_args(args_body);
57            let mut failed = false;
58
59            
60                match parse_tokens(&arg_strs) {
61                    Ok(tokens) => parsed_args = tokens,
62                    Err(err) => {
63                        eprintln!(
64                            "parsing error for of type : {err}",
65                        );
66                        failed = true;
67                        break;
68                    }
69                }
70            
71            if failed {
72                continue;
73            }
74
75            func(state, &parsed_args);
76        }
77    }
78}
79
80// Borrowing split_args
81#[inline(always)]
82fn split_args(s: &str) -> Vec<&str> {
83    let mut out = Vec::new();
84    let mut start = 0;
85    let mut in_str = false;
86    let mut escaped = false;
87    let bytes = s.as_bytes();
88
89    for (i, &b) in bytes.iter().enumerate() {
90        match b {
91            b'"' if !escaped => in_str = !in_str,
92            b'\\' if in_str => escaped = !escaped,
93            b',' if !in_str => {
94                // trim once when pushing
95                let segment = &s[start..i];
96                let segment = segment.trim();
97                if !segment.is_empty() {
98                    out.push(segment);
99                }
100                start = i + 1;
101                escaped = false;
102            }
103            _ => escaped = false,
104        }
105    }
106
107    if start < s.len() {
108        let segment = &s[start..].trim();
109        if !segment.is_empty() {
110            out.push(segment);
111        }
112    }
113
114    out
115}
116
117// parse_token uses Cow for zero-copy when possible
118#[inline(always)]
119pub fn parse_tokens<'a>(inputs: &[&'a str]) -> Result<Vec<Token<'a>>, String> {
120    let mut out: Vec<Token<'a>> = Vec::with_capacity(inputs.len());
121
122    for (i, raw) in inputs.iter().enumerate() {
123        let s = raw.trim();
124        if s.is_empty() {
125            return Err(format!("argument {} is empty", i + 1));
126        }
127
128        // String literal (supports simple escapes)
129        if let Some(stripped) = s.strip_prefix('"').and_then(|x| x.strip_suffix('"')) {     
130                out.push(Token::Str(stripped));
131                continue;
132        }
133
134        // Booleans (ASCII case-insensitive for common forms)
135        match s {
136            "true" | "TRUE" | "True" => {
137                out.push(Token::Bool(true));
138                continue;
139            }
140            "false" | "FALSE" | "False" => {
141                out.push(Token::Bool(false));
142                continue;
143            }
144            _ => {}
145        }
146
147        // Number classification (single cheap scan)
148        let mut is_float = false;
149        for &b in s.as_bytes() {
150            if b == b'.' || b == b'e' || b == b'E' {
151                is_float = true;
152                break;
153            }
154        }
155
156        if !is_float {
157            match lexical_core::parse::<i64>(s.as_bytes()) {
158                Ok(v) => out.push(Token::Int(v)),
159                Err(_) => return Err(format!("invalid integer at argument {}: {}", i + 1, s)),
160            }
161        } else {
162            match lexical_core::parse::<f64>(s.as_bytes()) {
163                Ok(v) => out.push(Token::Float(v)),
164                Err(_) => return Err(format!("invalid float at argument {}: {}", i + 1, s)),
165            }
166        }
167    }
168
169    Ok(out)
170}
171
172
173
174