psyk/
link.rs

1// SPDX-FileCopyrightText: © 2025 TTKB, LLC
2// SPDX-License-Identifier: BSD-3-CLAUSE
3
4//! PSY-Q Linker Script File Parser
5//!
6//! **n.b.!** This interface is not stable and should be considered unstable.
7//!
8//! ## Overview
9//!
10//! The `link.rs` module provides a parser for PSY-Q linker script files (`.LNK` files). These scripts control how the
11//! `psylink.exe` program links object files together, specifying memory layouts, symbol definitions, section placement,
12//! and overlay configurations.
13//!
14//! ## Purpose
15//!
16//! PSY-Q linker scripts define:
17//! - **Memory organization**: Where code and data are placed in memory
18//! - **Section grouping**: How logical sections map to physical memory regions
19//! - **Symbol management**: External references, definitions, and aliases
20//! - **Overlay management**: How code overlays are structured for memory-constrained systems
21//! - **Library inclusion**: Which object files and libraries to link
22//!
23//! These scripts were essential for PlayStation 1 development where memory constraints required precise control over code placement and overlays.
24//!
25//! ## Linker Script Syntax
26//!
27//! ### Integer Constants
28//!
29//! Three formats supported:
30//! ```asm
31//! 1234        # Decimal
32//! $1234       # Hexadecimal (0x1234)
33//! %1010       # Binary (10)
34//! ```
35//!
36//! ### Commands
37//!
38//! #### Memory Organization
39//!
40//! ```asm
41//! org $80010000           ; Set origin address
42//! workspace $801F0000     ; Set workspace address
43//! ```
44//!
45//! #### File Inclusion
46//!
47//! ```asm
48//! include "main.obj"      ; Include object file
49//! inclib "libgpu.lib"     ; Include library file
50//! ```
51//!
52//! #### Symbol Management
53//!
54//! ```asm
55//! ENTRY_POINT = $80010000     ; Symbol assignment
56//! ENTRY_POINT equ $80010000   ; Alternative syntax
57//!
58//! regs pc=ENTRY_POINT         ; Set register value
59//!
60//! global symbol1, symbol2      ; Declare global symbols
61//! xdef exported1, exported2    ; Define exported symbols
62//! xref imported1, imported2    ; Declare imported symbols
63//! ```
64//!
65//! #### Section Management
66//!
67//! ```asm
68//! ; Define group with attributes
69//! text group org($80010000), size($8000)
70//!
71//! ; Define section in group
72//! .text section text, word
73//!
74//! ; Alternative section syntax
75//! section .text
76//! section .data, text        ; Section with group
77//! ```
78//!
79//! #### Overlays
80//!
81//! ```asm
82//! ; Create overlay group
83//! overlay group over(main)
84//!
85//! ; Section in overlay
86//! ovl1 section overlay, file("overlay1.obj")
87//! ```
88//!
89//! #### Aliases and Units
90//!
91//! ```asm
92//! _start alias ENTRY_POINT    ; Create symbol alias
93//! unit 1                      ; Set unit number
94//! public on                   ; Enable public symbols
95//! public off                  ; Disable public symbols
96//! ```
97//!
98//! ### Attributes
99//!
100//! Attributes modify group/section behavior:
101//!
102//! ```asm
103//! bss                    ; Uninitialized data section
104//! org($80010000)        ; Set base address
105//! obj($80020000)        ; Object-specific address
106//! obj()                 ; Object-relative addressing
107//! over(groupname)       ; Part of overlay group
108//! word                  ; Word-aligned
109//! file("path.obj")      ; Associated source file
110//! size($8000)          ; Maximum size constraint
111//! ```
112//!
113//! ### Comments
114//!
115//! ```asm
116//! ; Full line comment
117//! org $80010000   ; End-of-line comment
118//! ```
119//!
120//! ## Usage Examples
121//!
122//! ### Parsing a Complete Script
123//!
124//! ```rust
125//! use psyk::link;
126//!
127//! let script = r#"
128//! ; Memory layout
129//! org $80010000
130//!
131//! ; Main program group
132//! text group org($80010000), size($8000)
133//!
134//! ; Code section
135//! .text section text, word
136//!
137//! ; Include main code
138//! include "main.obj"
139//!
140//! ; Library functions
141//! inclib "libgpu.lib"
142//! "#;
143//!
144//! for line in script.lines() {
145//!     let mut line_str = line;
146//!     match link::parse_line(&mut line_str) {
147//!         Ok((Some(command), comment)) => {
148//!             println!("Command: {:?}", command);
149//!             if let Some(c) = comment {
150//!                 println!("Comment: {}", c.comment);
151//!             }
152//!         }
153//!         Ok((None, Some(comment))) => {
154//!             println!("Comment: {}", comment.comment);
155//!         }
156//!         Err(e) => {
157//!             eprintln!("Parse error: {:?}", e);
158//!         }
159//!         _ => {}
160//!     }
161//! }
162//! ```
163//!
164//! ### Parsing Individual Commands
165//!
166//! ```rust
167//! use psyk::link;
168//!
169//! let mut input = "org $80010000";
170//! let result = link::parse_line(&mut input);
171//! match result {
172//!     Ok((Some(link::Command::Origin { address }), _)) => {
173//!         println!("Origin set to: 0x{:x}", address);
174//!     }
175//!     _ => {}
176//! }
177//! ```
178//!
179//! ### Building a Linker Configuration
180//!
181//! ```no_run
182//! use psyk::link;
183//!
184//! # fn main() -> Result<(), std::io::Error> {
185//!     let mut commands = Vec::new();
186//!
187//!     // Parse entire script
188//!     let script = std::fs::read_to_string("game.lnk")?;
189//!     for line in script.lines() {
190//!         let mut line_str = line;
191//!         if let Ok((Some(cmd), _)) = link::parse_line(&mut line_str) {
192//!             commands.push(cmd);
193//!         }
194//!     }
195//!
196//!     // Process commands
197//!     for cmd in commands {
198//!         match cmd {
199//!             link::Command::Origin { address } => {
200//!                 println!("Set origin: 0x{:x}", address);
201//!             }
202//!             link::Command::Include { filename } => {
203//!                 println!("Include: {}", filename);
204//!             }
205//!             // Handle other commands...
206//!             _ => {}
207//!         }
208//!     }
209//! #    Ok(())
210//! # }
211//! ```
212//!
213//! ## Common Linker Script Patterns
214//!
215//! ### Basic Memory Layout
216//!
217//! ```asm
218//! ; PlayStation 1 typical layout
219//! org $80010000                   ; Code starts at 1MB mark
220//!
221//! text group org($80010000)       ; Text segment
222//! .text section text, word
223//!
224//! data group org($80080000)       ; Data segment
225//! .data section data
226//!
227//! bss group org($800C0000), bss   ; Uninitialized data
228//! .bss section bss
229//! ```
230//!
231//! ### Overlay System
232//!
233//! ```asm
234//! ; Main program
235//! org $80010000
236//! main group org($80010000)
237//! .text section main
238//!
239//! ; Overlay region
240//! overlay group org($80100000), size($10000)
241//!
242//! ; Individual overlays (loaded on demand)
243//! level1 section overlay, over(main), file("level1.obj")
244//! level2 section overlay, over(main), file("level2.obj")
245//! ```
246//!
247//! ### Symbol Management
248//!
249//! ```asm
250//! ; Export entry point
251//! xdef _start, main
252//!
253//! ; Import library functions
254//! xref InitGeom, SetDefDrawEnv
255//!
256//! ; Register setup
257//! regs pc=_start
258//! regs sp=$801FFF00
259//!
260//! ; Symbol assignments
261//! SCREEN_WIDTH = 320
262//! SCREEN_HEIGHT = 240
263//! ```
264//!
265//! ## Error Handling
266//!
267//! The parser uses `winnow`'s error handling with context:
268//!
269//! ```rust
270//! use psyk::link;
271//! use winnow::error::ContextError;
272//!
273//! let mut input = "...";
274//!
275//! match link::parse_line(&mut input) {
276//!     Ok(result) => { /* ... */ },
277//!     Err(e) => {
278//!         eprintln!("Parse error at: {}", e);
279//!         // Error includes context about what was expected
280//!     }
281//! }
282//! ```
283//!
284//! Common error scenarios:
285//! - **Invalid integer format**: `org xyz` (not a number)
286//! - **Missing quotes**: `include file.obj` (needs quotes)
287//! - **Invalid attribute syntax**: `org(80010000)` (missing $)
288//! - **Unknown command**: Misspelled keywords
289
290use std::fmt;
291use std::fmt::Debug;
292
293use winnow::ascii::digit1;
294use winnow::ascii::hex_digit1;
295use winnow::ascii::space0;
296use winnow::ascii::space1;
297use winnow::ascii::Caseless;
298use winnow::combinator::alt;
299use winnow::combinator::cut_err;
300use winnow::combinator::delimited;
301use winnow::combinator::fail;
302use winnow::combinator::opt;
303use winnow::combinator::preceded;
304use winnow::combinator::separated;
305use winnow::combinator::seq;
306use winnow::error::ContextError;
307use winnow::error::ErrMode;
308use winnow::error::StrContext;
309use winnow::error::StrContextValue;
310use winnow::stream::Stream;
311use winnow::token::take_while;
312use winnow::ModalResult;
313use winnow::Parser;
314
315#[derive(Debug, PartialEq)]
316pub enum Attribute {
317    BSS,
318    Origin { address: u64 },
319    Obj { address: Option<u64> },
320    Over { group: String },
321    Word,
322    File { filename: String },
323    Size { maxsize: u64 },
324}
325
326/// An expression in a linker script
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub enum Expression {
329    /// Integer constant: `1234`, `$ABCD`, `%1010`
330    Constant(u64),
331
332    /// Symbol reference: `BUFFER_START`, `_end`
333    Symbol(String),
334
335    /// Binary operation: `a + b`, `x & $FF`
336    Binary {
337        left: Box<Expression>,
338        op: BinaryOp,
339        right: Box<Expression>,
340    },
341
342    /// Unary operation: `-x`, `~flags`
343    Unary {
344        op: UnaryOp,
345        operand: Box<Expression>,
346    },
347
348    /// Parenthesized expression: `(a + b)`
349    Parens(Box<Expression>),
350
351    /// Function call: `sectstart(text)`, `sectbase(1)`
352    Function { name: String, arg: Box<Expression> },
353}
354
355impl fmt::Display for Expression {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        match self {
358            Expression::Constant(n) => write!(f, "${:x}", n),
359            Expression::Symbol(s) => write!(f, "{}", s),
360            Expression::Binary { left, op, right } => {
361                write!(f, "({} {} {})", left, op, right)
362            }
363            Expression::Unary { op, operand } => write!(f, "({}{})", op, operand),
364            Expression::Parens(expr) => write!(f, "({})", expr),
365            Expression::Function { name, arg } => write!(f, "{}({})", name, arg),
366        }
367    }
368}
369
370/// Binary operators
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum BinaryOp {
373    // Arithmetic
374    Add, // +
375    Sub, // -
376    Mul, // *
377    Div, // /
378    Mod, // %
379
380    // Bitwise
381    And, // &
382    Or,  // |
383    Xor, // ^
384    Shl, // <<
385    Shr, // >>
386
387    // Comparison
388    Eq, // ==
389    Ne, // !=
390    Lt, // <
391    Le, // <=
392    Gt, // >
393    Ge, // >=
394
395    // Logical
396    LogAnd, // &&
397    LogOr,  // ||
398}
399
400impl fmt::Display for BinaryOp {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        let s = match self {
403            BinaryOp::Add => "+",
404            BinaryOp::Sub => "-",
405            BinaryOp::Mul => "*",
406            BinaryOp::Div => "/",
407            BinaryOp::Mod => "%",
408            BinaryOp::And => "&",
409            BinaryOp::Or => "|",
410            BinaryOp::Xor => "^",
411            BinaryOp::Shl => "<<",
412            BinaryOp::Shr => ">>",
413            BinaryOp::Eq => "==",
414            BinaryOp::Ne => "!=",
415            BinaryOp::Lt => "<",
416            BinaryOp::Le => "<=",
417            BinaryOp::Gt => ">",
418            BinaryOp::Ge => ">=",
419            BinaryOp::LogAnd => "&&",
420            BinaryOp::LogOr => "||",
421        };
422        write!(f, "{}", s)
423    }
424}
425
426/// Unary operators
427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
428pub enum UnaryOp {
429    Neg,    // -
430    Not,    // ~
431    LogNot, // !
432}
433
434impl fmt::Display for UnaryOp {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        let s = match self {
437            UnaryOp::Neg => "-",
438            UnaryOp::Not => "~",
439            UnaryOp::LogNot => "!",
440        };
441        write!(f, "{}", s)
442    }
443}
444
445/// Operator precedence (higher = binds tighter)
446#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
447struct Precedence(u8);
448
449impl Precedence {
450    const LOWEST: Self = Self(0);
451    const LOGICAL_OR: Self = Self(1); // ||
452    const LOGICAL_AND: Self = Self(2); // &&
453    const BITWISE_OR: Self = Self(3); // |
454    const BITWISE_XOR: Self = Self(4); // ^
455    const BITWISE_AND: Self = Self(5); // &
456    const EQUALITY: Self = Self(6); // == !=
457    const COMPARISON: Self = Self(7); // < <= > >=
458    const SHIFT: Self = Self(8); // << >>
459    const ADDITIVE: Self = Self(9); // + -
460    const MULTIPLICATIVE: Self = Self(10); // * / %
461                                           // n.b.! currently unused
462                                           // const UNARY: Self = Self(11); // - ~ !
463                                           // const CALL: Self = Self(12); // function()
464}
465
466impl BinaryOp {
467    fn precedence(self) -> Precedence {
468        match self {
469            BinaryOp::LogOr => Precedence::LOGICAL_OR,
470            BinaryOp::LogAnd => Precedence::LOGICAL_AND,
471            BinaryOp::Or => Precedence::BITWISE_OR,
472            BinaryOp::Xor => Precedence::BITWISE_XOR,
473            BinaryOp::And => Precedence::BITWISE_AND,
474            BinaryOp::Eq | BinaryOp::Ne => Precedence::EQUALITY,
475            BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => Precedence::COMPARISON,
476            BinaryOp::Shl | BinaryOp::Shr => Precedence::SHIFT,
477            BinaryOp::Add | BinaryOp::Sub => Precedence::ADDITIVE,
478            BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => Precedence::MULTIPLICATIVE,
479        }
480    }
481
482    fn is_left_associative(self) -> bool {
483        true // All binary ops in linker scripts are left-associative
484    }
485}
486
487#[derive(Debug, PartialEq)]
488pub enum Size {
489    Byte,
490    Word,
491    Long,
492}
493
494#[derive(Debug, PartialEq)]
495pub enum Command {
496    /// Include an object file
497    Include {
498        filename: String,
499    },
500
501    /// Include a library file
502    IncLib {
503        filename: String,
504    },
505
506    /// Specify the origin address
507    Origin {
508        address: u64,
509    },
510
511    /// Specify the workspace address
512    Workspace {
513        address: u64,
514    },
515
516    Equals {
517        left: String,
518        right: Expression,
519    },
520
521    Regs {
522        register: String,
523        expression: Expression,
524    },
525
526    Group {
527        name: String,
528        attributes: Vec<Attribute>,
529    },
530
531    Section {
532        name: String,
533        group: Option<String>,
534        attributes: Vec<Attribute>,
535    },
536
537    Alias {
538        name: String,
539        target: String,
540    },
541
542    Unit {
543        unitnum: u64,
544    },
545
546    Global {
547        symbols: Vec<String>,
548    },
549
550    XDef {
551        symbols: Vec<String>,
552    },
553
554    XRef {
555        symbols: Vec<String>,
556    },
557
558    Public {
559        public: bool,
560    },
561
562    DC {
563        size: Size,
564        expression: Vec<Expression>,
565    },
566}
567
568fn parse_file_name(input: &mut &str) -> ModalResult<String> {
569    let s = take_while(1.., |c| c != '"').parse_next(input)?;
570    Ok(s.to_string())
571}
572
573fn parse_symbol(input: &mut &str) -> ModalResult<String> {
574    let s = (seq!(
575        take_while(1, (('a'..='z'), ('A'..='Z'), '_')),
576        take_while(0.., (('a'..='z'), ('A'..='Z'), ('0'..='9'), '?', '_', '.'))
577    ))
578    .parse_next(input)?;
579    Ok(format!("{}{}", s.0, s.1))
580}
581
582fn parse_bin_digits(input: &mut &str) -> ModalResult<u64> {
583    let digits = take_while(1.., '0'..='1').parse_next(input)?;
584    match u64::from_str_radix(digits, 2) {
585        Ok(i) => Ok(i),
586        Err(_e) => Err(ErrMode::Cut(ContextError::new())),
587    }
588}
589
590fn parse_decimal_digits(input: &mut &str) -> ModalResult<u64> {
591    let digits = digit1.parse_next(input)?;
592    match digits.parse::<u64>() {
593        Ok(i) => Ok(i),
594        Err(_e) => Err(ErrMode::Cut(ContextError::new())),
595    }
596}
597
598fn parse_hex_digits(input: &mut &str) -> ModalResult<u64> {
599    let digits = hex_digit1.parse_next(input)?;
600    match u64::from_str_radix(digits, 16) {
601        Ok(i) => Ok(i),
602        Err(_e) => Err(ErrMode::Cut(ContextError::new())),
603    }
604}
605
606fn parse_prefixed_digits(input: &mut &str) -> ModalResult<u64> {
607    let i = alt((
608        ('$', cut_err(parse_hex_digits)),
609        ('%', cut_err(parse_bin_digits)),
610        fail.context(StrContext::Label("integer constant")),
611    ))
612    .parse_next(input)?;
613
614    Ok(i.1)
615}
616
617fn parse_integer_constant(input: &mut &str) -> ModalResult<u64> {
618    alt((
619        parse_decimal_digits,
620        parse_prefixed_digits,
621        fail.context(StrContext::Label("integer constant")),
622    ))
623    .parse_next(input)
624}
625
626fn parse_symbol_list(input: &mut &str) -> ModalResult<Vec<String>> {
627    separated(1.., parse_symbol, (space0, ',', space0)).parse_next(input)
628}
629
630// Parse known function names
631fn parse_function_name(input: &mut &str) -> ModalResult<String> {
632    alt((
633        "sectstart",
634        "sectend",
635        "sectbase",
636        "sectof",
637        "offs",
638        "bank",
639        "groupstart",
640        "groupof",
641        "grouporg",
642        "seg",
643    ))
644    .map(|s: &str| s.to_lowercase())
645    .parse_next(input)
646}
647
648/// Parse a primary expression (atomic unit)
649fn parse_primary(input: &mut &str) -> ModalResult<Expression> {
650    preceded(
651        space0,
652        alt((
653            // Function call: func(expr)
654            (parse_function_name, delimited('(', parse_expression, ')')).map(|(name, arg)| {
655                Expression::Function {
656                    name,
657                    arg: Box::new(arg),
658                }
659            }),
660            // Parenthesized expression: (expr)
661            delimited('(', parse_expression, ')').map(|expr| Expression::Parens(Box::new(expr))),
662            // Integer constant
663            parse_integer_constant.map(Expression::Constant),
664            // Symbol
665            parse_symbol.map(Expression::Symbol),
666            // Error fallback
667            fail.context(StrContext::Label("expression")),
668        )),
669    )
670    .parse_next(input)
671}
672
673/// Parse a unary expression
674fn parse_unary(input: &mut &str) -> ModalResult<Expression> {
675    preceded(
676        space0,
677        alt((
678            // Unary operators
679            preceded('-', cut_err(parse_unary)).map(|operand| Expression::Unary {
680                op: UnaryOp::Neg,
681                operand: Box::new(operand),
682            }),
683            preceded('~', cut_err(parse_unary)).map(|operand| Expression::Unary {
684                op: UnaryOp::Not,
685                operand: Box::new(operand),
686            }),
687            preceded('!', cut_err(parse_unary)).map(|operand| Expression::Unary {
688                op: UnaryOp::LogNot,
689                operand: Box::new(operand),
690            }),
691            // Primary expression
692            parse_primary,
693        )),
694    )
695    .parse_next(input)
696}
697
698/// Parse binary operator
699fn parse_binary_op(input: &mut &str) -> ModalResult<BinaryOp> {
700    preceded(
701        space0,
702        alt((
703            // Two-character operators (must come before single-char)
704            "<<".value(BinaryOp::Shl),
705            ">>".value(BinaryOp::Shr),
706            "==".value(BinaryOp::Eq),
707            "!=".value(BinaryOp::Ne),
708            "<=".value(BinaryOp::Le),
709            ">=".value(BinaryOp::Ge),
710            "&&".value(BinaryOp::LogAnd),
711            "||".value(BinaryOp::LogOr),
712            // Single-character operators
713            '+'.value(BinaryOp::Add),
714            '-'.value(BinaryOp::Sub),
715            '*'.value(BinaryOp::Mul),
716            '/'.value(BinaryOp::Div),
717            '%'.value(BinaryOp::Mod),
718            '&'.value(BinaryOp::And),
719            '|'.value(BinaryOp::Or),
720            '^'.value(BinaryOp::Xor),
721            '<'.value(BinaryOp::Lt),
722            '>'.value(BinaryOp::Gt),
723        )),
724    )
725    .parse_next(input)
726}
727
728/// Parse a binary expression using precedence climbing
729///
730/// This implements a Pratt parser, which handles operator precedence
731/// and associativity elegantly.
732fn parse_binary_rhs(
733    input: &mut &str,
734    min_precedence: Precedence,
735    mut lhs: Expression,
736) -> ModalResult<Expression> {
737    loop {
738        // Try to parse an operator
739        let checkpoint = input.checkpoint();
740        let op = match opt(parse_binary_op).parse_next(input) {
741            Ok(Some(op)) => op,
742            Ok(None) => {
743                // No more operators
744                break;
745            }
746            Err(e) => return Err(e),
747        };
748
749        let precedence = op.precedence();
750
751        // If this operator has lower precedence than what we're looking for,
752        // backtrack and return what we have
753        if precedence < min_precedence {
754            input.reset(&checkpoint);
755            break;
756        }
757
758        // Parse the right-hand side
759        let mut rhs = parse_unary(input)?;
760
761        // Look ahead to see if the next operator has higher precedence
762        loop {
763            let checkpoint2 = input.checkpoint();
764            let next_op = match opt(parse_binary_op).parse_next(input) {
765                Ok(Some(op)) => op,
766                Ok(None) => {
767                    break;
768                }
769                Err(e) => return Err(e),
770            };
771
772            let next_precedence = next_op.precedence();
773
774            // If next operator has higher precedence (or same precedence but right-associative),
775            // recursively parse the RHS with higher precedence requirement
776            if next_precedence > precedence
777                || (next_precedence == precedence && !next_op.is_left_associative())
778            {
779                input.reset(&checkpoint2);
780                rhs = parse_binary_rhs(input, next_precedence, rhs)?;
781            } else {
782                // Next operator has lower/equal precedence, stop lookahead
783                input.reset(&checkpoint2);
784                break;
785            }
786        }
787
788        // Build the binary expression
789        lhs = Expression::Binary {
790            left: Box::new(lhs),
791            op,
792            right: Box::new(rhs),
793        };
794    }
795
796    Ok(lhs)
797}
798
799/// Parse a complete expression
800pub fn parse_expression(input: &mut &str) -> ModalResult<Expression> {
801    let lhs = parse_unary(input)?;
802    parse_binary_rhs(input, Precedence::LOWEST, lhs)
803}
804
805fn parse_command_generic_filename(command: &str, input: &mut &str) -> ModalResult<String> {
806    let c = (
807        space0,
808        Caseless(command),
809        space1,
810        "\"",
811        parse_file_name,
812        "\"",
813    )
814        .parse_next(input)?;
815    Ok(c.4.to_string())
816}
817
818fn parse_command_include(input: &mut &str) -> ModalResult<Command> {
819    let filename = parse_command_generic_filename("include", input)?;
820    Ok(Command::Include { filename })
821}
822
823fn parse_command_inclib(input: &mut &str) -> ModalResult<Command> {
824    let filename = parse_command_generic_filename("inclib", input)?;
825    Ok(Command::IncLib { filename })
826}
827
828fn parse_command_origin(input: &mut &str) -> ModalResult<Command> {
829    let c = (space0, Caseless("org"), space1, parse_integer_constant).parse_next(input)?;
830    Ok(Command::Origin { address: c.3 })
831}
832
833fn parse_command_workspace(input: &mut &str) -> ModalResult<Command> {
834    let c = (
835        space0,
836        Caseless("workspace"),
837        space1,
838        parse_integer_constant,
839    )
840        .parse_next(input)?;
841    Ok(Command::Workspace { address: c.3 })
842}
843
844fn parse_command_equals(input: &mut &str) -> ModalResult<Command> {
845    let c = (
846        space0,
847        parse_symbol,
848        alt(((space0, "=", space0), (space1, "EQU", space1))),
849        parse_expression,
850        space0,
851    )
852        .parse_next(input)?;
853
854    Ok(Command::Equals {
855        left: c.1,
856        right: c.3,
857    })
858}
859
860fn parse_command_regs(input: &mut &str) -> ModalResult<Command> {
861    let c = (
862        space0,
863        Caseless("regs"),
864        space1,
865        parse_symbol,
866        "=",
867        parse_expression,
868    )
869        .parse_next(input)?;
870
871    Ok(Command::Regs {
872        register: c.3,
873        expression: c.5,
874    })
875}
876
877fn parse_attribute_bss(input: &mut &str) -> ModalResult<Attribute> {
878    Caseless("bss").parse_next(input)?;
879    Ok(Attribute::BSS)
880}
881
882fn parse_attribute_org(input: &mut &str) -> ModalResult<Attribute> {
883    let c = (Caseless("org"), "(", parse_integer_constant, ")").parse_next(input)?;
884    Ok(Attribute::Origin { address: c.2 })
885}
886
887fn parse_attribute_obj(input: &mut &str) -> ModalResult<Attribute> {
888    let c = (Caseless("obj"), "(", opt(parse_integer_constant), ")").parse_next(input)?;
889    Ok(Attribute::Obj { address: c.2 })
890}
891
892fn parse_attribute_over(input: &mut &str) -> ModalResult<Attribute> {
893    let c = (Caseless("over"), "(", parse_symbol, ")").parse_next(input)?;
894    Ok(Attribute::Over { group: c.2 })
895}
896
897fn parse_attribute_word(input: &mut &str) -> ModalResult<Attribute> {
898    Caseless("word").parse_next(input)?;
899    Ok(Attribute::Word)
900}
901
902fn parse_attribute_file(input: &mut &str) -> ModalResult<Attribute> {
903    let c = (Caseless("file"), "(\"", parse_file_name, "\")").parse_next(input)?;
904    Ok(Attribute::File {
905        filename: c.2.to_string(),
906    })
907}
908
909fn parse_attribute_size(input: &mut &str) -> ModalResult<Attribute> {
910    let c = (Caseless("size"), "(", parse_integer_constant, ")").parse_next(input)?;
911    Ok(Attribute::Size { maxsize: c.2 })
912}
913
914fn parse_attribute(input: &mut &str) -> ModalResult<Attribute> {
915    alt((
916        parse_attribute_bss,
917        parse_attribute_org,
918        parse_attribute_obj,
919        parse_attribute_over,
920        parse_attribute_word,
921        parse_attribute_file,
922        parse_attribute_size,
923    ))
924    .parse_next(input)
925}
926
927fn parse_attribute_list(input: &mut &str) -> ModalResult<Vec<Attribute>> {
928    separated(0.., parse_attribute, (space0, ',', space0)).parse_next(input)
929}
930
931fn parse_optional_attribute_list(input: &mut &str) -> ModalResult<Vec<Attribute>> {
932    let c = opt((space1, parse_attribute_list)).parse_next(input)?;
933    Ok(c.map_or_else(Vec::new, |(_, attr_list)| attr_list))
934}
935
936fn parse_command_group(input: &mut &str) -> ModalResult<Command> {
937    let c = (
938        space0,
939        parse_symbol,
940        space1,
941        Caseless("group"),
942        parse_optional_attribute_list,
943    )
944        .parse_next(input)?;
945
946    Ok(Command::Group {
947        name: c.1,
948        attributes: c.4,
949    })
950}
951
952fn parse_command_section_with_attributes(input: &mut &str) -> ModalResult<Command> {
953    let c = (
954        space0,
955        parse_symbol,
956        space1,
957        Caseless("section"),
958        parse_optional_attribute_list,
959    )
960        .parse_next(input)?;
961
962    Ok(Command::Section {
963        name: c.1,
964        group: None,
965        attributes: c.4,
966    })
967}
968
969fn parse_command_section_with_name(input: &mut &str) -> ModalResult<Command> {
970    let c = (
971        space0,
972        Caseless("section"),
973        space1,
974        parse_symbol,
975        opt((",", parse_symbol)),
976    )
977        .parse_next(input)?;
978
979    let group = c.4.map(|(_, group)| group);
980
981    Ok(Command::Section {
982        name: c.3,
983        group,
984        attributes: vec![],
985    })
986}
987
988fn parse_command_section(input: &mut &str) -> ModalResult<Command> {
989    alt((
990        parse_command_section_with_attributes,
991        parse_command_section_with_name,
992    ))
993    .parse_next(input)
994}
995
996fn parse_command_alias(input: &mut &str) -> ModalResult<Command> {
997    let c = (
998        space0,
999        parse_symbol,
1000        space1,
1001        Caseless("alias"),
1002        space1,
1003        parse_symbol,
1004    )
1005        .parse_next(input)?;
1006
1007    Ok(Command::Alias {
1008        name: c.1,
1009        target: c.5,
1010    })
1011}
1012
1013fn parse_command_unit(input: &mut &str) -> ModalResult<Command> {
1014    let c = (space0, Caseless("unit"), space1, parse_integer_constant).parse_next(input)?;
1015
1016    Ok(Command::Unit { unitnum: c.3 })
1017}
1018
1019fn parse_command_public(input: &mut &str) -> ModalResult<Command> {
1020    let c = (
1021        space0,
1022        Caseless("public"),
1023        space1,
1024        alt((Caseless("on"), Caseless("off")))
1025            .map(|s: &str| s.to_lowercase())
1026            .context(StrContext::Label("public"))
1027            .context(StrContext::Expected(StrContextValue::Description(
1028                "on or off",
1029            ))),
1030    )
1031        .parse_next(input)?;
1032
1033    Ok(Command::Public {
1034        public: c.3 == "on",
1035    })
1036}
1037
1038fn parse_command_generic_symbol_list(command: &str, input: &mut &str) -> ModalResult<Vec<String>> {
1039    let c = (space0, Caseless(command), space1, parse_symbol_list).parse_next(input)?;
1040    Ok(c.3)
1041}
1042
1043fn parse_command_global(input: &mut &str) -> ModalResult<Command> {
1044    let symbols = parse_command_generic_symbol_list("global", input)?;
1045    Ok(Command::Global { symbols })
1046}
1047
1048fn parse_command_xdef(input: &mut &str) -> ModalResult<Command> {
1049    let symbols = parse_command_generic_symbol_list("xdef", input)?;
1050    Ok(Command::XDef { symbols })
1051}
1052
1053fn parse_command_xref(input: &mut &str) -> ModalResult<Command> {
1054    let symbols = parse_command_generic_symbol_list("xref", input)?;
1055    Ok(Command::XRef { symbols })
1056}
1057
1058#[derive(Debug)]
1059pub struct Comment {
1060    pub comment: String,
1061}
1062
1063fn parse_comment(input: &mut &str) -> ModalResult<Comment> {
1064    let c = (space0, ";", space0, take_while(0.., |c| c != '\n')).parse_next(input)?;
1065    Ok(Comment {
1066        comment: c.3.into(),
1067    })
1068}
1069
1070pub fn parse_line(input: &mut &str) -> ModalResult<(Option<Command>, Option<Comment>)> {
1071    let command = opt(alt((
1072        parse_command_include,
1073        parse_command_inclib,
1074        parse_command_origin,
1075        parse_command_workspace,
1076        parse_command_equals,
1077        parse_command_regs,
1078        parse_command_group,
1079        parse_command_section,
1080        parse_command_alias,
1081        parse_command_unit,
1082        parse_command_global,
1083        parse_command_xdef,
1084        parse_command_xref,
1085        parse_command_public,
1086    )))
1087    .parse_next(input)?;
1088
1089    let comment = opt(parse_comment).parse_next(input)?;
1090
1091    Ok((command, comment))
1092}
1093
1094#[cfg(test)]
1095mod test {
1096    use super::*;
1097
1098    fn parse_command(input: &str) -> Command {
1099        let mut input = input;
1100        parse_line.parse_next(&mut input).unwrap().0.unwrap()
1101    }
1102
1103    #[test]
1104    fn test_parse_integer_constant() {
1105        let mut input = "1234";
1106        let output = parse_integer_constant.parse_next(&mut input).unwrap();
1107        assert_eq!(1234, output);
1108
1109        let mut input = "$1234";
1110        let output = parse_integer_constant.parse_next(&mut input).unwrap();
1111        assert_eq!(0x1234, output);
1112
1113        let mut input = "%1010";
1114        let output = parse_integer_constant.parse_next(&mut input).unwrap();
1115        assert_eq!(10, output);
1116    }
1117
1118    #[test]
1119    fn test_parse_command_include() {
1120        let output = parse_command("include \"foo.obj\"");
1121
1122        match output {
1123            Command::Include { filename } => assert_eq!("foo.obj", filename),
1124            _ => panic!("unexpected output: {:?}", output),
1125        }
1126    }
1127
1128    #[test]
1129    fn test_parse_command_inclib() {
1130        let output = parse_command("inclib \"bar.lib\"");
1131
1132        match output {
1133            Command::IncLib { filename } => assert_eq!("bar.lib", filename),
1134            _ => panic!("unexpected output: {:?}", output),
1135        }
1136    }
1137
1138    #[test]
1139    fn test_parse_command_org() {
1140        let output = parse_command("org 1234");
1141        match output {
1142            Command::Origin { address } => assert_eq!(1234, address),
1143            _ => panic!("unexpected output: {:?}", output),
1144        }
1145
1146        let output = parse_command("org $1234");
1147        match output {
1148            Command::Origin { address } => assert_eq!(0x1234, address),
1149            _ => panic!("unexpected output: {:?}", output),
1150        }
1151
1152        let output = parse_command("org %1010");
1153        match output {
1154            Command::Origin { address } => assert_eq!(10, address),
1155            _ => panic!("unexpected output: {:?}", output),
1156        }
1157    }
1158
1159    #[test]
1160    fn test_parse_command_workspace() {
1161        let output = parse_command("workspace 1234");
1162        match output {
1163            Command::Workspace { address } => assert_eq!(1234, address),
1164            _ => panic!("unexpected output: {:?}", output),
1165        }
1166
1167        let output = parse_command("workspace $1234");
1168        match output {
1169            Command::Workspace { address } => assert_eq!(0x1234, address),
1170            _ => panic!("unexpected output: {:?}", output),
1171        }
1172
1173        let output = parse_command("workspace %1010");
1174        match output {
1175            Command::Workspace { address } => assert_eq!(10, address),
1176            _ => panic!("unexpected output: {:?}", output),
1177        }
1178    }
1179
1180    #[test]
1181    fn test_parse_command_equals() {
1182        let output = parse_command("foo = bar");
1183        match output {
1184            Command::Equals { left, right } => {
1185                assert_eq!("foo", left);
1186                let Expression::Symbol(symbol) = right else {
1187                    panic!("unexpected value: {:?}", right);
1188                };
1189                assert_eq!("bar", symbol);
1190            }
1191            _ => panic!("unexpected output: {:?}", output),
1192        }
1193    }
1194
1195    #[test]
1196    fn test_parse_command_regs() {
1197        let output = parse_command("regs pc=ENTRY_POINT");
1198
1199        match output {
1200            Command::Regs {
1201                register,
1202                expression,
1203            } => {
1204                assert_eq!("pc", register);
1205                let Expression::Symbol(symbol) = expression else {
1206                    panic!("unexpected value: {:?}", expression);
1207                };
1208                assert_eq!("ENTRY_POINT", symbol);
1209            }
1210            _ => panic!("unexpected output: {:?}", output),
1211        }
1212    }
1213
1214    #[test]
1215    fn parse_command_group() {
1216        let output = parse_command("anim group");
1217
1218        match output {
1219            Command::Group { name, attributes } => {
1220                assert_eq!("anim", name);
1221                assert!(attributes.is_empty());
1222            }
1223            _ => panic!("unexpected output: {:?}", output),
1224        }
1225
1226        let output = parse_command("anim group bss");
1227
1228        match output {
1229            Command::Group { name, attributes } => {
1230                assert_eq!("anim", name);
1231                assert_eq!(vec![Attribute::BSS], attributes);
1232            }
1233            _ => panic!("unexpected output: {:?}", output),
1234        }
1235    }
1236
1237    #[test]
1238    fn test_parse_command_section() {
1239        let output = parse_command("anim section");
1240
1241        match output {
1242            Command::Section {
1243                name,
1244                group: _,
1245                attributes,
1246            } => {
1247                assert_eq!("anim", name);
1248                assert!(attributes.is_empty());
1249            }
1250            _ => panic!("unexpected output: {:?}", output),
1251        }
1252
1253        let output = parse_command("anim section bss");
1254
1255        let Command::Section {
1256            name,
1257            group: _,
1258            attributes,
1259        } = output
1260        else {
1261            panic!("unexpected output: {:?}", output);
1262        };
1263        assert_eq!("anim", name);
1264        assert_eq!(vec![Attribute::BSS], attributes);
1265
1266        let output = parse_command("section anim");
1267        let Command::Section {
1268            name,
1269            group,
1270            attributes,
1271        } = output
1272        else {
1273            panic!("unexpected output: {:?}", output);
1274        };
1275        assert_eq!("anim", name);
1276        assert!(group.is_none());
1277        assert!(attributes.is_empty());
1278
1279        let output = parse_command("section anim,squares");
1280        let Command::Section {
1281            name,
1282            group,
1283            attributes,
1284        } = output
1285        else {
1286            panic!("unexpected output: {:?}", output);
1287        };
1288        assert_eq!("anim", name);
1289        let Some(group) = group else {
1290            panic!("unexpected output: {:?}", group);
1291        };
1292        assert_eq!("squares".to_string(), group);
1293        assert!(attributes.is_empty());
1294    }
1295
1296    #[test]
1297    fn test_parse_command_alias() {
1298        let output = parse_command("foo alias bar");
1299        let Command::Alias { name, target } = output else {
1300            panic!("unexpected output: {:?}", output);
1301        };
1302        assert_eq!("foo".to_string(), name);
1303        assert_eq!("bar".to_string(), target);
1304    }
1305
1306    #[test]
1307    fn test_parse_command_unit() {
1308        let output = parse_command("unit %1010");
1309        let Command::Unit { unitnum } = output else {
1310            panic!("unexpected output: {:?}", output);
1311        };
1312        assert_eq!(10, unitnum);
1313    }
1314
1315    #[test]
1316    fn test_parse_command_global() {
1317        let output = parse_command("global foo");
1318
1319        match output {
1320            Command::Global { symbols } => assert_eq!(vec!["foo".to_string()], symbols),
1321            _ => panic!("unexpected output: {:?}", output),
1322        }
1323
1324        let output = parse_command("global foo, bar , baz");
1325
1326        match output {
1327            Command::Global { symbols } => assert_eq!(
1328                vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),],
1329                symbols
1330            ),
1331            _ => panic!("unexpected output: {:?}", output),
1332        }
1333    }
1334
1335    #[test]
1336    fn test_parse_command_xdef() {
1337        let output = parse_command("xdef foo, bar, baz");
1338
1339        match output {
1340            Command::XDef { symbols } => assert_eq!(
1341                vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),],
1342                symbols
1343            ),
1344            _ => panic!("unexpected output: {:?}", output),
1345        }
1346    }
1347
1348    #[test]
1349    fn test_parse_command_xref() {
1350        let output = parse_command("xref foo, bar, baz");
1351
1352        match output {
1353            Command::XRef { symbols } => assert_eq!(
1354                vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),],
1355                symbols
1356            ),
1357            _ => panic!("unexpected output: {:?}", output),
1358        }
1359    }
1360
1361    #[test]
1362    fn test_parse_command_public() {
1363        let output = parse_command("public on");
1364        match output {
1365            Command::Public { public } => assert!(public),
1366            _ => panic!("unexpected output: {:?}", output),
1367        }
1368
1369        let output = parse_command("PUBLIC OFF");
1370        match output {
1371            Command::Public { public } => assert!(!public),
1372            _ => panic!("unexpected output: {:?}", output),
1373        }
1374    }
1375
1376    #[test]
1377    fn test_parse_comment() {
1378        // line with only a comment
1379        let mut input = "; hello, world!";
1380        let line = parse_line.parse_next(&mut input).unwrap();
1381
1382        assert!(line.0.is_none());
1383        assert_eq!("hello, world!", line.1.unwrap().comment);
1384
1385        // line with command & comment
1386        let mut input = "global foo; my global\nnot comment content";
1387        let line = parse_line.parse_next(&mut input).unwrap();
1388
1389        match line.0 {
1390            Some(Command::Global { symbols }) => assert_eq!(vec!["foo".to_string()], symbols),
1391            _ => panic!("unexpected output: {:?}", line),
1392        }
1393        assert_eq!("my global", line.1.unwrap().comment);
1394
1395        // line with command no comment
1396        let mut input = "global foo";
1397        let line = parse_line.parse_next(&mut input).unwrap();
1398
1399        match line.0 {
1400            Some(Command::Global { symbols }) => assert_eq!(vec!["foo".to_string()], symbols),
1401            _ => panic!("unexpected output: {:?}", line),
1402        }
1403        assert!(line.1.is_none());
1404
1405        // empty line
1406        let mut input = "   \t ";
1407        let line = parse_line.parse_next(&mut input).unwrap();
1408        assert!(line.0.is_none());
1409        assert!(line.1.is_none());
1410    }
1411
1412    #[test]
1413    fn test_parse_attribute_list() {
1414        let mut input = "bss,word,file(\"foo\")";
1415        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1416        assert_eq!(3, attributes.len());
1417
1418        assert!(matches!(attributes.first(), Some(Attribute::BSS)));
1419        assert!(matches!(attributes.get(1), Some(Attribute::Word)));
1420        let Some(Attribute::File { filename }) = attributes.get(2) else {
1421            panic!("unexpected value: {:?}", attributes.get(2));
1422        };
1423        assert_eq!("foo", filename);
1424
1425        let mut input = "";
1426        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1427        assert!(attributes.is_empty());
1428
1429        let mut input = "bss";
1430        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1431        assert_eq!(1, attributes.len());
1432        assert!(matches!(attributes.first(), Some(Attribute::BSS)));
1433
1434        let mut input = "size(42)";
1435        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1436        assert_eq!(1, attributes.len());
1437        assert!(matches!(
1438            attributes.first(),
1439            Some(Attribute::Size { maxsize: 42 })
1440        ));
1441
1442        let mut input = "over(squares)";
1443        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1444        assert_eq!(1, attributes.len());
1445        let Some(Attribute::Over { group }) = attributes.first() else {
1446            panic!("unexpected value: {:?}", attributes.first());
1447        };
1448        assert_eq!("squares", group);
1449
1450        let mut input = "org($1234)";
1451        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1452        assert_eq!(1, attributes.len());
1453        let Some(Attribute::Origin { address }) = attributes.first() else {
1454            panic!("unexpected value: {:?}", attributes.first());
1455        };
1456        assert_eq!(0x1234, *address);
1457
1458        let mut input = "obj($4567)";
1459        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1460        assert_eq!(1, attributes.len());
1461        let Some(Attribute::Obj { address }) = attributes.first() else {
1462            panic!("unexpected value: {:?}", attributes.first());
1463        };
1464        assert!(matches!(address, Some(0x4567)));
1465
1466        let mut input = "obj()";
1467        let attributes = parse_attribute_list.parse_next(&mut input).unwrap();
1468        assert_eq!(1, attributes.len());
1469        let Some(Attribute::Obj { address }) = attributes.first() else {
1470            panic!("unexpected value: {:?}", attributes.first());
1471        };
1472        assert!(address.is_none());
1473    }
1474
1475    fn parse_expr(input: &str) -> Expression {
1476        let mut input = input;
1477        parse_expression(&mut input).expect("parse failed")
1478    }
1479
1480    #[test]
1481    fn test_constant() {
1482        assert_eq!(parse_expr("42"), Expression::Constant(42));
1483        assert_eq!(parse_expr("$ABCD"), Expression::Constant(0xABCD));
1484        assert_eq!(parse_expr("%1010"), Expression::Constant(0b1010));
1485    }
1486
1487    #[test]
1488    fn test_symbol() {
1489        assert_eq!(parse_expr("foo"), Expression::Symbol("foo".into()));
1490        assert_eq!(parse_expr("_start"), Expression::Symbol("_start".into()));
1491        assert_eq!(parse_expr("var123"), Expression::Symbol("var123".into()));
1492    }
1493
1494    #[test]
1495    fn test_simple_binary() {
1496        let expr = parse_expr("1 + 2");
1497        assert_eq!(
1498            expr,
1499            Expression::Binary {
1500                left: Box::new(Expression::Constant(1)),
1501                op: BinaryOp::Add,
1502                right: Box::new(Expression::Constant(2)),
1503            }
1504        );
1505    }
1506
1507    #[test]
1508    fn test_precedence() {
1509        // 1 + 2 * 3 should parse as 1 + (2 * 3)
1510        let expr = parse_expr("1 + 2 * 3");
1511        assert_eq!(
1512            expr,
1513            Expression::Binary {
1514                left: Box::new(Expression::Constant(1)),
1515                op: BinaryOp::Add,
1516                right: Box::new(Expression::Binary {
1517                    left: Box::new(Expression::Constant(2)),
1518                    op: BinaryOp::Mul,
1519                    right: Box::new(Expression::Constant(3)),
1520                }),
1521            }
1522        );
1523    }
1524
1525    #[test]
1526    fn test_left_associativity() {
1527        // 1 - 2 - 3 should parse as (1 - 2) - 3
1528        let expr = parse_expr("1 - 2 - 3");
1529        assert_eq!(
1530            expr,
1531            Expression::Binary {
1532                left: Box::new(Expression::Binary {
1533                    left: Box::new(Expression::Constant(1)),
1534                    op: BinaryOp::Sub,
1535                    right: Box::new(Expression::Constant(2)),
1536                }),
1537                op: BinaryOp::Sub,
1538                right: Box::new(Expression::Constant(3)),
1539            }
1540        );
1541    }
1542
1543    #[test]
1544    fn test_parentheses() {
1545        // (1 + 2) * 3
1546        let expr = parse_expr("(1 + 2) * 3");
1547        assert_eq!(
1548            expr,
1549            Expression::Binary {
1550                left: Box::new(Expression::Parens(Box::new(Expression::Binary {
1551                    left: Box::new(Expression::Constant(1)),
1552                    op: BinaryOp::Add,
1553                    right: Box::new(Expression::Constant(2)),
1554                }))),
1555                op: BinaryOp::Mul,
1556                right: Box::new(Expression::Constant(3)),
1557            }
1558        );
1559    }
1560
1561    #[test]
1562    fn test_unary() {
1563        assert_eq!(
1564            parse_expr("-42"),
1565            Expression::Unary {
1566                op: UnaryOp::Neg,
1567                operand: Box::new(Expression::Constant(42)),
1568            }
1569        );
1570
1571        assert_eq!(
1572            parse_expr("~$FF"),
1573            Expression::Unary {
1574                op: UnaryOp::Not,
1575                operand: Box::new(Expression::Constant(0xFF)),
1576            }
1577        );
1578    }
1579
1580    #[test]
1581    fn test_function_call() {
1582        let expr = parse_expr("sectstart(text)");
1583        assert_eq!(
1584            expr,
1585            Expression::Function {
1586                name: "sectstart".into(),
1587                arg: Box::new(Expression::Symbol("text".into())),
1588            }
1589        );
1590    }
1591
1592    #[test]
1593    fn test_complex_expression() {
1594        // base + (offset & $FFFF) | $8000
1595        let expr = parse_expr("base + (offset & $FFFF) | $8000");
1596
1597        // Should parse as: (base + (offset & 0xFFFF)) | 0x8000
1598        // Because: | has lower precedence than + and &
1599        match expr {
1600            Expression::Binary {
1601                left,
1602                op: BinaryOp::Or,
1603                right,
1604            } => {
1605                // Right should be $8000
1606                assert_eq!(*right, Expression::Constant(0x8000));
1607
1608                // Left should be base + (offset & $FFFF)
1609                match *left {
1610                    Expression::Binary {
1611                        left: base,
1612                        op: BinaryOp::Add,
1613                        right: mask_expr,
1614                    } => {
1615                        assert_eq!(*base, Expression::Symbol("base".into()));
1616
1617                        // mask_expr should be (offset & $FFFF)
1618                        match *mask_expr {
1619                            Expression::Parens(inner) => match *inner {
1620                                Expression::Binary {
1621                                    left,
1622                                    op: BinaryOp::And,
1623                                    right,
1624                                } => {
1625                                    assert_eq!(*left, Expression::Symbol("offset".into()));
1626                                    assert_eq!(*right, Expression::Constant(0xFFFF));
1627                                }
1628                                _ => panic!("unexpected inner expression"),
1629                            },
1630                            _ => panic!("expected parenthesized expression"),
1631                        }
1632                    }
1633                    _ => panic!("unexpected left side"),
1634                }
1635            }
1636            _ => panic!("expected binary OR expression"),
1637        }
1638    }
1639
1640    #[test]
1641    fn test_bitwise_operators() {
1642        parse_expr("a & b");
1643        parse_expr("a | b");
1644        parse_expr("a ^ b");
1645        parse_expr("a << 4");
1646        parse_expr("a >> 2");
1647    }
1648
1649    #[test]
1650    fn test_comparison_operators() {
1651        parse_expr("a == b");
1652        parse_expr("a != b");
1653        parse_expr("a < b");
1654        parse_expr("a <= b");
1655        parse_expr("a > b");
1656        parse_expr("a >= b");
1657    }
1658
1659    #[test]
1660    fn test_logical_operators() {
1661        parse_expr("a && b");
1662        parse_expr("a || b");
1663        parse_expr("!a");
1664    }
1665
1666    #[test]
1667    fn test_whitespace_handling() {
1668        assert_eq!(parse_expr("1+2"), parse_expr("1 + 2"));
1669        assert_eq!(parse_expr("  1  +  2  "), parse_expr("1+2"));
1670    }
1671
1672    #[test]
1673    fn test_real_world_examples() {
1674        // From actual PSY-Q linker scripts
1675        parse_expr("BUFFER_END = BUFFER_START + $1000");
1676        parse_expr("(base & $FFFF0000) | $8000");
1677        parse_expr("sectstart(text) + $100");
1678        parse_expr("-(offset + 4)");
1679        parse_expr("~(flags | $FF)");
1680    }
1681
1682    #[test]
1683    fn test_display() {
1684        let expr = Expression::Binary {
1685            left: Box::new(Expression::Symbol("a".into())),
1686            op: BinaryOp::Add,
1687            right: Box::new(Expression::Constant(0x100)),
1688        };
1689        assert_eq!(format!("{}", expr), "(a + $100)");
1690    }
1691}