Skip to main content

at_parser_rs/
parser.rs

1/***************************************************************************
2 *
3 * AT Command Parser
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20 
21use crate::context::AtContext;
22use crate::{AtError, AtResult, Args};
23
24/*
25AT Command Forms:
26- AT+CMD     (execution)
27- AT+CMD?    (query)
28- AT+CMD=?   (test)
29- AT+CMD=... (set with arguments)
30 */
31
32/// Represents the different forms an AT command can take
33enum AtForm<'a> {
34    /// Execute command without parameters (AT+CMD)
35    Exec,
36    /// Query the current state (AT+CMD?)
37    Query,
38    /// Test command availability or get valid ranges (AT+CMD=?)
39    Test,
40    /// Set command with arguments (AT+CMD=args)
41    Set(Args<'a>),
42}
43
44/// The main AT command parser
45///
46/// Generic over `T` which must implement the [`AtContext<SIZE>`](crate::context::AtContext) trait,
47/// and over the const `SIZE` which determines the response buffer size.
48///
49/// # Generic Design
50///
51/// The parser is generic over the command handler type `T` and response size `SIZE` to allow
52/// compile-time type checking when all handlers are of the same type. This provides:
53///
54/// - **Type safety**: Compile-time verification of handler types
55/// - **Zero overhead**: No dynamic dispatch when using concrete types
56/// - **Flexibility**: Can be used with trait objects (`dyn AtContext<SIZE>`) for mixed handler types
57///
58/// # Usage Patterns
59///
60/// ## With trait objects (recommended for mixed types):
61/// ```rust,no_run
62/// # use at_parser_rs::parser::AtParser;
63/// # use at_parser_rs::context::AtContext;
64/// # struct Dummy; impl AtContext<64> for Dummy {}
65/// # let mut echo_handler = Dummy; let mut reset_handler = Dummy;
66/// const SIZE: usize = 64;
67/// let mut parser: AtParser<dyn AtContext<SIZE>, SIZE> = AtParser::new();
68/// let commands: &mut [(&str, &mut dyn AtContext<SIZE>)] = &mut [
69///     ("AT+ECHO", &mut echo_handler),
70///     ("AT+RST", &mut reset_handler),
71/// ];
72/// parser.set_commands(commands);
73/// ```
74///
75/// ## With concrete types (for homogeneous handlers):
76/// ```rust,no_run
77/// # use at_parser_rs::parser::AtParser;
78/// # use at_parser_rs::context::AtContext;
79/// # struct MyHandler; impl AtContext<64> for MyHandler {}
80/// # let mut handler1 = MyHandler; let mut handler2 = MyHandler;
81/// const SIZE: usize = 64;
82/// let mut parser: AtParser<MyHandler, SIZE> = AtParser::new();
83/// let commands: &mut [(&str, &mut MyHandler)] = &mut [
84///     ("AT+CMD1", &mut handler1),
85///     ("AT+CMD2", &mut handler2),
86/// ];
87/// parser.set_commands(commands);
88/// ```
89pub struct AtParser<'a, T, const SIZE: usize>
90where
91    T: AtContext<SIZE> + ?Sized {
92    /// Array of registered commands with their name and handler
93    pub commands: &'a mut [(&'static str, &'a mut T)],
94}
95
96impl<'a, T, const SIZE: usize> AtParser<'a, T, SIZE>
97where
98    T: AtContext<SIZE> + ?Sized {
99
100    /// Create a new empty parser
101    pub const fn new() -> Self {
102        Self { commands: & mut [] }
103    }
104
105    /// Register commands that this parser will handle
106    pub fn set_commands(&mut self, commands: &'a mut [(&'static str, &'a mut T)]) {
107        self.commands = commands;
108    }
109
110    /// Parse and execute an AT command string
111    /// 
112    /// # Arguments
113    /// * `input` - The raw AT command string (e.g., "AT+CMD?")
114    /// 
115    /// # Returns
116    /// * `Ok(Bytes<SIZE>)` - Success response from the command handler
117    /// * `Err(AtError)` - Error if parsing fails or command is not found
118    pub fn execute(&mut self, input: &str) -> AtResult<SIZE> {
119        let input = input.trim();
120        let (name, form) = parse(input)?;
121
122        // Find the command handler
123        let (_, module) = self.commands
124            .iter_mut()
125            .find(|(n, _)| *n == name)
126            .ok_or(AtError::UnknownCommand)?;
127
128        // Dispatch to the appropriate handler method
129        match form {
130            AtForm::Exec => module.exec(),
131            AtForm::Query => module.query(),
132            AtForm::Test => module.test(),
133            AtForm::Set(args) => module.set(args),
134        }
135    }
136}
137
138/// Parse an AT command string into its name and form
139/// 
140/// # Arguments
141/// * `input` - The command string to parse
142/// 
143/// # Returns
144/// A tuple of (command_name, command_form)
145fn parse<'a>(input: &'a str) -> Result<(&'a str, AtForm<'a>), AtError> {
146    let input = input.trim();
147
148    // Check suffixes to determine command form
149    if let Some(cmd) = input.strip_suffix("=?") {
150        Ok((cmd, AtForm::Test))
151    } else if let Some(cmd) = input.strip_suffix('?') {
152        Ok((cmd, AtForm::Query))
153    } else if let Some((cmd, args)) = input.split_once('=') {
154        Ok((cmd, AtForm::Set(Args { raw: args })))
155    } else {
156        Ok((input, AtForm::Exec))
157    }
158}