Skip to main content

cmsis_pdsc_parser/
debug_access.rs

1//! Types representing  [PDSC Debug Access](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_family_pg.html#block_DebugSyntaxRules)
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6use crate::debug_access::Statement::Comment;
7
8/// Parse error for debug access XML elements.
9#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
10pub enum DebugAccessParseError {
11    /// A required attribute or structural element was absent.
12    MissingAttribute(String),
13    /// An unrecognised statement or function name was encountered.
14    UnknownStatement(String),
15}
16
17impl Default for DebugAccessParseError {
18    fn default() -> Self {
19        Self::UnknownStatement(String::default())
20    }
21}
22
23impl From<DebugAccessParseError> for crate::Error {
24    fn from(value: DebugAccessParseError) -> Self {
25        Self::Debug(value)
26    }
27}
28
29impl std::fmt::Display for DebugAccessParseError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::MissingAttribute(msg) => write!(f, "missing attribute: {msg}"),
33            Self::UnknownStatement(name) => write!(f, "unknown statement: {name}"),
34        }
35    }
36}
37
38impl std::error::Error for DebugAccessParseError {}
39
40#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
41/// Types representing the valid [PDSC Debug Access](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_family_pg.html#block_DebugSyntaxRules)
42/// statements.
43pub enum Statement {
44    /// A sole expression, e.g. `expression;`
45    Expression(Expression),
46
47    /// A variable assignment, e.g. `variable = expression;`
48    Assignment(Assignment),
49
50    /// A variable definition, e.g. `__var variable = 0;`
51    Definition(Assignment),
52
53    /// Comment, e.g. `// This is a comment`
54    Comment(String),
55}
56
57impl Default for Statement {
58    fn default() -> Self {
59        Comment(String::default())
60    }
61}
62
63impl TryFrom<String> for Statement {
64    type Error = crate::Error;
65
66    fn try_from(value: String) -> Result<Self, Self::Error> {
67        // If present trim any whitespace
68        let input = value.trim().to_string();
69
70        // Check if it is a comment
71        if input.starts_with("//") {
72            return Ok(Self::Comment(input));
73        }
74
75        // If present remove the semicolon
76        let input = input.strip_suffix(";").unwrap_or(&input).to_string();
77
78        // Check if this is an assignment or declaration
79        let split: Option<(&str, &str)> = input.split_once('=');
80        let result: Self = match split {
81            None => {
82                // No '=', must be a standalone expression
83                let expression: Expression = input.try_into()?;
84                Self::Expression(expression)
85            }
86            Some((variable, expression)) => {
87                let variable = variable.trim();
88                let expression = expression.trim();
89                variable.strip_prefix("__var").map_or_else(
90                    || {
91                        let expression: Expression = expression.try_into()?;
92                        Ok::<Self, Self::Error>(Self::Assignment(Assignment {
93                            variable: variable.to_string(),
94                            expression,
95                        }))
96                    },
97                    |variable| {
98                        let variable = variable.trim();
99                        let expression: Expression = expression.try_into()?;
100                        Ok::<Self, Self::Error>(Self::Definition(Assignment {
101                            variable: variable.to_string(),
102                            expression,
103                        }))
104                    },
105                )?
106            }
107        };
108
109        Ok(result)
110    }
111}
112
113#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
114/// A variable assignment, e.g. `variable = expression;`
115pub struct Assignment {
116    pub variable: String,
117    pub expression: Expression,
118}
119
120#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
121/// A variable representing a [PDSC Expression](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_family_pg.html#block_ExpressionType)
122pub enum Expression {
123    /// An arithmetic, bitwise, or comparison expression, e.g. `2 + 2`, `reg & 0xFF`, `x == 1`, or a bare variable reference
124    Normal(String),
125
126    /// An expression representing an inline if statement, e.g. `(x < y) ? a : b`
127    ///
128    /// # Note
129    ///
130    /// The parser currently does not handle nested conditionals, e.g. `(x < y) ? ( (a < b) ? c : d ) : e`
131    /// I hope noone has written a PDSC file which does this, if so this can be implemented.
132    Conditional(Box<Conditional>),
133
134    /// A call to a predefined [PDSC debug access function](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#DebugFunctions),
135    /// e.g. `Read32(0x40000000)` or `Sequence("ResetAndHalt")`
136    FunctionCall(Box<DebugFunction>),
137}
138
139impl Default for Expression {
140    fn default() -> Self {
141        Self::Normal(String::default())
142    }
143}
144
145impl TryFrom<String> for Expression {
146    type Error = crate::Error;
147
148    fn try_from(value: String) -> Result<Self, Self::Error> {
149        Ok(Self::try_from(value.as_str())?)
150    }
151}
152
153impl TryFrom<&str> for Expression {
154    type Error = DebugAccessParseError;
155
156    fn try_from(value: &str) -> Result<Self, Self::Error> {
157        if let Ok(condition) = Conditional::try_from(value) {
158            return Ok(Self::Conditional(Box::new(condition)));
159        }
160
161        if let Some((name, args_str)) = detect_function_call(value) {
162            let args: Vec<Self> = split_args(args_str)
163                .into_iter()
164                .map(Self::try_from)
165                .collect::<Result<Vec<_>, _>>()?;
166            let func = DebugFunction::try_from((name.to_string(), args))?;
167
168            return Ok(Self::FunctionCall(Box::new(func)));
169        }
170
171        Ok(Self::Normal(value.to_string()))
172    }
173}
174
175impl fmt::Display for Expression {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match self {
178            Self::Normal(value) => f.write_str(value),
179            Self::Conditional(condition) => condition.fmt(f),
180            Self::FunctionCall(function) => function.fmt(f),
181        }
182    }
183}
184
185#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
186/// An expression representing an inline if statement, e.g. `(x < y) ? a : b`
187///
188/// # Note
189///
190/// The parser currently does not handle nested conditionals, e.g. `(x < y) ? ( (a < b) ? c : d ) : e`
191/// I hope noone has written a PDSC file which does this, if so this can be implemented.
192pub struct Conditional {
193    /// The conditional part, `(x < y) ? a : b -> x < y`
194    pub condition: Expression,
195    /// The value when the conditional evaluates to true, `(x < y) ? a : b -> a`
196    pub true_value: Expression,
197    /// The value when the conditional evaluates to false, `(x < y) ? a : b -> b`
198    pub false_value: Expression,
199}
200
201impl TryFrom<String> for Conditional {
202    type Error = DebugAccessParseError;
203
204    /// Performs the conversion between [String] and [Conditional]
205    ///
206    /// # Note
207    ///
208    /// The parser currently does not handle nested conditionals, e.g. `(x < y) ? ( (a < b) ? c : d ) : e`
209    /// I hope noone has written a PDSC file which does this, if so this can be implemented.
210    /// This will return a valid type with a garbage value.
211    fn try_from(value: String) -> Result<Self, Self::Error> {
212        Self::try_from(value.as_str())
213    }
214}
215
216impl TryFrom<&str> for Conditional {
217    type Error = DebugAccessParseError;
218
219    /// Performs the conversion between [&str] and [Conditional]
220    ///
221    /// # Note
222    ///
223    /// The parser currently does not handle nested conditionals, e.g. `(x < y) ? ( (a < b) ? c : d ) : e`
224    /// I hope noone has written a PDSC file which does this, if so this can be implemented.
225    /// This will return a valid type with a garbage value.
226    fn try_from(value: &str) -> Result<Self, Self::Error> {
227        // Create the sates for the state machine
228        #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
229        enum WalkerProgress {
230            #[default]
231            None,
232            ParenOpen,
233            ParenClose,
234            Question,
235            Colon,
236        }
237
238        // Variables to store the result
239        let mut condition_str: String = String::new();
240        let mut truthy_str: String = String::new();
241        let mut falsey_str: String = String::new();
242
243        // Use a state machine to walk the string
244        let mut progress = WalkerProgress::None;
245        for c in value.chars() {
246            match progress {
247                WalkerProgress::None => {
248                    if c == '(' {
249                        progress = WalkerProgress::ParenOpen;
250                    }
251                }
252                WalkerProgress::ParenOpen => {
253                    if c == ')' {
254                        progress = WalkerProgress::ParenClose;
255                    } else {
256                        condition_str.push(c);
257                    }
258                }
259                WalkerProgress::ParenClose => {
260                    if c == '?' {
261                        progress = WalkerProgress::Question;
262                    }
263                }
264                WalkerProgress::Question => {
265                    if c == ':' {
266                        progress = WalkerProgress::Colon;
267                    } else {
268                        truthy_str.push(c);
269                    }
270                }
271                WalkerProgress::Colon => {
272                    if c == ';' {
273                        break;
274                    }
275                    falsey_str.push(c);
276                }
277            }
278        }
279
280        let walk_ok = progress == WalkerProgress::Colon && !falsey_str.is_empty();
281
282        if walk_ok {
283            let condition: Expression = condition_str.trim().try_into()?;
284            let true_value: Expression = truthy_str.trim().try_into()?;
285            let false_value: Expression = falsey_str.trim().try_into()?;
286
287            Ok(Self {
288                condition,
289                true_value,
290                false_value,
291            })
292        } else {
293            Err(DebugAccessParseError::MissingAttribute(
294                "conditional syntax: expected '(condition) ? truthy : falsy'".to_string(),
295            ))
296        }
297    }
298}
299
300impl fmt::Display for Conditional {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        write!(
303            f,
304            "({}) ? {} : {}",
305            self.condition, self.true_value, self.false_value
306        )
307    }
308}
309
310#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
311/// A predefined [PDSC debug access function](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#DebugFunctions).
312///
313/// Unknown function names are a parse error — if the spec adds new functions they will surface as panics.
314pub enum DebugFunction {
315    // Memory access
316    /// Read 8-bit value from target memory
317    Read8 { addr: Expression },
318    /// Read 16-bit value from target memory
319    Read16 { addr: Expression },
320    /// Read 32-bit value from target memory
321    Read32 { addr: Expression },
322    /// Read 64-bit value from target memory
323    Read64 { addr: Expression },
324    /// Write 8-bit value to target memory
325    Write8 { addr: Expression, val: Expression },
326    /// Write 16-bit value to target memory
327    Write16 { addr: Expression, val: Expression },
328    /// Write 32-bit value to target memory
329    Write32 { addr: Expression, val: Expression },
330    /// Write 64-bit value to target memory
331    Write64 { addr: Expression, val: Expression },
332
333    // Register access
334    /// Read access port register
335    ReadAP { addr: Expression },
336    /// Write access port register
337    WriteAP { addr: Expression, val: Expression },
338    /// Read debug port register
339    ReadDP { addr: Expression },
340    /// Write debug port register
341    WriteDP { addr: Expression, val: Expression },
342    /// APv2/ADIv6 access port read
343    ReadAccessAP { addr: Expression },
344    /// APv2/ADIv6 access port write
345    WriteAccessAP { addr: Expression, val: Expression },
346
347    // Debug port / probe
348    /// Wait for a specific delay (microseconds)
349    DapDelay { delay: Expression },
350    /// Write abort request to CoreSight register
351    DapWriteAbort { value: Expression },
352    /// Monitor and control debugger I/O pins
353    DapSwjPins {
354        pinout: Expression,
355        pinselect: Expression,
356        pinwait: Expression,
357    },
358    /// Set JTAG/SWD clock frequency (Hz)
359    DapSwjClock { val: Expression },
360    /// Generate SWJ sequences
361    DapSwjSequence { cnt: Expression, val: Expression },
362    /// Generate JTAG sequences
363    DapJtagSequence {
364        cnt: Expression,
365        tms: Expression,
366        tdi: Expression,
367    },
368
369    // Sequence control
370    /// Execute a debug access sequence by name
371    Sequence { name: Expression },
372    /// Prompt user for confirmation or selection
373    Query {
374        query_type: Expression,
375        message: Expression,
376        default: Expression,
377    },
378    /// Query an input value from the user
379    QueryValue {
380        message: Expression,
381        default: Expression,
382    },
383    /// Output a formatted message to the debug log (variadic: `msg_type`, `format`, then optional extra args)
384    Message {
385        msg_type: Expression,
386        format: Expression,
387        args: Vec<Expression>,
388    },
389
390    // Flash operations
391    /// Write flash buffer contents into target memory
392    FlashWriteBuffer {
393        addr: Expression,
394        offs: Expression,
395        len: Expression,
396        mode: Expression,
397    },
398    /// Select FLM flash algorithm for operations
399    FlashLoadAlgorithm {
400        algo_path: Expression,
401        ram_start: Expression,
402        ram_size: Expression,
403    },
404
405    // Buffer management
406    /// Fill buffer with a value pattern
407    BufferSet {
408        buff_id: Expression,
409        buff_offset: Expression,
410        count: Expression,
411        size: Expression,
412        value: Expression,
413    },
414    /// Retrieve an item from a buffer
415    BufferGet {
416        buff_id: Expression,
417        buff_offset: Expression,
418        size: Expression,
419    },
420    /// Get current buffer size in bytes
421    BufferSize { buff_id: Expression },
422    /// Read target data into a buffer
423    BufferRead {
424        buff_id: Expression,
425        buff_offset: Expression,
426        addr: Expression,
427        length: Expression,
428        mode: Expression,
429    },
430    /// Transfer buffer data to target
431    BufferWrite {
432        buff_id: Expression,
433        buff_offset: Expression,
434        addr: Expression,
435        length: Expression,
436        mode: Expression,
437    },
438
439    // External tool integration
440    /// Stream data from an external source into a buffer
441    BufferStreamIn {
442        buff_id: Expression,
443        buff_offset: Expression,
444        length: Expression,
445        path: Expression,
446        mode: Expression,
447        timeout: Expression,
448    },
449    /// Transfer buffer data to an external sink
450    BufferStreamOut {
451        buff_id: Expression,
452        buff_offset: Expression,
453        length: Expression,
454        dest_path: Expression,
455        dest_mode: Expression,
456        timeout: Expression,
457    },
458    /// Execute an external application
459    RunApplication {
460        app_path: Expression,
461        arguments: Expression,
462        work_directory: Expression,
463        timeout: Expression,
464    },
465    /// Run a Python script on the host system
466    RunPythonScript {
467        script_path: Expression,
468        arguments: Expression,
469        work_directory: Expression,
470        timeout: Expression,
471    },
472    /// Check if a path exists on the host filesystem
473    FilePathExists {
474        path: Expression,
475        timeout: Expression,
476    },
477    /// Load DWARF debug information
478    LoadDebugInfo { file: Expression },
479}
480
481fn fmt_debug_function(f: &mut fmt::Formatter<'_>, name: &str, args: &[&Expression]) -> fmt::Result {
482    write!(f, "{name}(")?;
483    for (index, arg) in args.iter().enumerate() {
484        if index != 0 {
485            f.write_str(", ")?;
486        }
487        fmt::Display::fmt(*arg, f)?;
488    }
489    f.write_str(")")
490}
491
492impl fmt::Display for DebugFunction {
493    #[allow(clippy::too_many_lines)]
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        match self {
496            Self::Read8 { addr } => fmt_debug_function(f, "Read8", &[addr]),
497            Self::Read16 { addr } => fmt_debug_function(f, "Read16", &[addr]),
498            Self::Read32 { addr } => fmt_debug_function(f, "Read32", &[addr]),
499            Self::Read64 { addr } => fmt_debug_function(f, "Read64", &[addr]),
500            Self::Write8 { addr, val } => fmt_debug_function(f, "Write8", &[addr, val]),
501            Self::Write16 { addr, val } => fmt_debug_function(f, "Write16", &[addr, val]),
502            Self::Write32 { addr, val } => fmt_debug_function(f, "Write32", &[addr, val]),
503            Self::Write64 { addr, val } => fmt_debug_function(f, "Write64", &[addr, val]),
504            Self::ReadAP { addr } => fmt_debug_function(f, "ReadAP", &[addr]),
505            Self::WriteAP { addr, val } => fmt_debug_function(f, "WriteAP", &[addr, val]),
506            Self::ReadDP { addr } => fmt_debug_function(f, "ReadDP", &[addr]),
507            Self::WriteDP { addr, val } => fmt_debug_function(f, "WriteDP", &[addr, val]),
508            Self::ReadAccessAP { addr } => fmt_debug_function(f, "ReadAccessAP", &[addr]),
509            Self::WriteAccessAP { addr, val } => {
510                fmt_debug_function(f, "WriteAccessAP", &[addr, val])
511            }
512            Self::DapDelay { delay } => fmt_debug_function(f, "DAP_Delay", &[delay]),
513            Self::DapWriteAbort { value } => fmt_debug_function(f, "DAP_WriteABORT", &[value]),
514            Self::DapSwjPins {
515                pinout,
516                pinselect,
517                pinwait,
518            } => fmt_debug_function(f, "DAP_SWJ_Pins", &[pinout, pinselect, pinwait]),
519            Self::DapSwjClock { val } => fmt_debug_function(f, "DAP_SWJ_Clock", &[val]),
520            Self::DapSwjSequence { cnt, val } => {
521                fmt_debug_function(f, "DAP_SWJ_Sequence", &[cnt, val])
522            }
523            Self::DapJtagSequence { cnt, tms, tdi } => {
524                fmt_debug_function(f, "DAP_JTAG_Sequence", &[cnt, tms, tdi])
525            }
526            Self::Sequence { name } => fmt_debug_function(f, "Sequence", &[name]),
527            Self::Query {
528                query_type,
529                message,
530                default,
531            } => fmt_debug_function(f, "Query", &[query_type, message, default]),
532            Self::QueryValue { message, default } => {
533                fmt_debug_function(f, "QueryValue", &[message, default])
534            }
535            Self::Message {
536                msg_type,
537                format,
538                args,
539            } => {
540                let mut all_args = Vec::with_capacity(args.len().saturating_add(2));
541                all_args.push(msg_type);
542                all_args.push(format);
543                all_args.extend(args);
544                fmt_debug_function(f, "Message", &all_args)
545            }
546            Self::FlashWriteBuffer {
547                addr,
548                offs,
549                len,
550                mode,
551            } => fmt_debug_function(f, "FlashWriteBuffer", &[addr, offs, len, mode]),
552            Self::FlashLoadAlgorithm {
553                algo_path,
554                ram_start,
555                ram_size,
556            } => fmt_debug_function(f, "FlashLoadAlgorithm", &[algo_path, ram_start, ram_size]),
557            Self::BufferSet {
558                buff_id,
559                buff_offset,
560                count,
561                size,
562                value,
563            } => fmt_debug_function(f, "BufferSet", &[buff_id, buff_offset, count, size, value]),
564            Self::BufferGet {
565                buff_id,
566                buff_offset,
567                size,
568            } => fmt_debug_function(f, "BufferGet", &[buff_id, buff_offset, size]),
569            Self::BufferSize { buff_id } => fmt_debug_function(f, "BufferSize", &[buff_id]),
570            Self::BufferRead {
571                buff_id,
572                buff_offset,
573                addr,
574                length,
575                mode,
576            } => fmt_debug_function(f, "BufferRead", &[buff_id, buff_offset, addr, length, mode]),
577            Self::BufferWrite {
578                buff_id,
579                buff_offset,
580                addr,
581                length,
582                mode,
583            } => fmt_debug_function(
584                f,
585                "BufferWrite",
586                &[buff_id, buff_offset, addr, length, mode],
587            ),
588            Self::BufferStreamIn {
589                buff_id,
590                buff_offset,
591                length,
592                path,
593                mode,
594                timeout,
595            } => fmt_debug_function(
596                f,
597                "BufferStreamIn",
598                &[buff_id, buff_offset, length, path, mode, timeout],
599            ),
600            Self::BufferStreamOut {
601                buff_id,
602                buff_offset,
603                length,
604                dest_path,
605                dest_mode,
606                timeout,
607            } => fmt_debug_function(
608                f,
609                "BufferStreamOut",
610                &[buff_id, buff_offset, length, dest_path, dest_mode, timeout],
611            ),
612            Self::RunApplication {
613                app_path,
614                arguments,
615                work_directory,
616                timeout,
617            } => fmt_debug_function(
618                f,
619                "RunApplication",
620                &[app_path, arguments, work_directory, timeout],
621            ),
622            Self::RunPythonScript {
623                script_path,
624                arguments,
625                work_directory,
626                timeout,
627            } => fmt_debug_function(
628                f,
629                "RunPythonScript",
630                &[script_path, arguments, work_directory, timeout],
631            ),
632            Self::FilePathExists { path, timeout } => {
633                fmt_debug_function(f, "FilePathExists", &[path, timeout])
634            }
635            Self::LoadDebugInfo { file } => fmt_debug_function(f, "LoadDebugInfo", &[file]),
636        }
637    }
638}
639
640impl Default for DebugFunction {
641    fn default() -> Self {
642        Self::DapDelay {
643            delay: Expression::Normal("0".to_string()),
644        }
645    }
646}
647
648impl TryFrom<(String, Vec<Expression>)> for DebugFunction {
649    type Error = DebugAccessParseError;
650
651    /// Parses a debug access function by name and argument list.
652    ///
653    /// Returns [Err] if the function name is not in the CMSIS-Pack spec or the argument count is wrong.
654    #[allow(clippy::too_many_lines)]
655    fn try_from((name, args): (String, Vec<Expression>)) -> Result<Self, Self::Error> {
656        match name.as_str() {
657            // Memory — 1 arg (addr)
658            "Read8" => match <[Expression; 1]>::try_from(args) {
659                Ok([addr]) => Ok(Self::Read8 { addr }),
660                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
661                    "Read8 expects 1 argument, got {}",
662                    v.len()
663                ))),
664            },
665            "Read16" => match <[Expression; 1]>::try_from(args) {
666                Ok([addr]) => Ok(Self::Read16 { addr }),
667                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
668                    "Read16 expects 1 argument, got {}",
669                    v.len()
670                ))),
671            },
672            "Read32" => match <[Expression; 1]>::try_from(args) {
673                Ok([addr]) => Ok(Self::Read32 { addr }),
674                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
675                    "Read32 expects 1 argument, got {}",
676                    v.len()
677                ))),
678            },
679            "Read64" => match <[Expression; 1]>::try_from(args) {
680                Ok([addr]) => Ok(Self::Read64 { addr }),
681                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
682                    "Read64 expects 1 argument, got {}",
683                    v.len()
684                ))),
685            },
686            // Memory — 2 args (addr, val)
687            "Write8" => match <[Expression; 2]>::try_from(args) {
688                Ok([addr, val]) => Ok(Self::Write8 { addr, val }),
689                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
690                    "Write8 expects 2 arguments, got {}",
691                    v.len()
692                ))),
693            },
694            "Write16" => match <[Expression; 2]>::try_from(args) {
695                Ok([addr, val]) => Ok(Self::Write16 { addr, val }),
696                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
697                    "Write16 expects 2 arguments, got {}",
698                    v.len()
699                ))),
700            },
701            "Write32" => match <[Expression; 2]>::try_from(args) {
702                Ok([addr, val]) => Ok(Self::Write32 { addr, val }),
703                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
704                    "Write32 expects 2 arguments, got {}",
705                    v.len()
706                ))),
707            },
708            "Write64" => match <[Expression; 2]>::try_from(args) {
709                Ok([addr, val]) => Ok(Self::Write64 { addr, val }),
710                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
711                    "Write64 expects 2 arguments, got {}",
712                    v.len()
713                ))),
714            },
715            // Register — 1 arg (addr)
716            "ReadAP" => match <[Expression; 1]>::try_from(args) {
717                Ok([addr]) => Ok(Self::ReadAP { addr }),
718                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
719                    "ReadAP expects 1 argument, got {}",
720                    v.len()
721                ))),
722            },
723            "ReadDP" => match <[Expression; 1]>::try_from(args) {
724                Ok([addr]) => Ok(Self::ReadDP { addr }),
725                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
726                    "ReadDP expects 1 argument, got {}",
727                    v.len()
728                ))),
729            },
730            "ReadAccessAP" => match <[Expression; 1]>::try_from(args) {
731                Ok([addr]) => Ok(Self::ReadAccessAP { addr }),
732                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
733                    "ReadAccessAP expects 1 argument, got {}",
734                    v.len()
735                ))),
736            },
737            // Register — 2 args (addr, val)
738            "WriteAP" => match <[Expression; 2]>::try_from(args) {
739                Ok([addr, val]) => Ok(Self::WriteAP { addr, val }),
740                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
741                    "WriteAP expects 2 arguments, got {}",
742                    v.len()
743                ))),
744            },
745            "WriteDP" => match <[Expression; 2]>::try_from(args) {
746                Ok([addr, val]) => Ok(Self::WriteDP { addr, val }),
747                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
748                    "WriteDP expects 2 arguments, got {}",
749                    v.len()
750                ))),
751            },
752            "WriteAccessAP" => match <[Expression; 2]>::try_from(args) {
753                Ok([addr, val]) => Ok(Self::WriteAccessAP { addr, val }),
754                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
755                    "WriteAccessAP expects 2 arguments, got {}",
756                    v.len()
757                ))),
758            },
759            // Debug port — 1 arg
760            "DAP_Delay" => match <[Expression; 1]>::try_from(args) {
761                Ok([delay]) => Ok(Self::DapDelay { delay }),
762                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
763                    "DAP_Delay expects 1 argument, got {}",
764                    v.len()
765                ))),
766            },
767            "DAP_WriteABORT" => match <[Expression; 1]>::try_from(args) {
768                Ok([value]) => Ok(Self::DapWriteAbort { value }),
769                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
770                    "DAP_WriteABORT expects 1 argument, got {}",
771                    v.len()
772                ))),
773            },
774            "DAP_SWJ_Clock" => match <[Expression; 1]>::try_from(args) {
775                Ok([val]) => Ok(Self::DapSwjClock { val }),
776                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
777                    "DAP_SWJ_Clock expects 1 argument, got {}",
778                    v.len()
779                ))),
780            },
781            // Debug port — 2 args
782            "DAP_SWJ_Sequence" => match <[Expression; 2]>::try_from(args) {
783                Ok([cnt, val]) => Ok(Self::DapSwjSequence { cnt, val }),
784                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
785                    "DAP_SWJ_Sequence expects 2 arguments, got {}",
786                    v.len()
787                ))),
788            },
789            // Debug port — 3 args
790            "DAP_SWJ_Pins" => match <[Expression; 3]>::try_from(args) {
791                Ok([pinout, pinselect, pinwait]) => Ok(Self::DapSwjPins {
792                    pinout,
793                    pinselect,
794                    pinwait,
795                }),
796                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
797                    "DAP_SWJ_Pins expects 3 arguments, got {}",
798                    v.len()
799                ))),
800            },
801            "DAP_JTAG_Sequence" => match <[Expression; 3]>::try_from(args) {
802                Ok([cnt, tms, tdi]) => Ok(Self::DapJtagSequence { cnt, tms, tdi }),
803                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
804                    "DAP_JTAG_Sequence expects 3 arguments, got {}",
805                    v.len()
806                ))),
807            },
808            // Sequence control — 1 arg
809            "Sequence" => match <[Expression; 1]>::try_from(args) {
810                Ok([name]) => Ok(Self::Sequence { name }),
811                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
812                    "Sequence expects 1 argument, got {}",
813                    v.len()
814                ))),
815            },
816            // Sequence control — 2 args
817            "QueryValue" => match <[Expression; 2]>::try_from(args) {
818                Ok([message, default]) => Ok(Self::QueryValue { message, default }),
819                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
820                    "QueryValue expects 2 arguments, got {}",
821                    v.len()
822                ))),
823            },
824            // Sequence control — 3 args
825            "Query" => match <[Expression; 3]>::try_from(args) {
826                Ok([query_type, message, default]) => Ok(Self::Query {
827                    query_type,
828                    message,
829                    default,
830                }),
831                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
832                    "Query expects 3 arguments, got {}",
833                    v.len()
834                ))),
835            },
836            // Sequence control — variadic (2+ args)
837            "Message" => {
838                let mut it = args.into_iter();
839                let msg_type = it.next().ok_or_else(|| {
840                    DebugAccessParseError::MissingAttribute(
841                        "Message expects at least 2 arguments, got 0".to_string(),
842                    )
843                })?;
844                let format_expr = it.next().ok_or_else(|| {
845                    DebugAccessParseError::MissingAttribute(
846                        "Message expects at least 2 arguments, got 1".to_string(),
847                    )
848                })?;
849                Ok(Self::Message {
850                    msg_type,
851                    format: format_expr,
852                    args: it.collect(),
853                })
854            }
855            // Flash — 3 args
856            "FlashLoadAlgorithm" => match <[Expression; 3]>::try_from(args) {
857                Ok([algo_path, ram_start, ram_size]) => Ok(Self::FlashLoadAlgorithm {
858                    algo_path,
859                    ram_start,
860                    ram_size,
861                }),
862                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
863                    "FlashLoadAlgorithm expects 3 arguments, got {}",
864                    v.len()
865                ))),
866            },
867            // Flash — 4 args
868            "FlashWriteBuffer" => match <[Expression; 4]>::try_from(args) {
869                Ok([addr, offs, len, mode]) => Ok(Self::FlashWriteBuffer {
870                    addr,
871                    offs,
872                    len,
873                    mode,
874                }),
875                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
876                    "FlashWriteBuffer expects 4 arguments, got {}",
877                    v.len()
878                ))),
879            },
880            // Buffer — 1 arg
881            "BufferSize" => match <[Expression; 1]>::try_from(args) {
882                Ok([buff_id]) => Ok(Self::BufferSize { buff_id }),
883                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
884                    "BufferSize expects 1 argument, got {}",
885                    v.len()
886                ))),
887            },
888            // Buffer — 3 args
889            "BufferGet" => match <[Expression; 3]>::try_from(args) {
890                Ok([buff_id, buff_offset, size]) => Ok(Self::BufferGet {
891                    buff_id,
892                    buff_offset,
893                    size,
894                }),
895                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
896                    "BufferGet expects 3 arguments, got {}",
897                    v.len()
898                ))),
899            },
900            // Buffer — 5 args
901            "BufferSet" => match <[Expression; 5]>::try_from(args) {
902                Ok([buff_id, buff_offset, count, size, value]) => Ok(Self::BufferSet {
903                    buff_id,
904                    buff_offset,
905                    count,
906                    size,
907                    value,
908                }),
909                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
910                    "BufferSet expects 5 arguments, got {}",
911                    v.len()
912                ))),
913            },
914            "BufferRead" => match <[Expression; 5]>::try_from(args) {
915                Ok([buff_id, buff_offset, addr, length, mode]) => Ok(Self::BufferRead {
916                    buff_id,
917                    buff_offset,
918                    addr,
919                    length,
920                    mode,
921                }),
922                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
923                    "BufferRead expects 5 arguments, got {}",
924                    v.len()
925                ))),
926            },
927            "BufferWrite" => match <[Expression; 5]>::try_from(args) {
928                Ok([buff_id, buff_offset, addr, length, mode]) => Ok(Self::BufferWrite {
929                    buff_id,
930                    buff_offset,
931                    addr,
932                    length,
933                    mode,
934                }),
935                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
936                    "BufferWrite expects 5 arguments, got {}",
937                    v.len()
938                ))),
939            },
940            // External — 1 arg
941            "LoadDebugInfo" => match <[Expression; 1]>::try_from(args) {
942                Ok([file]) => Ok(Self::LoadDebugInfo { file }),
943                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
944                    "LoadDebugInfo expects 1 argument, got {}",
945                    v.len()
946                ))),
947            },
948            // External — 2 args
949            "FilePathExists" => match <[Expression; 2]>::try_from(args) {
950                Ok([path, timeout]) => Ok(Self::FilePathExists { path, timeout }),
951                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
952                    "FilePathExists expects 2 arguments, got {}",
953                    v.len()
954                ))),
955            },
956            // External — 4 args
957            "RunApplication" => match <[Expression; 4]>::try_from(args) {
958                Ok([app_path, arguments, work_directory, timeout]) => Ok(Self::RunApplication {
959                    app_path,
960                    arguments,
961                    work_directory,
962                    timeout,
963                }),
964                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
965                    "RunApplication expects 4 arguments, got {}",
966                    v.len()
967                ))),
968            },
969            "RunPythonScript" => match <[Expression; 4]>::try_from(args) {
970                Ok([script_path, arguments, work_directory, timeout]) => {
971                    Ok(Self::RunPythonScript {
972                        script_path,
973                        arguments,
974                        work_directory,
975                        timeout,
976                    })
977                }
978                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
979                    "RunPythonScript expects 4 arguments, got {}",
980                    v.len()
981                ))),
982            },
983            // External — 6 args
984            "BufferStreamIn" => match <[Expression; 6]>::try_from(args) {
985                Ok([buff_id, buff_offset, length, path, mode, timeout]) => {
986                    Ok(Self::BufferStreamIn {
987                        buff_id,
988                        buff_offset,
989                        length,
990                        path,
991                        mode,
992                        timeout,
993                    })
994                }
995                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
996                    "BufferStreamIn expects 6 arguments, got {}",
997                    v.len()
998                ))),
999            },
1000            "BufferStreamOut" => match <[Expression; 6]>::try_from(args) {
1001                Ok([buff_id, buff_offset, length, dest_path, dest_mode, timeout]) => {
1002                    Ok(Self::BufferStreamOut {
1003                        buff_id,
1004                        buff_offset,
1005                        length,
1006                        dest_path,
1007                        dest_mode,
1008                        timeout,
1009                    })
1010                }
1011                Err(v) => Err(DebugAccessParseError::MissingAttribute(format!(
1012                    "BufferStreamOut expects 6 arguments, got {}",
1013                    v.len()
1014                ))),
1015            },
1016            _ => Err(DebugAccessParseError::UnknownStatement(name)),
1017        }
1018    }
1019}
1020
1021/// Returns `Some((name, args_str))` if `s` matches `identifier(...)`, otherwise `None`.
1022///
1023/// `name` is the function name; `args_str` is the raw content between the outer parentheses.
1024fn detect_function_call(s: &str) -> Option<(&str, &str)> {
1025    if !s.ends_with(')') {
1026        return None;
1027    }
1028
1029    let paren_pos = s.find('(')?;
1030    #[allow(clippy::string_slice)]
1031    // Safety:
1032    //   This is known to have a lot of false positives, and is OK if
1033    //   given a valid position, which `find` should return.
1034    let name = &s[..paren_pos];
1035
1036    // Validate name is a non-empty identifier [A-Za-z_][A-Za-z0-9_]*
1037    let mut name_chars = name.chars();
1038    let first = name_chars.next()?;
1039    if !first.is_alphabetic() && first != '_' {
1040        return None;
1041    }
1042    if !name_chars.all(|c| c.is_alphanumeric() || c == '_') {
1043        return None;
1044    }
1045
1046    #[allow(clippy::arithmetic_side_effects)]
1047    // Safety:
1048    //   While in theory `paren_pos + 1` could overflow it is
1049    //   extremely unlikely, if so `s.len()` would also have
1050    //   problems.
1051    #[allow(clippy::string_slice)]
1052    // Safety:
1053    //   This is known to have a lot of false positives, and is OK if
1054    //   given a valid position, which `find` should return. The end
1055    //   of the string should also always be a valid position.
1056    let args_str = &s[paren_pos + 1..s.len() - 1];
1057    Some((name, args_str))
1058}
1059
1060/// Splits a comma-separated argument string into trimmed segments, respecting nested parentheses.
1061///
1062/// e.g. `"addr, Read32(base)"` → `["addr", "Read32(base)"]`
1063fn split_args(args_str: &str) -> Vec<&str> {
1064    if args_str.trim().is_empty() {
1065        return Vec::new();
1066    }
1067
1068    let mut result = Vec::new();
1069    let mut depth: u32 = 0u32;
1070    let mut start: usize = 0;
1071
1072    #[allow(clippy::arithmetic_side_effects)]
1073    // Safety:
1074    //   If you have nested to `u32::MAX` I will be thoroughly impressed
1075    for (i, c) in args_str.char_indices() {
1076        match c {
1077            '(' => depth += 1,
1078            ')' => depth -= 1,
1079            ',' if depth == 0 => {
1080                #[allow(clippy::string_slice)]
1081                // Safety:
1082                //   We are iterating over char indices which is
1083                //   explicitly used as a false positive in the clippy
1084                //   documentation.
1085                result.push(args_str[start..i].trim());
1086                start = i + 1;
1087            }
1088            _ => {}
1089        }
1090    }
1091
1092    #[allow(clippy::string_slice)]
1093    // Safety:
1094    //   `start` value was obtained via `char_indices`
1095    let last = args_str[start..].trim();
1096    if !last.is_empty() {
1097        result.push(last);
1098    }
1099
1100    result
1101}
1102
1103#[cfg(test)]
1104mod tests {
1105    use crate::debug_access::{
1106        Assignment, Conditional, DebugAccessParseError, DebugFunction, Expression, Statement,
1107    };
1108
1109    #[test]
1110    fn parse_comment() {
1111        let line = "// This is a comment!".to_string();
1112
1113        let statement: Statement = line.try_into().unwrap();
1114
1115        assert_eq!(
1116            statement,
1117            Statement::Comment("// This is a comment!".to_string())
1118        );
1119    }
1120
1121    #[test]
1122    fn semicolon_handling() {
1123        let line1 = "Read32(0x10)".to_string();
1124        let line2 = "Read32(0x10);".to_string();
1125
1126        let statement1: Statement = line1.try_into().unwrap();
1127        let statement2: Statement = line2.try_into().unwrap();
1128
1129        assert_eq!(statement1, statement2);
1130    }
1131
1132    #[test]
1133    fn parse_expression_normal() {
1134        let line = "addr + offset;".to_string();
1135
1136        let statement: Statement = line.try_into().unwrap();
1137
1138        assert_eq!(
1139            statement,
1140            Statement::Expression(Expression::Normal("addr + offset".to_string()))
1141        );
1142    }
1143
1144    #[test]
1145    fn parse_expression_normal_variable() {
1146        let line = "doIfBlock".to_string();
1147
1148        let statement: Statement = line.try_into().unwrap();
1149
1150        assert_eq!(
1151            statement,
1152            Statement::Expression(Expression::Normal("doIfBlock".to_string()))
1153        );
1154    }
1155
1156    #[test]
1157    fn parse_expression_conditional() {
1158        let line = "(x < y) ? a : b".to_string();
1159
1160        let statement: Statement = line.try_into().unwrap();
1161
1162        assert_eq!(
1163            statement,
1164            Statement::Expression(Expression::Conditional(Box::new(Conditional {
1165                condition: Expression::Normal("x < y".to_string()),
1166                true_value: Expression::Normal("a".to_string()),
1167                false_value: Expression::Normal("b".to_string())
1168            })))
1169        );
1170    }
1171
1172    #[test]
1173    fn parse_assignment_comparison() {
1174        let line = "thisValue = (readTheCoolRegister(0x248) == 5);".to_string();
1175
1176        let statement: Statement = line.try_into().unwrap();
1177
1178        assert_eq!(
1179            statement,
1180            Statement::Assignment(Assignment {
1181                variable: "thisValue".to_string(),
1182                expression: Expression::Normal("(readTheCoolRegister(0x248) == 5)".to_string())
1183            })
1184        );
1185    }
1186
1187    #[test]
1188    fn parse_assignment() {
1189        let line = "variable = expression;".to_string();
1190
1191        let statement: Statement = line.try_into().unwrap();
1192
1193        assert_eq!(
1194            statement,
1195            Statement::Assignment(Assignment {
1196                expression: Expression::Normal("expression".to_string()),
1197                variable: "variable".to_string(),
1198            })
1199        )
1200    }
1201
1202    #[test]
1203    fn parse_definition() {
1204        let line = "__var variable = 0;".to_string();
1205
1206        let statement: Statement = line.try_into().unwrap();
1207
1208        assert_eq!(
1209            statement,
1210            Statement::Definition(Assignment {
1211                expression: Expression::Normal("0".to_string()),
1212                variable: "variable".to_string(),
1213            })
1214        )
1215    }
1216
1217    #[test]
1218    fn parse_function_call_single_arg() {
1219        let line = "Read32(0x40000000);".to_string();
1220
1221        let statement: Statement = line.try_into().unwrap();
1222
1223        assert_eq!(
1224            statement,
1225            Statement::Expression(Expression::FunctionCall(Box::new(DebugFunction::Read32 {
1226                addr: Expression::Normal("0x40000000".to_string())
1227            })))
1228        );
1229    }
1230
1231    #[test]
1232    fn parse_function_call_two_args() {
1233        let line = "Write32(addr, val);".to_string();
1234
1235        let statement: Statement = line.try_into().unwrap();
1236
1237        assert_eq!(
1238            statement,
1239            Statement::Expression(Expression::FunctionCall(Box::new(DebugFunction::Write32 {
1240                addr: Expression::Normal("addr".to_string()),
1241                val: Expression::Normal("val".to_string()),
1242            })))
1243        );
1244    }
1245
1246    #[test]
1247    fn parse_function_call_string_arg() {
1248        let line = "Sequence(\"ResetAndHalt\");".to_string();
1249
1250        let statement: Statement = line.try_into().unwrap();
1251
1252        assert_eq!(
1253            statement,
1254            Statement::Expression(Expression::FunctionCall(Box::new(
1255                DebugFunction::Sequence {
1256                    name: Expression::Normal("\"ResetAndHalt\"".to_string())
1257                }
1258            )))
1259        );
1260    }
1261
1262    #[test]
1263    fn parse_function_call_three_args() {
1264        let line = "DAP_SWJ_Pins(pinout, pinselect, pinwait);".to_string();
1265
1266        let statement: Statement = line.try_into().unwrap();
1267
1268        assert_eq!(
1269            statement,
1270            Statement::Expression(Expression::FunctionCall(Box::new(
1271                DebugFunction::DapSwjPins {
1272                    pinout: Expression::Normal("pinout".to_string()),
1273                    pinselect: Expression::Normal("pinselect".to_string()),
1274                    pinwait: Expression::Normal("pinwait".to_string()),
1275                }
1276            )))
1277        );
1278    }
1279
1280    #[test]
1281    fn parse_function_call_variadic() {
1282        let line = "Message(1, \"debug message\");".to_string();
1283
1284        let statement: Statement = line.try_into().unwrap();
1285
1286        assert_eq!(
1287            statement,
1288            Statement::Expression(Expression::FunctionCall(Box::new(DebugFunction::Message {
1289                msg_type: Expression::Normal("1".to_string()),
1290                format: Expression::Normal("\"debug message\"".to_string()),
1291                args: vec![],
1292            })))
1293        );
1294    }
1295
1296    #[test]
1297    fn parse_function_call_nested_arg() {
1298        // Read32(base) is an argument to Write32 — split_args must not split on the inner comma
1299        let line = "Write32(addr, Read32(base));".to_string();
1300
1301        let statement: Statement = line.try_into().unwrap();
1302
1303        assert_eq!(
1304            statement,
1305            Statement::Expression(Expression::FunctionCall(Box::new(DebugFunction::Write32 {
1306                addr: Expression::Normal("addr".to_string()),
1307                val: Expression::FunctionCall(Box::new(DebugFunction::Read32 {
1308                    addr: Expression::Normal("base".to_string()),
1309                })),
1310            })))
1311        );
1312    }
1313
1314    #[test]
1315    #[should_panic(expected = "unknown statement: GetBase")]
1316    fn unknown_function_panics() {
1317        if let Err(e) = Expression::try_from("GetBase()") {
1318            panic!("{e}");
1319        }
1320    }
1321
1322    #[test]
1323    fn conditional_missing_syntax() {
1324        let result = Conditional::try_from("no parentheses here");
1325        assert!(matches!(
1326            result,
1327            Err(DebugAccessParseError::MissingAttribute(_))
1328        ));
1329    }
1330
1331    #[test]
1332    fn unknown_function_returns_unknown_statement() {
1333        let result = DebugFunction::try_from(("GetBase".to_string(), vec![]));
1334        assert_eq!(
1335            result.unwrap_err(),
1336            DebugAccessParseError::UnknownStatement("GetBase".to_string())
1337        );
1338    }
1339
1340    fn normal(value: &str) -> Expression {
1341        Expression::Normal(value.to_string())
1342    }
1343
1344    #[test]
1345    fn format_debug_functions_exhaustively() {
1346        let cases = vec![
1347            (DebugFunction::Read8 { addr: normal("a") }, "Read8(a)"),
1348            (DebugFunction::Read16 { addr: normal("a") }, "Read16(a)"),
1349            (DebugFunction::Read32 { addr: normal("a") }, "Read32(a)"),
1350            (DebugFunction::Read64 { addr: normal("a") }, "Read64(a)"),
1351            (
1352                DebugFunction::Write8 {
1353                    addr: normal("a"),
1354                    val: normal("v"),
1355                },
1356                "Write8(a, v)",
1357            ),
1358            (
1359                DebugFunction::Write16 {
1360                    addr: normal("a"),
1361                    val: normal("v"),
1362                },
1363                "Write16(a, v)",
1364            ),
1365            (
1366                DebugFunction::Write32 {
1367                    addr: normal("a"),
1368                    val: normal("v"),
1369                },
1370                "Write32(a, v)",
1371            ),
1372            (
1373                DebugFunction::Write64 {
1374                    addr: normal("a"),
1375                    val: normal("v"),
1376                },
1377                "Write64(a, v)",
1378            ),
1379            (DebugFunction::ReadAP { addr: normal("a") }, "ReadAP(a)"),
1380            (
1381                DebugFunction::WriteAP {
1382                    addr: normal("a"),
1383                    val: normal("v"),
1384                },
1385                "WriteAP(a, v)",
1386            ),
1387            (DebugFunction::ReadDP { addr: normal("a") }, "ReadDP(a)"),
1388            (
1389                DebugFunction::WriteDP {
1390                    addr: normal("a"),
1391                    val: normal("v"),
1392                },
1393                "WriteDP(a, v)",
1394            ),
1395            (
1396                DebugFunction::ReadAccessAP { addr: normal("a") },
1397                "ReadAccessAP(a)",
1398            ),
1399            (
1400                DebugFunction::WriteAccessAP {
1401                    addr: normal("a"),
1402                    val: normal("v"),
1403                },
1404                "WriteAccessAP(a, v)",
1405            ),
1406            (
1407                DebugFunction::DapDelay {
1408                    delay: normal("delay"),
1409                },
1410                "DAP_Delay(delay)",
1411            ),
1412            (
1413                DebugFunction::DapWriteAbort {
1414                    value: normal("value"),
1415                },
1416                "DAP_WriteABORT(value)",
1417            ),
1418            (
1419                DebugFunction::DapSwjPins {
1420                    pinout: normal("pinout"),
1421                    pinselect: normal("pinselect"),
1422                    pinwait: normal("pinwait"),
1423                },
1424                "DAP_SWJ_Pins(pinout, pinselect, pinwait)",
1425            ),
1426            (
1427                DebugFunction::DapSwjClock { val: normal("val") },
1428                "DAP_SWJ_Clock(val)",
1429            ),
1430            (
1431                DebugFunction::DapSwjSequence {
1432                    cnt: normal("cnt"),
1433                    val: normal("val"),
1434                },
1435                "DAP_SWJ_Sequence(cnt, val)",
1436            ),
1437            (
1438                DebugFunction::DapJtagSequence {
1439                    cnt: normal("cnt"),
1440                    tms: normal("tms"),
1441                    tdi: normal("tdi"),
1442                },
1443                "DAP_JTAG_Sequence(cnt, tms, tdi)",
1444            ),
1445            (
1446                DebugFunction::Sequence {
1447                    name: normal("name"),
1448                },
1449                "Sequence(name)",
1450            ),
1451            (
1452                DebugFunction::Query {
1453                    query_type: normal("query_type"),
1454                    message: normal("message"),
1455                    default: normal("default"),
1456                },
1457                "Query(query_type, message, default)",
1458            ),
1459            (
1460                DebugFunction::QueryValue {
1461                    message: normal("message"),
1462                    default: normal("default"),
1463                },
1464                "QueryValue(message, default)",
1465            ),
1466            (
1467                DebugFunction::Message {
1468                    msg_type: normal("msg_type"),
1469                    format: normal("format"),
1470                    args: vec![],
1471                },
1472                "Message(msg_type, format)",
1473            ),
1474            (
1475                DebugFunction::FlashWriteBuffer {
1476                    addr: normal("addr"),
1477                    offs: normal("offs"),
1478                    len: normal("len"),
1479                    mode: normal("mode"),
1480                },
1481                "FlashWriteBuffer(addr, offs, len, mode)",
1482            ),
1483            (
1484                DebugFunction::FlashLoadAlgorithm {
1485                    algo_path: normal("algo_path"),
1486                    ram_start: normal("ram_start"),
1487                    ram_size: normal("ram_size"),
1488                },
1489                "FlashLoadAlgorithm(algo_path, ram_start, ram_size)",
1490            ),
1491            (
1492                DebugFunction::BufferSet {
1493                    buff_id: normal("buff_id"),
1494                    buff_offset: normal("buff_offset"),
1495                    count: normal("count"),
1496                    size: normal("size"),
1497                    value: normal("value"),
1498                },
1499                "BufferSet(buff_id, buff_offset, count, size, value)",
1500            ),
1501            (
1502                DebugFunction::BufferGet {
1503                    buff_id: normal("buff_id"),
1504                    buff_offset: normal("buff_offset"),
1505                    size: normal("size"),
1506                },
1507                "BufferGet(buff_id, buff_offset, size)",
1508            ),
1509            (
1510                DebugFunction::BufferSize {
1511                    buff_id: normal("buff_id"),
1512                },
1513                "BufferSize(buff_id)",
1514            ),
1515            (
1516                DebugFunction::BufferRead {
1517                    buff_id: normal("buff_id"),
1518                    buff_offset: normal("buff_offset"),
1519                    addr: normal("addr"),
1520                    length: normal("length"),
1521                    mode: normal("mode"),
1522                },
1523                "BufferRead(buff_id, buff_offset, addr, length, mode)",
1524            ),
1525            (
1526                DebugFunction::BufferWrite {
1527                    buff_id: normal("buff_id"),
1528                    buff_offset: normal("buff_offset"),
1529                    addr: normal("addr"),
1530                    length: normal("length"),
1531                    mode: normal("mode"),
1532                },
1533                "BufferWrite(buff_id, buff_offset, addr, length, mode)",
1534            ),
1535            (
1536                DebugFunction::BufferStreamIn {
1537                    buff_id: normal("buff_id"),
1538                    buff_offset: normal("buff_offset"),
1539                    length: normal("length"),
1540                    path: normal("path"),
1541                    mode: normal("mode"),
1542                    timeout: normal("timeout"),
1543                },
1544                "BufferStreamIn(buff_id, buff_offset, length, path, mode, timeout)",
1545            ),
1546            (
1547                DebugFunction::BufferStreamOut {
1548                    buff_id: normal("buff_id"),
1549                    buff_offset: normal("buff_offset"),
1550                    length: normal("length"),
1551                    dest_path: normal("dest_path"),
1552                    dest_mode: normal("dest_mode"),
1553                    timeout: normal("timeout"),
1554                },
1555                "BufferStreamOut(buff_id, buff_offset, length, dest_path, dest_mode, timeout)",
1556            ),
1557            (
1558                DebugFunction::RunApplication {
1559                    app_path: normal("app_path"),
1560                    arguments: normal("arguments"),
1561                    work_directory: normal("work_directory"),
1562                    timeout: normal("timeout"),
1563                },
1564                "RunApplication(app_path, arguments, work_directory, timeout)",
1565            ),
1566            (
1567                DebugFunction::RunPythonScript {
1568                    script_path: normal("script_path"),
1569                    arguments: normal("arguments"),
1570                    work_directory: normal("work_directory"),
1571                    timeout: normal("timeout"),
1572                },
1573                "RunPythonScript(script_path, arguments, work_directory, timeout)",
1574            ),
1575            (
1576                DebugFunction::FilePathExists {
1577                    path: normal("path"),
1578                    timeout: normal("timeout"),
1579                },
1580                "FilePathExists(path, timeout)",
1581            ),
1582            (
1583                DebugFunction::LoadDebugInfo {
1584                    file: normal("file"),
1585                },
1586                "LoadDebugInfo(file)",
1587            ),
1588        ];
1589
1590        for (function, expected) in cases {
1591            let actual = function.to_string();
1592            assert_eq!(actual, expected);
1593            assert!(!actual.contains(';'));
1594            assert!(!actual.contains(",  "));
1595        }
1596
1597        assert_eq!(
1598            DebugFunction::Read8 {
1599                addr: normal("0x64FF")
1600            }
1601            .to_string(),
1602            "Read8(0x64FF)"
1603        );
1604    }
1605
1606    #[test]
1607    fn format_expression_variants_recursively() {
1608        assert_eq!(
1609            normal("arbitrary text, unchanged").to_string(),
1610            "arbitrary text, unchanged"
1611        );
1612        assert_eq!(
1613            Expression::Conditional(Box::new(Conditional {
1614                condition: normal("x < y"),
1615                true_value: normal("a"),
1616                false_value: normal("b"),
1617            }))
1618            .to_string(),
1619            "(x < y) ? a : b"
1620        );
1621
1622        let nested = Expression::Conditional(Box::new(Conditional {
1623            condition: Expression::FunctionCall(Box::new(DebugFunction::Read8 {
1624                addr: normal("condition_addr"),
1625            })),
1626            true_value: Expression::FunctionCall(Box::new(DebugFunction::Read16 {
1627                addr: normal("true_addr"),
1628            })),
1629            false_value: Expression::FunctionCall(Box::new(DebugFunction::Read32 {
1630                addr: normal("false_addr"),
1631            })),
1632        }));
1633        assert_eq!(
1634            nested.to_string(),
1635            "(Read8(condition_addr)) ? Read16(true_addr) : Read32(false_addr)"
1636        );
1637    }
1638
1639    #[test]
1640    fn format_message_variadic_arguments() {
1641        let message = |args| DebugFunction::Message {
1642            msg_type: normal("1"),
1643            format: normal("\"message\""),
1644            args,
1645        };
1646
1647        assert_eq!(message(vec![]).to_string(), "Message(1, \"message\")");
1648        assert_eq!(
1649            message(vec![normal("arg1")]).to_string(),
1650            "Message(1, \"message\", arg1)"
1651        );
1652        assert_eq!(
1653            message(vec![normal("arg1"), normal("arg2"), normal("arg3")]).to_string(),
1654            "Message(1, \"message\", arg1, arg2, arg3)"
1655        );
1656    }
1657
1658    #[test]
1659    fn format_canonical_debug_names() {
1660        let cases = [
1661            ("DAP_Delay", DebugFunction::DapDelay { delay: normal("1") }),
1662            (
1663                "DAP_WriteABORT",
1664                DebugFunction::DapWriteAbort { value: normal("2") },
1665            ),
1666            (
1667                "DAP_SWJ_Pins",
1668                DebugFunction::DapSwjPins {
1669                    pinout: normal("3"),
1670                    pinselect: normal("4"),
1671                    pinwait: normal("5"),
1672                },
1673            ),
1674            (
1675                "DAP_SWJ_Clock",
1676                DebugFunction::DapSwjClock { val: normal("6") },
1677            ),
1678            (
1679                "DAP_SWJ_Sequence",
1680                DebugFunction::DapSwjSequence {
1681                    cnt: normal("7"),
1682                    val: normal("8"),
1683                },
1684            ),
1685            (
1686                "DAP_JTAG_Sequence",
1687                DebugFunction::DapJtagSequence {
1688                    cnt: normal("9"),
1689                    tms: normal("10"),
1690                    tdi: normal("11"),
1691                },
1692            ),
1693        ];
1694
1695        for (name, function) in cases {
1696            assert!(function.to_string().starts_with(name));
1697        }
1698    }
1699
1700    #[test]
1701    fn format_parsed_expressions_round_trip() {
1702        let cases = [
1703            ("Read8(0x64FF)", "Read8(0x64FF)"),
1704            ("Write32(addr, Read32(base))", "Write32(addr, Read32(base))"),
1705            (
1706                "(condition) ? Write8(addr, 1) : Read16(addr)",
1707                "(condition) ? Write8(addr, 1) : Read16(addr)",
1708            ),
1709            ("Sequence(\"ResetAndHalt\")", "Sequence(\"ResetAndHalt\")"),
1710            (
1711                "Message(1, \"value\", Read32(addr), extra)",
1712                "Message(1, \"value\", Read32(addr), extra)",
1713            ),
1714        ];
1715
1716        for (source, expected) in cases {
1717            let expression = Expression::try_from(source).unwrap();
1718            assert_eq!(expression.to_string(), expected);
1719        }
1720    }
1721}