Skip to main content

xlsynth_g8r/netlist/
parse.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Token scanner and parser for gate-level netlists.
4//!
5//! For the Liberty-free structural `assign` subset used by `gv2aig`, see
6//! `src/netlist/STRUCTURAL_ASSIGNS.md`.
7
8use std::collections::{HashMap, HashSet};
9use std::fmt;
10use std::io::{BufRead, BufReader, Read};
11use string_interner::symbol::SymbolU32;
12use string_interner::{StringInterner, backend::StringBackend};
13use xlsynth::IrBits;
14
15pub type PortId = SymbolU32;
16pub type NetId = SymbolU32;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct NetIndex(pub usize);
20
21/// Index into `NetlistModule.instances`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub struct InstIndex(pub usize);
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Net {
27    pub name: NetId,
28    /// Optional (msb, lsb) width for this net.
29    pub width: Option<(u32, u32)>,
30}
31
32impl Net {
33    /// Returns the packed width of this net in bits.
34    pub fn width_bits(&self) -> usize {
35        if let Some((msb, lsb)) = self.width {
36            (u32::abs_diff(msb, lsb) as usize) + 1
37        } else {
38            1
39        }
40    }
41
42    /// Returns the declared numeric index of this net's least-significant bit.
43    pub fn declared_lsb_number(&self) -> u32 {
44        self.width.map(|(_, lsb)| lsb).unwrap_or(0)
45    }
46
47    /// Converts a declared bit number into the lsb-based offset used by
48    /// `AigBitVector` and structural-assign bookkeeping.
49    pub fn bit_offset(&self, bit_number: u32) -> Option<usize> {
50        match self.width {
51            Some((msb, lsb)) => {
52                let min_bit = msb.min(lsb);
53                let max_bit = msb.max(lsb);
54                if bit_number < min_bit || bit_number > max_bit {
55                    None
56                } else {
57                    Some(u32::abs_diff(bit_number, lsb) as usize)
58                }
59            }
60            None => {
61                if bit_number == 0 {
62                    Some(0)
63                } else {
64                    None
65                }
66            }
67        }
68    }
69
70    /// Converts an lsb-based offset back into the declared bit number.
71    pub fn bit_number(&self, bit_offset: usize) -> Option<u32> {
72        if bit_offset >= self.width_bits() {
73            return None;
74        }
75        match self.width {
76            Some((msb, lsb)) => {
77                let offset = bit_offset as u32;
78                if msb >= lsb {
79                    Some(lsb + offset)
80                } else {
81                    Some(lsb - offset)
82                }
83            }
84            None => Some(0),
85        }
86    }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct NetlistPort {
91    pub direction: PortDirection,
92    pub width: Option<(u32, u32)>, // (msb, lsb)
93    pub name: PortId,
94}
95
96/// Parsed gate-level module.
97///
98/// Invariants enforced by the parser:
99/// - `instances` is the list of instance declarations in the module body.
100/// - `instance_name` values are **unique within a module**; if the input
101///   netlist contains multiple instances with the same name, parsing fails with
102///   a `ScanError` instead of constructing a `NetlistModule`.
103/// - Gate-level parsing honors Verilog implicit-wire semantics: if an
104///   identifier is used in a net context but has not been explicitly declared,
105///   the parser (by default) synthesizes an implicit 1-bit `wire` for that
106///   name, scoped to the current module.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct NetlistModule {
109    pub name: PortId,
110    /// Range into the file-global `nets` table that belongs to this module.
111    pub net_index_range: std::ops::Range<usize>,
112    pub ports: Vec<NetlistPort>,
113    pub wires: Vec<NetIndex>,
114    pub assigns: Vec<NetlistAssign>,
115    pub instances: Vec<NetlistInstance>,
116}
117
118impl NetlistModule {
119    /// Looks up a net by name within this module's namespace.
120    pub fn find_net_index(&self, net_name: NetId, nets: &[Net]) -> Option<NetIndex> {
121        nets[self.net_index_range.clone()]
122            .iter()
123            .position(|net| net.name == net_name)
124            .map(|offset| NetIndex(self.net_index_range.start + offset))
125    }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum NetRef {
130    Simple(NetIndex),
131    BitSelect(NetIndex, u32),       // b[5]
132    PartSelect(NetIndex, u32, u32), // b[msb:lsb]
133    Literal(IrBits),
134    UnknownLiteral(usize),
135    Unconnected,
136    Concat(Vec<NetRef>), // { a, b[5], c[7:0], 1'b0 }
137}
138
139impl NetRef {
140    /// Collect all `NetIndex` values reachable from this `NetRef` into `out`.
141    pub fn collect_net_indices(&self, out: &mut Vec<NetIndex>) {
142        match self {
143            NetRef::Simple(idx) | NetRef::BitSelect(idx, _) | NetRef::PartSelect(idx, _, _) => {
144                out.push(*idx);
145            }
146            NetRef::Concat(elems) => {
147                for e in elems {
148                    e.collect_net_indices(out);
149                }
150            }
151            NetRef::Literal(_) | NetRef::UnknownLiteral(_) | NetRef::Unconnected => {}
152        }
153    }
154}
155
156/// Expression tree for the narrow structural-assign subset.
157///
158/// The parser preserves syntax here; Liberty-free `gv2aig` later applies the
159/// exact-width structural rules documented in `STRUCTURAL_ASSIGNS.md`.
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub enum AssignExpr {
162    Leaf(NetRef),
163    Not(Box<AssignExpr>),
164    And(Box<AssignExpr>, Box<AssignExpr>),
165    Or(Box<AssignExpr>, Box<AssignExpr>),
166    Xor(Box<AssignExpr>, Box<AssignExpr>),
167}
168
169impl AssignExpr {
170    /// Collect all `NetIndex` values reachable from this assign expression into
171    /// `out`.
172    pub fn collect_net_indices(&self, out: &mut Vec<NetIndex>) {
173        match self {
174            AssignExpr::Leaf(net_ref) => net_ref.collect_net_indices(out),
175            AssignExpr::Not(inner) => inner.collect_net_indices(out),
176            AssignExpr::And(lhs, rhs) | AssignExpr::Or(lhs, rhs) | AssignExpr::Xor(lhs, rhs) => {
177                lhs.collect_net_indices(out);
178                rhs.collect_net_indices(out);
179            }
180        }
181    }
182}
183
184/// Continuous assignment preserved from the parsed netlist.
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct NetlistAssign {
187    pub kind: NetlistAssignKind,
188    pub lhs: NetRef,
189    pub rhs: AssignExpr,
190    pub span: Span,
191}
192
193/// Preserved netlist statement kind represented by `NetlistAssign`.
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub enum NetlistAssignKind {
196    Continuous,
197    Tran,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct NetlistInstance {
202    pub type_name: PortId,
203    pub instance_name: PortId,
204    pub connections: Vec<(PortId, NetRef)>, // (port, net ref)
205    pub inst_lineno: u32,
206    pub inst_colno: u32,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub enum Keyword {
211    Wire,
212    Module,
213    Endmodule,
214    Input,
215    Output,
216    Inout,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub enum PortDirection {
221    Input,
222    Output,
223    Inout,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
227pub enum AnnotationValue {
228    I64(i64),
229    String(String),
230    VerilogInt { width: Option<usize>, value: IrBits },
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum TokenPayload {
235    Identifier(String),
236    Keyword(Keyword),
237    OParen,
238    CParen,
239    OBrack,
240    CBrack,
241    OBrace,
242    CBrace,
243    Colon,
244    Semi,
245    Comma,
246    Dot,
247    Equals,
248    Tilde,
249    Ampersand,
250    Pipe,
251    Caret,
252    Comment(String),
253    Annotation { key: String, value: AnnotationValue },
254    VerilogInt { width: Option<usize>, value: IrBits },
255    VerilogUnknownInt { width: Option<usize> },
256}
257
258fn is_simple_identifier(s: &str) -> bool {
259    let mut chars = s.chars();
260    match chars.next() {
261        Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
262        _ => return false,
263    }
264    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
265}
266
267impl fmt::Display for AnnotationValue {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        match self {
270            AnnotationValue::I64(i) => write!(f, "{}", i),
271            AnnotationValue::String(s) => {
272                // Annotation strings cannot contain embedded '"' per scanner rules.
273                let s = s.replace('"', "");
274                write!(f, "\"{}\"", s)
275            }
276            AnnotationValue::VerilogInt { width, value } => {
277                let v = xlsynth::IrValue::from_bits(value).to_u32().unwrap();
278                match width {
279                    Some(w) => write!(f, "{}'d{}", w, v),
280                    None => write!(f, "{}", v),
281                }
282            }
283        }
284    }
285}
286
287impl fmt::Display for TokenPayload {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        match self {
290            TokenPayload::Identifier(s) => {
291                if is_simple_identifier(s) {
292                    write!(f, "{}", s)
293                } else {
294                    // Escaped identifier: leading backslash, terminated by whitespace
295                    write!(f, "\\{} ", s)
296                }
297            }
298            TokenPayload::Keyword(Keyword::Module) => write!(f, "module"),
299            TokenPayload::Keyword(Keyword::Wire) => write!(f, "wire"),
300            TokenPayload::Keyword(Keyword::Endmodule) => write!(f, "endmodule"),
301            TokenPayload::Keyword(Keyword::Input) => write!(f, "input"),
302            TokenPayload::Keyword(Keyword::Output) => write!(f, "output"),
303            TokenPayload::Keyword(Keyword::Inout) => write!(f, "inout"),
304            TokenPayload::OParen => write!(f, "("),
305            TokenPayload::CParen => write!(f, ")"),
306            TokenPayload::OBrack => write!(f, "["),
307            TokenPayload::CBrack => write!(f, "]"),
308            TokenPayload::OBrace => write!(f, "{{"),
309            TokenPayload::CBrace => write!(f, "}}"),
310            TokenPayload::Colon => write!(f, ":"),
311            TokenPayload::Semi => write!(f, ";"),
312            TokenPayload::Comma => write!(f, ","),
313            TokenPayload::Dot => write!(f, "."),
314            TokenPayload::Equals => write!(f, "="),
315            TokenPayload::Tilde => write!(f, "~"),
316            TokenPayload::Ampersand => write!(f, "&"),
317            TokenPayload::Pipe => write!(f, "|"),
318            TokenPayload::Caret => write!(f, "^"),
319            TokenPayload::Comment(s) => write!(f, "//{}\n", s.replace('\n', " ")),
320            TokenPayload::Annotation { key, value } => {
321                write!(f, "(* {} = {} *)", key, value)
322            }
323            TokenPayload::VerilogInt { width, value } => {
324                let v = xlsynth::IrValue::from_bits(value).to_u32().unwrap();
325                match width {
326                    Some(w) => write!(f, "{}'d{}", w, v),
327                    None => write!(f, "{}", v),
328                }
329            }
330            TokenPayload::VerilogUnknownInt { width } => match width {
331                Some(w) => write!(f, "{}'hx", w),
332                None => write!(f, "'hx"),
333            },
334        }
335    }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub struct Pos {
340    pub lineno: u32,
341    pub colno: u32,
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub struct Span {
346    pub start: Pos,
347    pub limit: Pos,
348}
349
350impl Span {
351    pub fn to_human_string(&self) -> String {
352        format!(
353            "{}:{}..{}:{}",
354            self.start.lineno, self.start.colno, self.limit.lineno, self.limit.colno
355        )
356    }
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct Token {
361    pub payload: TokenPayload,
362    pub span: Span,
363}
364
365#[derive(Debug)]
366pub struct ScanError {
367    pub message: String,
368    pub span: Span,
369}
370
371pub struct TokenScanner<R: Read + 'static> {
372    reader: BufReader<R>,
373    pub pos: Pos,
374    lookahead: Option<Token>,
375    done: bool,
376    line_lookup: Box<dyn Fn(u32) -> Option<String>>, // line number -> line text
377}
378
379impl<R: Read + 'static> TokenScanner<R> {
380    /// Construct a TokenScanner with a custom line lookup callback.
381    pub fn with_line_lookup(reader: R, line_lookup: Box<dyn Fn(u32) -> Option<String>>) -> Self {
382        Self {
383            reader: BufReader::new(reader),
384            pos: Pos {
385                lineno: 1,
386                colno: 1,
387            },
388            lookahead: None,
389            done: false,
390            line_lookup,
391        }
392    }
393
394    /// Construct a TokenScanner for a file, using the file path for line
395    /// lookup.
396    pub fn from_file_with_path(reader: R, path: std::path::PathBuf) -> Self {
397        let path_clone = path.clone();
398        Self::with_line_lookup(
399            reader,
400            Box::new(move |lineno| {
401                use std::io::{BufRead, BufReader};
402                let file = std::fs::File::open(&path_clone).ok()?;
403                let reader = BufReader::new(file);
404                reader
405                    .lines()
406                    .nth((lineno - 1) as usize)
407                    .and_then(Result::ok)
408            }),
409        )
410    }
411}
412
413impl<'a> TokenScanner<std::io::Cursor<&'a [u8]>> {
414    /// Construct a TokenScanner for a string (for tests), using a Vec<String>
415    /// for line lookup.
416    pub fn from_str(input: &'a str) -> Self {
417        let lines: Vec<String> = input.lines().map(|s| s.to_string()).collect();
418        let lookup = move |lineno: u32| lines.get((lineno - 1) as usize).cloned();
419        Self::with_line_lookup(std::io::Cursor::new(input.as_bytes()), Box::new(lookup))
420    }
421}
422
423impl<R: Read + 'static> TokenScanner<R> {
424    #[inline]
425    fn peekb(&mut self) -> Option<u8> {
426        if self.done {
427            return None;
428        }
429        match self.reader.fill_buf() {
430            Ok(buf) => buf.first().copied(),
431            Err(_) => {
432                self.done = true;
433                None
434            }
435        }
436    }
437
438    #[inline]
439    fn popb(&mut self) -> Option<u8> {
440        if self.done {
441            return None;
442        }
443        match self.reader.fill_buf() {
444            Ok(buf) => {
445                if let Some(&b) = buf.first() {
446                    if b == b'\n' {
447                        self.pos.lineno += 1;
448                        self.pos.colno = 1;
449                    } else {
450                        self.pos.colno += 1;
451                    }
452                    // Consume exactly one byte.
453                    self.reader.consume(1);
454                    Some(b)
455                } else {
456                    self.done = true;
457                    None
458                }
459            }
460            Err(_) => {
461                self.done = true;
462                None
463            }
464        }
465    }
466
467    pub fn peekt(&mut self) -> Result<Option<&Token>, ScanError> {
468        if self.lookahead.is_none() && !self.done {
469            self.lookahead = self.next_token()?;
470        }
471        Ok(self.lookahead.as_ref())
472    }
473
474    pub fn popt(&mut self) -> Result<Option<Token>, ScanError> {
475        if self.lookahead.is_none() && !self.done {
476            self.lookahead = self.next_token()?;
477        }
478        Ok(self.lookahead.take())
479    }
480
481    pub fn pop_identifier_or_error(&mut self) -> Result<Token, ScanError> {
482        match self.popt()? {
483            Some(tok) => match &tok.payload {
484                TokenPayload::Identifier(_) => Ok(tok),
485                _ => Err(ScanError {
486                    message: "Expected identifier".to_string(),
487                    span: tok.span,
488                }),
489            },
490            None => Err(ScanError {
491                message: "Unexpected EOF, expected identifier".to_string(),
492                span: Span {
493                    start: self.pos,
494                    limit: self.pos,
495                },
496            }),
497        }
498    }
499
500    pub fn pop_i64_or_error(&mut self) -> Result<i64, ScanError> {
501        match self.popt()? {
502            Some(tok) => match &tok.payload {
503                TokenPayload::Annotation {
504                    value: AnnotationValue::I64(i),
505                    ..
506                } => Ok(*i),
507                _ => Err(ScanError {
508                    message: "Expected integer value".to_string(),
509                    span: tok.span,
510                }),
511            },
512            None => Err(ScanError {
513                message: "Unexpected EOF, expected integer value".to_string(),
514                span: Span {
515                    start: self.pos,
516                    limit: self.pos,
517                },
518            }),
519        }
520    }
521
522    fn error_with_context(&self, msg: &str, span: Span) -> ScanError {
523        let line = (self.line_lookup)(span.start.lineno)
524            .unwrap_or_else(|| "<line unavailable>".to_string());
525        let col = (span.start.colno as usize).saturating_sub(1);
526        log::error!("ScanError: {} @ {}", msg, span.to_human_string());
527        log::error!("{}", line);
528        log::error!("{}^", " ".repeat(col));
529        ScanError {
530            message: format!("{}", msg),
531            span,
532        }
533    }
534
535    fn pop_annotation(&mut self, start: Pos) -> Result<Token, ScanError> {
536        // We have already seen '(', next should be '*'
537        assert_eq!(self.popb(), Some(b'('));
538        assert_eq!(self.popb(), Some(b'*'));
539        // Skip whitespace
540        while let Some(b) = self.peekb() {
541            let c = b as char;
542            if c.is_whitespace() {
543                self.popb();
544            } else {
545                break;
546            }
547        }
548        // Parse key
549        let mut key = String::new();
550        while let Some(b) = self.peekb() {
551            let c = b as char;
552            if c.is_alphanumeric() || c == '_' {
553                self.popb();
554                key.push(c);
555            } else {
556                break;
557            }
558        }
559        // Skip whitespace
560        while let Some(b) = self.peekb() {
561            let c = b as char;
562            if c.is_whitespace() {
563                self.popb();
564            } else {
565                break;
566            }
567        }
568        // Expect '='
569        assert_eq!(self.popb(), Some(b'='));
570        // Skip whitespace
571        while let Some(b) = self.peekb() {
572            let c = b as char;
573            if c.is_whitespace() {
574                self.popb();
575            } else {
576                break;
577            }
578        }
579        // Parse value (string, integer, or Verilog integer)
580        let value: AnnotationValue = match self.peekb() {
581            Some(b'"') => {
582                self.popb(); // consume opening quote
583                let mut s = String::new();
584                while let Some(b) = self.popb() {
585                    let c = b as char;
586                    if c == '"' {
587                        break;
588                    }
589                    s.push(c);
590                }
591                AnnotationValue::String(s)
592            }
593            Some(b) if (b as char).is_ascii_digit() => {
594                // Check for Verilog integer: $width'$base$value
595                let mut num = String::new();
596                while let Some(b2) = self.peekb() {
597                    if (b2 as char).is_ascii_digit() {
598                        self.popb();
599                        num.push(b2 as char);
600                    } else {
601                        break;
602                    }
603                }
604                let width = if !num.is_empty() {
605                    Some(num.parse::<usize>().unwrap())
606                } else {
607                    None
608                };
609                if self.peekb() == Some(b'\'') {
610                    self.popb(); // consume '
611                    // Now parse base and value as a string until whitespace or '*' or ')'
612                    let mut base_and_value = String::new();
613                    while let Some(b2) = self.peekb() {
614                        let c2 = b2 as char;
615                        if c2.is_whitespace() || c2 == '*' || c2 == ')' {
616                            break;
617                        }
618                        self.popb();
619                        base_and_value.push(c2);
620                    }
621                    // Convert Verilog base to Rust-style
622                    let base_and_value = if let Some((_base, _rest)) = base_and_value
623                        .split_once(|c: char| c == 'b' || c == 'h' || c == 'o' || c == 'd')
624                    {
625                        let (base_char, _value_str) = base_and_value.split_at(1);
626                        let prefix = match base_char {
627                            "b" => "0b",
628                            "h" => "0x",
629                            "o" => "0o",
630                            "d" => "",
631                            _ => "",
632                        };
633                        format!("{}{}", prefix, &base_and_value[1..])
634                    } else {
635                        base_and_value.clone()
636                    };
637                    let irbits_str = if let Some(width) = width {
638                        format!("bits[{}]:{}", width, base_and_value)
639                    } else {
640                        base_and_value.clone()
641                    };
642                    let value = xlsynth::IrValue::parse_typed(&irbits_str)
643                        .unwrap()
644                        .to_bits()
645                        .unwrap();
646                    // Skip whitespace
647                    while let Some(b2) = self.peekb() {
648                        let c2 = b2 as char;
649                        if c2.is_whitespace() {
650                            self.popb();
651                        } else {
652                            break;
653                        }
654                    }
655                    // Expect '*)'
656                    if self.popb() != Some(b'*') {
657                        return Err(self.error_with_context(
658                            "Expected '*' to close annotation",
659                            Span {
660                                start,
661                                limit: self.pos,
662                            },
663                        ));
664                    }
665                    if self.popb() != Some(b')') {
666                        return Err(self.error_with_context(
667                            "Expected ')' to close annotation",
668                            Span {
669                                start,
670                                limit: self.pos,
671                            },
672                        ));
673                    }
674                    let limit = self.pos;
675                    return Ok(Token {
676                        payload: TokenPayload::Annotation {
677                            key,
678                            value: AnnotationValue::VerilogInt { width, value },
679                        },
680                        span: Span { start, limit },
681                    });
682                } else {
683                    // Not a Verilog int, treat as plain integer
684                    AnnotationValue::I64(num.parse().unwrap())
685                }
686            }
687            Some(b) if b == b'-' => {
688                let mut num = String::new();
689                num.push('-');
690                while let Some(b2) = self.peekb() {
691                    if (b2 as char).is_ascii_digit() {
692                        self.popb();
693                        num.push(b2 as char);
694                    } else {
695                        break;
696                    }
697                }
698                AnnotationValue::I64(num.parse().unwrap())
699            }
700            _ => {
701                return Err(self.error_with_context(
702                    "Expected annotation value",
703                    Span {
704                        start,
705                        limit: self.pos,
706                    },
707                ));
708            }
709        };
710        // Skip whitespace
711        while let Some(b) = self.peekb() {
712            let c = b as char;
713            if c.is_whitespace() {
714                self.popb();
715            } else {
716                break;
717            }
718        }
719        // Expect '*)'
720        if self.popb() != Some(b'*') {
721            return Err(self.error_with_context(
722                "Expected '*' to close annotation",
723                Span {
724                    start,
725                    limit: self.pos,
726                },
727            ));
728        }
729        if self.popb() != Some(b')') {
730            return Err(self.error_with_context(
731                "Expected ')' to close annotation",
732                Span {
733                    start,
734                    limit: self.pos,
735                },
736            ));
737        }
738        let limit = self.pos;
739        Ok(Token {
740            payload: TokenPayload::Annotation { key, value },
741            span: Span { start, limit },
742        })
743    }
744
745    fn pop_identifier(&mut self, start: Pos) -> Token {
746        let mut ident = String::new();
747        let mut escaped = false;
748        // Check if it's an escaped identifier
749        if self.peekb() == Some(b'\\') {
750            escaped = true;
751            // Escaped identifier: leading backslash, terminated by whitespace.
752            self.popb(); // Consume the backslash character.
753            while let Some(b) = self.peekb() {
754                let c = b as char;
755                if c.is_whitespace() {
756                    // End of escaped identifier, consume the whitespace.
757                    self.popb();
758                    break;
759                }
760                self.popb();
761                ident.push(c);
762            }
763        } else {
764            // Regular identifier: one or more [A-Za-z0-9_]. These identifiers
765            // are ASCII-only, so we can scan chunks directly from the
766            // BufReader buffer and advance by byte count.
767            loop {
768                if self.done {
769                    break;
770                }
771                match self.reader.fill_buf() {
772                    Ok(buf) => {
773                        if buf.is_empty() {
774                            self.done = true;
775                            break;
776                        }
777                        let mut consumed = 0usize;
778                        for &b in buf {
779                            let c = b as char;
780                            if c.is_alphanumeric() || c == '_' {
781                                ident.push(c);
782                                consumed += 1;
783                            } else {
784                                break;
785                            }
786                        }
787                        if consumed == 0 {
788                            // No identifier characters at the front of this
789                            // buffer; identifier is complete.
790                            break;
791                        }
792                        // Identifiers do not contain newlines, so we can
793                        // update the column using the total byte count.
794                        self.pos.colno += consumed as u32;
795                        self.reader.consume(consumed);
796                    }
797                    Err(_) => {
798                        self.done = true;
799                        break;
800                    }
801                }
802            }
803        }
804        let limit = self.pos;
805        if !escaped {
806            if let Some(kw) = Keyword::from_str(&ident) {
807                return Token {
808                    payload: TokenPayload::Keyword(kw),
809                    span: Span { start, limit },
810                };
811            }
812        }
813        Token {
814            payload: TokenPayload::Identifier(ident),
815            span: Span { start, limit },
816        }
817    }
818
819    pub fn next_token(&mut self) -> Result<Option<Token>, ScanError> {
820        // Always skip whitespace (except newlines) before any token logic
821        loop {
822            match self.peekb() {
823                Some(b) if b.is_ascii_whitespace() && b != b'\n' => {
824                    self.popb();
825                }
826                _ => break,
827            }
828        }
829        let start = self.pos;
830        let b = match self.peekb() {
831            Some(b) => b,
832            None => return Ok(None),
833        };
834        // Handle Verilog preprocessor-style directive lines we want to ignore.
835        // Currently, we skip lines that begin with `` `timescale`` anywhere in
836        // the file. This consumes through the end of the line and resumes
837        // scanning as if the directive were not present.
838        if b == b'`' {
839            let directive_start = start;
840            // consume backtick
841            self.popb();
842            // read directive word (letters/underscores)
843            let mut word = String::new();
844            while let Some(b2) = self.peekb() {
845                let c2 = b2 as char;
846                if c2.is_ascii_alphabetic() || c2 == '_' {
847                    self.popb();
848                    word.push(c2);
849                } else {
850                    break;
851                }
852            }
853            if word == "timescale" {
854                // consume until end-of-line (including the newline if present)
855                while let Some(b2) = self.popb() {
856                    if b2 == b'\n' {
857                        break;
858                    }
859                }
860                log::trace!("TokenScanner: skipped `timescale directive line");
861                // continue scanning next token
862                return match self.next_token()? {
863                    Some(tok) => Ok(Some(tok)),
864                    None => Ok(None),
865                };
866            } else {
867                // Not a supported directive: report unexpected backtick at its position
868                return Err(self.error_with_context(
869                    "Unexpected character '`'",
870                    Span {
871                        start: directive_start,
872                        limit: directive_start,
873                    },
874                ));
875            }
876        }
877        // Handle annotation: use non-consuming two-byte lookahead for "(*"
878        if b == b'(' {
879            if let Ok(buf) = self.reader.fill_buf() {
880                if buf.len() >= 2 && buf[0] == b'(' && buf[1] == b'*' {
881                    return self.pop_annotation(start).map(Some);
882                }
883            }
884        }
885        // Handle identifier/keyword
886        //
887        // Note that "escaped identifiers" can begin with the backslash character.
888        if b.is_ascii_alphabetic() || b == b'_' || b == b'\\' {
889            return Ok(Some(self.pop_identifier(start)));
890        }
891        // Handle number (plain integer literal or Verilog-style literal)
892        if b.is_ascii_digit() {
893            let mut num = String::new();
894            while let Some(b2) = self.peekb() {
895                if b2.is_ascii_digit() {
896                    self.popb();
897                    num.push(b2 as char);
898                } else {
899                    break;
900                }
901            }
902            let width = if !num.is_empty() {
903                Some(num.parse::<usize>().unwrap())
904            } else {
905                None
906            };
907            if self.peekb() == Some(b'\'') {
908                self.popb(); // consume '
909                // Now parse base and value as a string, but only consume characters
910                // that are valid inside a Verilog number literal (base char plus
911                // digits/hex digits, x/z/?, and underscores). This ensures we do
912                // not accidentally swallow structural punctuation like '}' that
913                // should be tokenized separately.
914                let mut base_and_value = String::new();
915                while let Some(b2) = self.peekb() {
916                    if b2.is_ascii_alphanumeric()
917                        || b2 == b'_'
918                        || b2 == b'x'
919                        || b2 == b'X'
920                        || b2 == b'z'
921                        || b2 == b'Z'
922                        || b2 == b'?'
923                    {
924                        self.popb();
925                        base_and_value.push(b2 as char);
926                    } else {
927                        break;
928                    }
929                }
930                let has_unknown_digit = base_and_value
931                    .chars()
932                    .skip(1)
933                    .any(|ch| matches!(ch, 'x' | 'X' | 'z' | 'Z' | '?'));
934                // Convert Verilog base to Rust-style
935                let base_and_value = if let Some((_base, _rest)) = base_and_value
936                    .split_once(|c: char| c == 'b' || c == 'h' || c == 'o' || c == 'd')
937                {
938                    let (base_char, _value_str) = base_and_value.split_at(1);
939                    let prefix = match base_char {
940                        "b" => "0b",
941                        "h" => "0x",
942                        "o" => "0o",
943                        "d" => "",
944                        _ => "",
945                    };
946                    format!("{}{}", prefix, &base_and_value[1..])
947                } else {
948                    base_and_value.clone()
949                };
950                let irbits_str = if let Some(width) = width {
951                    format!("bits[{}]:{}", width, base_and_value)
952                } else {
953                    base_and_value.clone()
954                };
955                let limit = self.pos;
956                if has_unknown_digit {
957                    return Ok(Some(Token {
958                        payload: TokenPayload::VerilogUnknownInt { width },
959                        span: Span { start, limit },
960                    }));
961                }
962                let value = xlsynth::IrValue::parse_typed(&irbits_str)
963                    .unwrap()
964                    .to_bits()
965                    .unwrap();
966                return Ok(Some(Token {
967                    payload: TokenPayload::VerilogInt { width, value },
968                    span: Span { start, limit },
969                }));
970            } else {
971                // Parse as IrBits (decimal, width 32 per Verilog standard)
972                let irbits_str = format!("bits[32]:{}", num);
973                let value = match xlsynth::IrValue::parse_typed(&irbits_str) {
974                    Ok(v) => v.to_bits().unwrap(),
975                    Err(e) => {
976                        let line = (self.line_lookup)(start.lineno)
977                            .unwrap_or_else(|| "<line unavailable>".to_string());
978                        let col = (start.colno as usize).saturating_sub(1);
979                        panic!(
980                            "Failed to parse integer literal '{}': {}\nLine {}: {}\n{}^",
981                            irbits_str,
982                            e,
983                            start.lineno,
984                            line,
985                            " ".repeat(col)
986                        );
987                    }
988                };
989                let limit = self.pos;
990                return Ok(Some(Token {
991                    payload: TokenPayload::VerilogInt { width: None, value },
992                    span: Span { start, limit },
993                }));
994            }
995        }
996        // Handle line comments
997        if b == b'/' {
998            self.popb();
999            match self.peekb() {
1000                Some(b'/') => {
1001                    self.popb();
1002                    let mut comment = String::new();
1003                    while let Some(b2) = self.popb() {
1004                        if b2 == b'\n' {
1005                            break;
1006                        }
1007                        comment.push(b2 as char);
1008                    }
1009                    let limit = self.pos;
1010                    return Ok(Some(Token {
1011                        payload: TokenPayload::Comment(comment),
1012                        span: Span { start, limit },
1013                    }));
1014                }
1015                Some(b'*') => {
1016                    // Skip block comment
1017                    self.popb(); // consume '*'
1018                    let mut prev: Option<u8> = None;
1019                    while let Some(b2) = self.popb() {
1020                        if prev == Some(b'*') && b2 == b'/' {
1021                            break;
1022                        }
1023                        prev = Some(b2);
1024                    }
1025                    // After skipping, try to get the next token
1026                    return match self.next_token()? {
1027                        Some(tok) => Ok(Some(tok)),
1028                        None => Ok(None),
1029                    };
1030                }
1031                _ => {
1032                    // Not a comment, error
1033                    let limit = self.pos;
1034                    return Err(self.error_with_context("Unexpected '/'", Span { start, limit }));
1035                }
1036            }
1037        }
1038        // Handle punctuation
1039        let payload = match b {
1040            b'(' => {
1041                self.popb();
1042                TokenPayload::OParen
1043            }
1044            b')' => {
1045                self.popb();
1046                TokenPayload::CParen
1047            }
1048            b'[' => {
1049                self.popb();
1050                TokenPayload::OBrack
1051            }
1052            b']' => {
1053                self.popb();
1054                TokenPayload::CBrack
1055            }
1056            b'{' => {
1057                self.popb();
1058                TokenPayload::OBrace
1059            }
1060            b'}' => {
1061                self.popb();
1062                TokenPayload::CBrace
1063            }
1064            b':' => {
1065                self.popb();
1066                TokenPayload::Colon
1067            }
1068            b';' => {
1069                self.popb();
1070                TokenPayload::Semi
1071            }
1072            b',' => {
1073                self.popb();
1074                TokenPayload::Comma
1075            }
1076            b'.' => {
1077                self.popb();
1078                TokenPayload::Dot
1079            }
1080            b'=' => {
1081                self.popb();
1082                TokenPayload::Equals
1083            }
1084            b'~' => {
1085                self.popb();
1086                TokenPayload::Tilde
1087            }
1088            b'&' => {
1089                self.popb();
1090                TokenPayload::Ampersand
1091            }
1092            b'|' => {
1093                self.popb();
1094                TokenPayload::Pipe
1095            }
1096            b'^' => {
1097                self.popb();
1098                TokenPayload::Caret
1099            }
1100            b'\n' => {
1101                self.popb();
1102                return match self.next_token()? {
1103                    Some(tok) => Ok(Some(tok)),
1104                    None => Ok(None),
1105                };
1106            } // skip newlines
1107            _ => {
1108                // Error for unknown token
1109                let limit = self.pos;
1110                return Err(self.error_with_context(
1111                    &format!("Unexpected character '{}'", b as char),
1112                    Span { start, limit },
1113                ));
1114            }
1115        };
1116        let limit = self.pos;
1117        Ok(Some(Token {
1118            payload,
1119            span: Span { start, limit },
1120        }))
1121    }
1122}
1123
1124impl Keyword {
1125    fn from_str(s: &str) -> Option<Self> {
1126        match s {
1127            "module" => Some(Keyword::Module),
1128            "wire" => Some(Keyword::Wire),
1129            "endmodule" => Some(Keyword::Endmodule),
1130            "input" => Some(Keyword::Input),
1131            "output" => Some(Keyword::Output),
1132            "inout" => Some(Keyword::Inout),
1133            _ => None,
1134        }
1135    }
1136}
1137
1138// --- Recursive descent parser ---
1139pub struct Parser<R: Read + 'static> {
1140    scanner: TokenScanner<R>,
1141    pub interner: StringInterner<StringBackend<SymbolU32>>,
1142    pub nets: Vec<Net>,
1143    net_index_by_name: HashMap<NetId, NetIndex>,
1144    /// Index into `nets` where the current module's nets begin.
1145    /// Used to scope name lookups so that widths are checked per-module
1146    /// instead of across the entire file.
1147    current_module_net_start: usize,
1148    /// Span where each net's current width was first determined, keyed by
1149    /// net name. Used only for diagnostics when reporting conflicting
1150    /// widths during parsing; not carried into result artifacts.
1151    net_width_span_by_name: HashMap<NetId, Span>,
1152    /// Whether the parser should synthesize implicit nets when an identifier
1153    /// is used in a net context but has not been explicitly declared.
1154    allow_implicit_nets: bool,
1155    /// Names of nets that were first introduced implicitly (via use in a
1156    /// net context) rather than via an explicit declaration. Tracked
1157    /// per-module for diagnostics; not carried into result artifacts.
1158    implicit_net_by_name: HashSet<NetId>,
1159}
1160
1161impl<R: Read + 'static> Parser<R> {
1162    /// Construct a parser with explicit control over implicit-net handling.
1163    pub fn new_with_options(scanner: TokenScanner<R>, allow_implicit_nets: bool) -> Self {
1164        Self {
1165            scanner,
1166            interner: StringInterner::new(),
1167            nets: Vec::new(),
1168            net_index_by_name: HashMap::new(),
1169            current_module_net_start: 0,
1170            net_width_span_by_name: HashMap::new(),
1171            allow_implicit_nets,
1172            implicit_net_by_name: HashSet::new(),
1173        }
1174    }
1175
1176    /// Construct a parser with implicit nets enabled (default gate-level
1177    /// behavior matching Verilog's implicit wire semantics).
1178    pub fn new(scanner: TokenScanner<R>) -> Self {
1179        Self::new_with_options(scanner, /* allow_implicit_nets= */ true)
1180    }
1181
1182    fn skip_trivia(&mut self) -> Result<(), ScanError> {
1183        loop {
1184            let peek = self.scanner.peekt()?;
1185            match peek {
1186                Some(tok) => match &tok.payload {
1187                    TokenPayload::Comment(_) | TokenPayload::Annotation { .. } => {
1188                        self.scanner.popt()?;
1189                    }
1190                    _ => break,
1191                },
1192                None => break,
1193            }
1194        }
1195        Ok(())
1196    }
1197
1198    fn parse_non_concat_netref_from_token(&mut self, net_tok: Token) -> Result<NetRef, ScanError> {
1199        match net_tok.payload {
1200            TokenPayload::Identifier(s) => {
1201                let net_sym = self.interner.get_or_intern(s);
1202                let net_idx = if let Some(&idx) = self.net_index_by_name.get(&net_sym) {
1203                    idx
1204                } else if let Some(pos) = self.nets[self.current_module_net_start..]
1205                    .iter()
1206                    .position(|n| n.name == net_sym)
1207                {
1208                    let idx = NetIndex(self.current_module_net_start + pos);
1209                    self.net_index_by_name.insert(net_sym, idx);
1210                    idx
1211                } else if self.allow_implicit_nets {
1212                    let idx = NetIndex(self.nets.len());
1213                    self.nets.push(Net {
1214                        name: net_sym,
1215                        width: Some((0, 0)),
1216                    });
1217                    self.net_index_by_name.insert(net_sym, idx);
1218                    self.net_width_span_by_name.insert(net_sym, net_tok.span);
1219                    self.implicit_net_by_name.insert(net_sym);
1220                    idx
1221                } else {
1222                    return Err(ScanError {
1223                        message: format!(
1224                            "net '{}' not declared as wire",
1225                            self.interner.resolve(net_sym).unwrap()
1226                        ),
1227                        span: net_tok.span,
1228                    });
1229                };
1230
1231                self.skip_trivia()?;
1232                if let Some(next) = self.scanner.peekt()? {
1233                    if matches!(next.payload, TokenPayload::OBrack) {
1234                        self.scanner.popt()?; // consume '['
1235                        self.skip_trivia()?;
1236                        let msb_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1237                            message: "expected msb or index in net reference".to_string(),
1238                            span: Span {
1239                                start: self.scanner.pos,
1240                                limit: self.scanner.pos,
1241                            },
1242                        })?;
1243                        let msb = match msb_tok.payload {
1244                            TokenPayload::VerilogInt { value, .. } => {
1245                                xlsynth::IrValue::from_bits(&value).to_u32().unwrap()
1246                            }
1247                            _ => {
1248                                return Err(ScanError {
1249                                    message: "expected integer for msb/index in net reference"
1250                                        .to_string(),
1251                                    span: msb_tok.span,
1252                                });
1253                            }
1254                        };
1255                        self.skip_trivia()?;
1256                        let next2 = self.scanner.popt()?.ok_or_else(|| ScanError {
1257                            message: "expected ':' or ']' in net reference".to_string(),
1258                            span: Span {
1259                                start: self.scanner.pos,
1260                                limit: self.scanner.pos,
1261                            },
1262                        })?;
1263                        return match next2.payload {
1264                            TokenPayload::Colon => {
1265                                self.skip_trivia()?;
1266                                let lsb_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1267                                    message: "expected lsb in net reference".to_string(),
1268                                    span: Span {
1269                                        start: self.scanner.pos,
1270                                        limit: self.scanner.pos,
1271                                    },
1272                                })?;
1273                                let lsb = match lsb_tok.payload {
1274                                    TokenPayload::VerilogInt { value, .. } => {
1275                                        xlsynth::IrValue::from_bits(&value).to_u32().unwrap()
1276                                    }
1277                                    _ => {
1278                                        return Err(ScanError {
1279                                            message: "expected integer for lsb in net reference"
1280                                                .to_string(),
1281                                            span: lsb_tok.span,
1282                                        });
1283                                    }
1284                                };
1285                                self.skip_trivia()?;
1286                                let cbrack_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1287                                    message: "expected ']' after part-select".to_string(),
1288                                    span: Span {
1289                                        start: self.scanner.pos,
1290                                        limit: self.scanner.pos,
1291                                    },
1292                                })?;
1293                                if !matches!(cbrack_tok.payload, TokenPayload::CBrack) {
1294                                    return Err(ScanError {
1295                                        message: "expected ']' after part-select".to_string(),
1296                                        span: cbrack_tok.span,
1297                                    });
1298                                }
1299                                Ok(NetRef::PartSelect(net_idx, msb, lsb))
1300                            }
1301                            TokenPayload::CBrack => Ok(NetRef::BitSelect(net_idx, msb)),
1302                            _ => Err(ScanError {
1303                                message: "expected ':' or ']' in net reference".to_string(),
1304                                span: next2.span,
1305                            }),
1306                        };
1307                    }
1308                }
1309                Ok(NetRef::Simple(net_idx))
1310            }
1311            TokenPayload::VerilogInt { value, width: _ } => Ok(NetRef::Literal(value)),
1312            TokenPayload::VerilogUnknownInt { width } => {
1313                Ok(NetRef::UnknownLiteral(width.unwrap_or(32)))
1314            }
1315            other => Err(ScanError {
1316                message: format!("expected identifier for net name, got {:?}", other),
1317                span: net_tok.span,
1318            }),
1319        }
1320    }
1321
1322    fn parse_netref_expr(&mut self) -> Result<NetRef, ScanError> {
1323        self.skip_trivia()?;
1324        if let Some(tok) = self.scanner.peekt()? {
1325            if matches!(tok.payload, TokenPayload::OBrace) {
1326                // Parse concatenation: { expr (, expr)* }
1327                self.scanner.popt()?; // consume '{'
1328                let mut elems: Vec<NetRef> = Vec::new();
1329                loop {
1330                    self.skip_trivia()?;
1331                    // If next is '}', end of concatenation
1332                    if let Some(tok2) = self.scanner.peekt()? {
1333                        if matches!(tok2.payload, TokenPayload::CBrace) {
1334                            self.scanner.popt()?; // consume '}'
1335                            break;
1336                        }
1337                    }
1338                    // Parse one element (identifier with optional select, literal, or nested
1339                    // concat)
1340                    let elem = self.parse_netref_expr()?;
1341                    elems.push(elem);
1342                    // Optional comma
1343                    if let Some(next) = self.scanner.peekt()? {
1344                        if matches!(next.payload, TokenPayload::Comma) {
1345                            self.scanner.popt()?; // consume ','
1346                            continue;
1347                        }
1348                    }
1349                    // Expect closing '}'
1350                    let cbrace = self.scanner.popt()?.ok_or_else(|| ScanError {
1351                        message: "expected '}' to close concatenation".to_string(),
1352                        span: Span {
1353                            start: self.scanner.pos,
1354                            limit: self.scanner.pos,
1355                        },
1356                    })?;
1357                    if !matches!(cbrace.payload, TokenPayload::CBrace) {
1358                        return Err(ScanError {
1359                            message: "expected '}' to close concatenation".to_string(),
1360                            span: cbrace.span,
1361                        });
1362                    }
1363                    break;
1364                }
1365                return Ok(NetRef::Concat(elems));
1366            }
1367        }
1368        let net_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1369            message: "expected net name".to_string(),
1370            span: Span {
1371                start: self.scanner.pos,
1372                limit: self.scanner.pos,
1373            },
1374        })?;
1375        self.parse_non_concat_netref_from_token(net_tok)
1376    }
1377
1378    fn parse_assign_primary(&mut self) -> Result<AssignExpr, ScanError> {
1379        self.skip_trivia()?;
1380        if let Some(tok) = self.scanner.peekt()? {
1381            match tok.payload {
1382                TokenPayload::OParen => {
1383                    self.scanner.popt()?;
1384                    let expr = self.parse_assign_expr()?;
1385                    self.skip_trivia()?;
1386                    let cparen = self.scanner.popt()?.ok_or_else(|| ScanError {
1387                        message: "expected ')' to close assign expression".to_string(),
1388                        span: Span {
1389                            start: self.scanner.pos,
1390                            limit: self.scanner.pos,
1391                        },
1392                    })?;
1393                    if !matches!(cparen.payload, TokenPayload::CParen) {
1394                        return Err(ScanError {
1395                            message: "expected ')' to close assign expression".to_string(),
1396                            span: cparen.span,
1397                        });
1398                    }
1399                    return Ok(expr);
1400                }
1401                TokenPayload::OBrace => {
1402                    return Ok(AssignExpr::Leaf(self.parse_netref_expr()?));
1403                }
1404                _ => {}
1405            }
1406        }
1407        let tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1408            message: "expected assign expression operand".to_string(),
1409            span: Span {
1410                start: self.scanner.pos,
1411                limit: self.scanner.pos,
1412            },
1413        })?;
1414        Ok(AssignExpr::Leaf(
1415            self.parse_non_concat_netref_from_token(tok)?,
1416        ))
1417    }
1418
1419    fn parse_assign_unary(&mut self) -> Result<AssignExpr, ScanError> {
1420        self.skip_trivia()?;
1421        if let Some(tok) = self.scanner.peekt()? {
1422            if matches!(tok.payload, TokenPayload::Tilde) {
1423                self.scanner.popt()?;
1424                let inner = self.parse_assign_unary()?;
1425                return Ok(AssignExpr::Not(Box::new(inner)));
1426            }
1427        }
1428        self.parse_assign_primary()
1429    }
1430
1431    fn parse_assign_and(&mut self) -> Result<AssignExpr, ScanError> {
1432        let mut lhs = self.parse_assign_unary()?;
1433        loop {
1434            self.skip_trivia()?;
1435            let Some(tok) = self.scanner.peekt()? else {
1436                break;
1437            };
1438            if !matches!(tok.payload, TokenPayload::Ampersand) {
1439                break;
1440            }
1441            self.scanner.popt()?;
1442            let rhs = self.parse_assign_unary()?;
1443            lhs = AssignExpr::And(Box::new(lhs), Box::new(rhs));
1444        }
1445        Ok(lhs)
1446    }
1447
1448    fn parse_assign_xor(&mut self) -> Result<AssignExpr, ScanError> {
1449        let mut lhs = self.parse_assign_and()?;
1450        loop {
1451            self.skip_trivia()?;
1452            let Some(tok) = self.scanner.peekt()? else {
1453                break;
1454            };
1455            if !matches!(tok.payload, TokenPayload::Caret) {
1456                break;
1457            }
1458            self.scanner.popt()?;
1459            let rhs = self.parse_assign_and()?;
1460            lhs = AssignExpr::Xor(Box::new(lhs), Box::new(rhs));
1461        }
1462        Ok(lhs)
1463    }
1464
1465    fn parse_assign_or(&mut self) -> Result<AssignExpr, ScanError> {
1466        let mut lhs = self.parse_assign_xor()?;
1467        loop {
1468            self.skip_trivia()?;
1469            let Some(tok) = self.scanner.peekt()? else {
1470                break;
1471            };
1472            if !matches!(tok.payload, TokenPayload::Pipe) {
1473                break;
1474            }
1475            self.scanner.popt()?;
1476            let rhs = self.parse_assign_xor()?;
1477            lhs = AssignExpr::Or(Box::new(lhs), Box::new(rhs));
1478        }
1479        Ok(lhs)
1480    }
1481
1482    fn parse_assign_expr(&mut self) -> Result<AssignExpr, ScanError> {
1483        self.parse_assign_or()
1484    }
1485
1486    /// Ensures there is a `Net` with the given `name` present in `self.nets`.
1487    /// If it already exists, reconciles optional `width` information:
1488    /// - (None, None) -> keep None
1489    /// - (Some(w), None) or (None, Some(w)) -> set to Some(w)
1490    /// - (Some(a), Some(b)) where a != b -> error
1491    /// Returns the `NetIndex` for the ensured net.
1492    fn ensure_net(
1493        &mut self,
1494        name: NetId,
1495        width: Option<(u32, u32)>,
1496        err_span: Span,
1497    ) -> Result<NetIndex, ScanError> {
1498        if let Some(&idx) = self.net_index_by_name.get(&name) {
1499            let implicit_scalar_placeholder =
1500                self.implicit_net_by_name.contains(&name) && self.nets[idx.0].width == Some((0, 0));
1501            let mut clear_implicit_marker = false;
1502            {
1503                let existing = &mut self.nets[idx.0];
1504                match (existing.width, width) {
1505                    (None, None) => {}
1506                    (None, Some(w)) => {
1507                        existing.width = Some(w);
1508                        self.net_width_span_by_name.insert(name, err_span);
1509                    }
1510                    (Some(_), None) => {
1511                        if implicit_scalar_placeholder {
1512                            self.net_width_span_by_name.insert(name, err_span);
1513                            clear_implicit_marker = true;
1514                        }
1515                    }
1516                    (Some(a), Some(b)) => {
1517                        if a != b {
1518                            if implicit_scalar_placeholder {
1519                                existing.width = Some(b);
1520                                self.net_width_span_by_name.insert(name, err_span);
1521                                clear_implicit_marker = true;
1522                            } else {
1523                                debug_assert!(
1524                                    self.net_width_span_by_name.contains_key(&name),
1525                                    "Net with known width should carry a width span for diagnostics"
1526                                );
1527                                let prev_span = self
1528                                    .net_width_span_by_name
1529                                    .get(&name)
1530                                    .copied()
1531                                    .unwrap_or(err_span);
1532                                return Err(ScanError {
1533                                    message: format!(
1534                                        "conflicting widths for net '{}': {:?} vs {:?}; previously determined width was {:?} @ {}",
1535                                        self.interner.resolve(name).unwrap_or("<unknown>"),
1536                                        a,
1537                                        b,
1538                                        a,
1539                                        prev_span.to_human_string()
1540                                    ),
1541                                    span: err_span,
1542                                });
1543                            }
1544                        } else if implicit_scalar_placeholder {
1545                            self.net_width_span_by_name.insert(name, err_span);
1546                            clear_implicit_marker = true;
1547                        }
1548                    }
1549                }
1550            }
1551            if clear_implicit_marker {
1552                self.implicit_net_by_name.remove(&name);
1553            }
1554            Ok(idx)
1555        } else {
1556            let idx = NetIndex(self.nets.len());
1557            self.nets.push(Net { name, width });
1558            if width.is_some() {
1559                self.net_width_span_by_name.insert(name, err_span);
1560            }
1561            self.net_index_by_name.insert(name, idx);
1562            Ok(idx)
1563        }
1564    }
1565
1566    /// Parses: assign <lhs> = <rhs>;
1567    ///
1568    /// The supported RHS subset is limited to bitwise `~`, `&`, `|`, and `^`
1569    /// over identifiers/literals/concats with optional selects plus
1570    /// parentheses.
1571    fn parse_assign(&mut self) -> Result<NetlistAssign, ScanError> {
1572        let t_assign = self.scanner.popt()?.ok_or_else(|| ScanError {
1573            message: "expected 'assign'".to_string(),
1574            span: Span {
1575                start: self.scanner.pos,
1576                limit: self.scanner.pos,
1577            },
1578        })?;
1579        match t_assign.payload {
1580            TokenPayload::Identifier(ref s) if s == "assign" => {}
1581            _ => {
1582                return Err(ScanError {
1583                    message: "expected 'assign'".to_string(),
1584                    span: t_assign.span,
1585                });
1586            }
1587        }
1588
1589        self.skip_trivia()?;
1590        let lhs = self.parse_netref_expr()?;
1591        if !matches!(
1592            lhs,
1593            NetRef::Simple(_)
1594                | NetRef::BitSelect(_, _)
1595                | NetRef::PartSelect(_, _, _)
1596                | NetRef::Concat(_)
1597        ) {
1598            return Err(ScanError {
1599                message: "left-hand side of assign must be an identifier, select, or concat"
1600                    .to_string(),
1601                span: t_assign.span,
1602            });
1603        }
1604
1605        self.skip_trivia()?;
1606        let t_eq = self.scanner.popt()?.ok_or_else(|| ScanError {
1607            message: "expected '=' in assign".to_string(),
1608            span: Span {
1609                start: self.scanner.pos,
1610                limit: self.scanner.pos,
1611            },
1612        })?;
1613        if !matches!(t_eq.payload, TokenPayload::Equals) {
1614            return Err(ScanError {
1615                message: "expected '=' in assign".to_string(),
1616                span: t_eq.span,
1617            });
1618        }
1619
1620        let rhs = self.parse_assign_expr()?;
1621
1622        self.skip_trivia()?;
1623        let t_semi = self.scanner.popt()?.ok_or_else(|| ScanError {
1624            message: "expected ';' after assign".to_string(),
1625            span: Span {
1626                start: self.scanner.pos,
1627                limit: self.scanner.pos,
1628            },
1629        })?;
1630        if !matches!(t_semi.payload, TokenPayload::Semi) {
1631            return Err(ScanError {
1632                message: "expected ';' after assign".to_string(),
1633                span: t_semi.span,
1634            });
1635        }
1636        Ok(NetlistAssign {
1637            kind: NetlistAssignKind::Continuous,
1638            lhs,
1639            rhs,
1640            span: Span {
1641                start: t_assign.span.start,
1642                limit: t_semi.span.limit,
1643            },
1644        })
1645    }
1646
1647    /// Parses: tran [instance_name [msb:lsb]] ( lhs, rhs );
1648    fn parse_tran(&mut self) -> Result<NetlistAssign, ScanError> {
1649        let t_tran = self.scanner.popt()?.ok_or_else(|| ScanError {
1650            message: "expected 'tran'".to_string(),
1651            span: Span {
1652                start: self.scanner.pos,
1653                limit: self.scanner.pos,
1654            },
1655        })?;
1656        if !matches!(t_tran.payload, TokenPayload::Identifier(ref s) if s == "tran") {
1657            return Err(ScanError {
1658                message: "expected 'tran'".to_string(),
1659                span: t_tran.span,
1660            });
1661        }
1662
1663        self.skip_trivia()?;
1664        if let Some(tok) = self.scanner.peekt()? {
1665            if matches!(tok.payload, TokenPayload::Identifier(_)) {
1666                self.scanner.popt()?;
1667                self.skip_trivia()?;
1668                if let Some(range_tok) = self.scanner.peekt()? {
1669                    if matches!(range_tok.payload, TokenPayload::OBrack) {
1670                        self.scanner.popt()?;
1671                        self.skip_trivia()?;
1672                        let _msb = self.scanner.popt()?.ok_or_else(|| ScanError {
1673                            message: "expected msb in tran instance range".to_string(),
1674                            span: Span {
1675                                start: self.scanner.pos,
1676                                limit: self.scanner.pos,
1677                            },
1678                        })?;
1679                        self.skip_trivia()?;
1680                        let colon = self.scanner.popt()?.ok_or_else(|| ScanError {
1681                            message: "expected ':' in tran instance range".to_string(),
1682                            span: Span {
1683                                start: self.scanner.pos,
1684                                limit: self.scanner.pos,
1685                            },
1686                        })?;
1687                        if !matches!(colon.payload, TokenPayload::Colon) {
1688                            return Err(ScanError {
1689                                message: "expected ':' in tran instance range".to_string(),
1690                                span: colon.span,
1691                            });
1692                        }
1693                        self.skip_trivia()?;
1694                        let _lsb = self.scanner.popt()?.ok_or_else(|| ScanError {
1695                            message: "expected lsb in tran instance range".to_string(),
1696                            span: Span {
1697                                start: self.scanner.pos,
1698                                limit: self.scanner.pos,
1699                            },
1700                        })?;
1701                        self.skip_trivia()?;
1702                        let cbrack = self.scanner.popt()?.ok_or_else(|| ScanError {
1703                            message: "expected ']' after tran instance range".to_string(),
1704                            span: Span {
1705                                start: self.scanner.pos,
1706                                limit: self.scanner.pos,
1707                            },
1708                        })?;
1709                        if !matches!(cbrack.payload, TokenPayload::CBrack) {
1710                            return Err(ScanError {
1711                                message: "expected ']' after tran instance range".to_string(),
1712                                span: cbrack.span,
1713                            });
1714                        }
1715                    }
1716                }
1717            }
1718        }
1719
1720        self.skip_trivia()?;
1721        let oparen = self.scanner.popt()?.ok_or_else(|| ScanError {
1722            message: "expected '(' after tran".to_string(),
1723            span: Span {
1724                start: self.scanner.pos,
1725                limit: self.scanner.pos,
1726            },
1727        })?;
1728        if !matches!(oparen.payload, TokenPayload::OParen) {
1729            return Err(ScanError {
1730                message: "expected '(' after tran".to_string(),
1731                span: oparen.span,
1732            });
1733        }
1734
1735        let lhs = self.parse_netref_expr()?;
1736        self.skip_trivia()?;
1737        let comma = self.scanner.popt()?.ok_or_else(|| ScanError {
1738            message: "expected ',' between tran terminals".to_string(),
1739            span: Span {
1740                start: self.scanner.pos,
1741                limit: self.scanner.pos,
1742            },
1743        })?;
1744        if !matches!(comma.payload, TokenPayload::Comma) {
1745            return Err(ScanError {
1746                message: "expected ',' between tran terminals".to_string(),
1747                span: comma.span,
1748            });
1749        }
1750        let rhs = self.parse_netref_expr()?;
1751        self.skip_trivia()?;
1752        let cparen = self.scanner.popt()?.ok_or_else(|| ScanError {
1753            message: "expected ')' after tran terminals".to_string(),
1754            span: Span {
1755                start: self.scanner.pos,
1756                limit: self.scanner.pos,
1757            },
1758        })?;
1759        if !matches!(cparen.payload, TokenPayload::CParen) {
1760            return Err(ScanError {
1761                message: "expected ')' after tran terminals".to_string(),
1762                span: cparen.span,
1763            });
1764        }
1765        self.skip_trivia()?;
1766        let t_semi = self.scanner.popt()?.ok_or_else(|| ScanError {
1767            message: "expected ';' after tran".to_string(),
1768            span: Span {
1769                start: self.scanner.pos,
1770                limit: self.scanner.pos,
1771            },
1772        })?;
1773        if !matches!(t_semi.payload, TokenPayload::Semi) {
1774            return Err(ScanError {
1775                message: "expected ';' after tran".to_string(),
1776                span: t_semi.span,
1777            });
1778        }
1779        Ok(NetlistAssign {
1780            kind: NetlistAssignKind::Tran,
1781            lhs,
1782            rhs: AssignExpr::Leaf(rhs),
1783            span: Span {
1784                start: t_tran.span.start,
1785                limit: t_semi.span.limit,
1786            },
1787        })
1788    }
1789    pub fn parse_file(&mut self) -> Result<Vec<NetlistModule>, ScanError> {
1790        log::trace!("parse_file: start");
1791        let mut modules = Vec::new();
1792        loop {
1793            match self.scanner.peekt()? {
1794                Some(tok) => {
1795                    log::trace!(
1796                        "parse_file: top-level token: {:?} at {:?}",
1797                        tok.payload,
1798                        tok.span
1799                    );
1800                    match &tok.payload {
1801                        TokenPayload::Keyword(Keyword::Module) => {
1802                            log::trace!("parse_file: found 'module', parsing module body");
1803                            modules.push(self.parse_module()?);
1804                        }
1805                        // Skip comments and annotations at file scope
1806                        TokenPayload::Comment(_) | TokenPayload::Annotation { .. } => {
1807                            log::trace!("parse_file: skipping comment/annotation at top level");
1808                            self.scanner.popt()?;
1809                        }
1810                        // Unexpected token at file scope: stop.
1811                        _ => {
1812                            log::trace!(
1813                                "parse_file: stopping on unexpected top-level token: {:?}",
1814                                tok.payload
1815                            );
1816                            break;
1817                        }
1818                    }
1819                }
1820                None => break,
1821            }
1822        }
1823        log::trace!("parse_file: done; modules parsed: {}", modules.len());
1824        Ok(modules)
1825    }
1826
1827    pub fn parse_module(&mut self) -> Result<NetlistModule, ScanError> {
1828        // Each module has its own namespace for nets. Reset the per-module
1829        // name index and record where this module's nets begin in the global
1830        // `nets` vector so that lookups (and width checks) are scoped
1831        // per-module instead of across the entire file.
1832        self.current_module_net_start = self.nets.len();
1833        let module_net_start = self.current_module_net_start;
1834        self.net_index_by_name.clear();
1835        self.net_width_span_by_name.clear();
1836        self.implicit_net_by_name.clear();
1837
1838        // Expect 'module' keyword
1839        let tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1840            message: "expected 'module' keyword".to_string(),
1841            span: Span {
1842                start: self.scanner.pos,
1843                limit: self.scanner.pos,
1844            },
1845        })?;
1846        if !matches!(tok.payload, TokenPayload::Keyword(Keyword::Module)) {
1847            return Err(ScanError {
1848                message: "expected 'module' keyword".to_string(),
1849                span: tok.span,
1850            });
1851        }
1852        // Expect module name (identifier)
1853        let name_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1854            message: "expected module name".to_string(),
1855            span: Span {
1856                start: self.scanner.pos,
1857                limit: self.scanner.pos,
1858            },
1859        })?;
1860        let name = match name_tok.payload {
1861            TokenPayload::Identifier(s) => self.interner.get_or_intern(s),
1862            _ => {
1863                return Err(ScanError {
1864                    message: "expected identifier for module name".to_string(),
1865                    span: name_tok.span,
1866                });
1867            }
1868        };
1869        // Expect '('
1870        let oparen_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1871            message: "expected '(' after module name".to_string(),
1872            span: Span {
1873                start: self.scanner.pos,
1874                limit: self.scanner.pos,
1875            },
1876        })?;
1877        if !matches!(oparen_tok.payload, TokenPayload::OParen) {
1878            return Err(ScanError {
1879                message: "expected '(' after module name".to_string(),
1880                span: oparen_tok.span,
1881            });
1882        }
1883        // Parse port list: identifier[, identifier ...]
1884        let mut port_names = Vec::new();
1885        loop {
1886            let t = self.scanner.popt()?.ok_or_else(|| ScanError {
1887                message: "expected token in port list".to_string(),
1888                span: Span {
1889                    start: self.scanner.pos,
1890                    limit: self.scanner.pos,
1891                },
1892            })?;
1893            match t.payload {
1894                TokenPayload::Identifier(s) => {
1895                    let sym = self.interner.get_or_intern(s);
1896                    port_names.push(sym);
1897                    // If next is ',', continue
1898                    if let Some(next) = self.scanner.peekt()? {
1899                        if matches!(next.payload, TokenPayload::Comma) {
1900                            self.scanner.popt()?; // consume ','
1901                            continue;
1902                        }
1903                    }
1904                }
1905                TokenPayload::CParen => break,
1906                _ => {
1907                    return Err(ScanError {
1908                        message: format!("unexpected token in port list: {:?}", t.payload),
1909                        span: t.span,
1910                    });
1911                }
1912            }
1913        }
1914        // Expect ';'
1915        let semi_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
1916            message: "expected ';' after port list".to_string(),
1917            span: Span {
1918                start: self.scanner.pos,
1919                limit: self.scanner.pos,
1920            },
1921        })?;
1922        if !matches!(semi_tok.payload, TokenPayload::Semi) {
1923            return Err(ScanError {
1924                message: "expected ';' after port list".to_string(),
1925                span: semi_tok.span,
1926            });
1927        }
1928        // Parse body: ports, wires, and instances until 'endmodule'
1929        let mut ports = Vec::new();
1930        let mut wires = Vec::new();
1931        let mut assigns = Vec::new();
1932        let mut instances = Vec::new();
1933        // Enforce uniqueness of instance names within a module: we track the
1934        // set of already-seen names and reject duplicates.
1935        let mut instance_names: HashSet<PortId> = HashSet::new();
1936        loop {
1937            match self.scanner.peekt()? {
1938                Some(tok) => match &tok.payload {
1939                    TokenPayload::Keyword(Keyword::Input) => {
1940                        let mut decls = self.parse_port_decl(PortDirection::Input)?;
1941                        ports.append(&mut decls);
1942                    }
1943                    TokenPayload::Keyword(Keyword::Output) => {
1944                        let mut decls = self.parse_port_decl(PortDirection::Output)?;
1945                        ports.append(&mut decls);
1946                    }
1947                    TokenPayload::Keyword(Keyword::Inout) => {
1948                        let mut decls = self.parse_port_decl(PortDirection::Inout)?;
1949                        ports.append(&mut decls);
1950                    }
1951                    TokenPayload::Keyword(Keyword::Wire) => {
1952                        let wire_indices = self.parse_wire_decl()?;
1953                        wires.extend(wire_indices);
1954                    }
1955                    TokenPayload::Keyword(Keyword::Endmodule) => {
1956                        self.scanner.popt()?; // consume 'endmodule'
1957                        break;
1958                    }
1959                    TokenPayload::Identifier(s) if s == "assign" => {
1960                        assigns.push(self.parse_assign()?);
1961                    }
1962                    TokenPayload::Identifier(s) if s == "tran" => {
1963                        assigns.push(self.parse_tran()?);
1964                    }
1965                    TokenPayload::Identifier(_) => {
1966                        let instance = self.parse_instance()?;
1967                        if !instance_names.insert(instance.instance_name) {
1968                            let instance_start_pos = Pos {
1969                                lineno: instance.inst_lineno,
1970                                colno: instance.inst_colno,
1971                            };
1972                            return Err(ScanError {
1973                                message: format!(
1974                                    "duplicate instance name '{}' in module",
1975                                    self.interner.resolve(instance.instance_name).unwrap()
1976                                ),
1977                                span: Span {
1978                                    start: instance_start_pos,
1979                                    limit: instance_start_pos,
1980                                },
1981                            });
1982                        }
1983                        instances.push(instance);
1984                    }
1985                    // Skip comments and annotations
1986                    TokenPayload::Comment(_) | TokenPayload::Annotation { .. } => {
1987                        self.scanner.popt()?;
1988                    }
1989                    _ => {
1990                        // Unexpected token
1991                        break;
1992                    }
1993                },
1994                None => break,
1995            }
1996        }
1997        // Preserve top-level port order from the module header list, even when
1998        // non-ANSI input/output declarations are grouped by direction in the body.
1999        // Ports not present in the header (if any) are left at the end in their
2000        // original declaration order.
2001        let mut header_port_index: HashMap<PortId, usize> = HashMap::new();
2002        for (i, port_name) in port_names.into_iter().enumerate() {
2003            header_port_index.entry(port_name).or_insert(i);
2004        }
2005        ports.sort_by_key(|port| {
2006            header_port_index
2007                .get(&port.name)
2008                .copied()
2009                .unwrap_or(usize::MAX)
2010        });
2011        Ok(NetlistModule {
2012            name,
2013            net_index_range: module_net_start..self.nets.len(),
2014            ports,
2015            wires,
2016            assigns,
2017            instances,
2018        })
2019    }
2020
2021    /// Parses: input/output/inout [msb:lsb] foo, bar, ...;
2022    pub fn parse_port_decl(
2023        &mut self,
2024        direction: PortDirection,
2025    ) -> Result<Vec<NetlistPort>, ScanError> {
2026        // Consume the keyword
2027        let kw_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2028            message: "expected port direction keyword".to_string(),
2029            span: Span {
2030                start: self.scanner.pos,
2031                limit: self.scanner.pos,
2032            },
2033        })?;
2034        if !matches!(
2035            kw_tok.payload,
2036            TokenPayload::Keyword(Keyword::Input)
2037                | TokenPayload::Keyword(Keyword::Output)
2038                | TokenPayload::Keyword(Keyword::Inout)
2039        ) {
2040            return Err(ScanError {
2041                message: "expected port direction keyword".to_string(),
2042                span: kw_tok.span,
2043            });
2044        }
2045        // Optional width: [msb:lsb]
2046        let mut width = None;
2047        if let Some(next) = self.scanner.peekt()? {
2048            if matches!(next.payload, TokenPayload::OBrack) {
2049                self.scanner.popt()?; // consume '['
2050                let msb_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2051                    message: "expected msb in width".to_string(),
2052                    span: Span {
2053                        start: self.scanner.pos,
2054                        limit: self.scanner.pos,
2055                    },
2056                })?;
2057                let msb = match msb_tok.payload {
2058                    TokenPayload::VerilogInt { value, .. } => {
2059                        xlsynth::IrValue::from_bits(&value).to_u32().unwrap()
2060                    }
2061                    _ => {
2062                        return Err(ScanError {
2063                            message: "expected integer for msb".to_string(),
2064                            span: msb_tok.span,
2065                        });
2066                    }
2067                };
2068                let colon_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2069                    message: "expected ':' in width".to_string(),
2070                    span: Span {
2071                        start: self.scanner.pos,
2072                        limit: self.scanner.pos,
2073                    },
2074                })?;
2075                if !matches!(colon_tok.payload, TokenPayload::Colon) {
2076                    return Err(ScanError {
2077                        message: "expected ':' in width".to_string(),
2078                        span: colon_tok.span,
2079                    });
2080                }
2081                let lsb_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2082                    message: "expected lsb in width".to_string(),
2083                    span: Span {
2084                        start: self.scanner.pos,
2085                        limit: self.scanner.pos,
2086                    },
2087                })?;
2088                let lsb = match lsb_tok.payload {
2089                    TokenPayload::VerilogInt { value, .. } => {
2090                        xlsynth::IrValue::from_bits(&value).to_u32().unwrap()
2091                    }
2092                    _ => {
2093                        return Err(ScanError {
2094                            message: "expected integer for lsb".to_string(),
2095                            span: lsb_tok.span,
2096                        });
2097                    }
2098                };
2099                let cbrack_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2100                    message: "expected ']' after width".to_string(),
2101                    span: Span {
2102                        start: self.scanner.pos,
2103                        limit: self.scanner.pos,
2104                    },
2105                })?;
2106                if !matches!(cbrack_tok.payload, TokenPayload::CBrack) {
2107                    return Err(ScanError {
2108                        message: "expected ']' after width".to_string(),
2109                        span: cbrack_tok.span,
2110                    });
2111                }
2112                width = Some((msb, lsb));
2113            }
2114        }
2115        // Parse one or more identifiers (comma-separated)
2116        let mut ports = Vec::new();
2117        loop {
2118            let t = self.scanner.popt()?.ok_or_else(|| ScanError {
2119                message: "expected identifier in port decl".to_string(),
2120                span: Span {
2121                    start: self.scanner.pos,
2122                    limit: self.scanner.pos,
2123                },
2124            })?;
2125            let name = match t.payload {
2126                TokenPayload::Identifier(s) => self.interner.get_or_intern(s),
2127                _ => {
2128                    return Err(ScanError {
2129                        message: "expected identifier in port decl".to_string(),
2130                        span: t.span,
2131                    });
2132                }
2133            };
2134            // Ensure a corresponding net exists for this port (non-ANSI body decls imply
2135            // nets). If a width is present here and not previously known,
2136            // record it; if conflicting, error.
2137            let _net_index = self.ensure_net(name, width, t.span)?;
2138            ports.push(NetlistPort {
2139                direction: direction.clone(),
2140                width,
2141                name,
2142            });
2143            // If next is ',', continue
2144            if let Some(next) = self.scanner.peekt()? {
2145                if matches!(next.payload, TokenPayload::Comma) {
2146                    self.scanner.popt()?; // consume ','
2147                    continue;
2148                }
2149            }
2150            // Otherwise, expect ';'
2151            let semi = self.scanner.popt()?.ok_or_else(|| ScanError {
2152                message: "expected ';' after port decl".to_string(),
2153                span: Span {
2154                    start: self.scanner.pos,
2155                    limit: self.scanner.pos,
2156                },
2157            })?;
2158            if !matches!(semi.payload, TokenPayload::Semi) {
2159                return Err(ScanError {
2160                    message: "expected ';' after port decl".to_string(),
2161                    span: semi.span,
2162                });
2163            }
2164            break;
2165        }
2166        Ok(ports)
2167    }
2168
2169    /// Parses: wire foo, bar, baz;
2170    pub fn parse_wire_decl(&mut self) -> Result<Vec<NetIndex>, ScanError> {
2171        // Expect 'wire' keyword
2172        let tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2173            message: "expected 'wire' keyword".to_string(),
2174            span: Span {
2175                start: self.scanner.pos,
2176                limit: self.scanner.pos,
2177            },
2178        })?;
2179        if !matches!(tok.payload, TokenPayload::Keyword(Keyword::Wire)) {
2180            return Err(ScanError {
2181                message: "expected 'wire' keyword".to_string(),
2182                span: tok.span,
2183            });
2184        }
2185        // Optional width: [msb:lsb]
2186        let mut width = None;
2187        if let Some(next) = self.scanner.peekt()? {
2188            if matches!(next.payload, TokenPayload::OBrack) {
2189                self.scanner.popt()?; // consume '['
2190                let msb_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2191                    message: "expected msb in width".to_string(),
2192                    span: Span {
2193                        start: self.scanner.pos,
2194                        limit: self.scanner.pos,
2195                    },
2196                })?;
2197                let msb = match msb_tok.payload {
2198                    TokenPayload::VerilogInt { value, .. } => {
2199                        xlsynth::IrValue::from_bits(&value).to_u32().unwrap()
2200                    }
2201                    _ => {
2202                        return Err(ScanError {
2203                            message: "expected integer for msb".to_string(),
2204                            span: msb_tok.span,
2205                        });
2206                    }
2207                };
2208                let colon_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2209                    message: "expected ':' in width".to_string(),
2210                    span: Span {
2211                        start: self.scanner.pos,
2212                        limit: self.scanner.pos,
2213                    },
2214                })?;
2215                if !matches!(colon_tok.payload, TokenPayload::Colon) {
2216                    return Err(ScanError {
2217                        message: "expected ':' in width".to_string(),
2218                        span: colon_tok.span,
2219                    });
2220                }
2221                let lsb_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2222                    message: "expected lsb in width".to_string(),
2223                    span: Span {
2224                        start: self.scanner.pos,
2225                        limit: self.scanner.pos,
2226                    },
2227                })?;
2228                let lsb = match lsb_tok.payload {
2229                    TokenPayload::VerilogInt { value, .. } => {
2230                        xlsynth::IrValue::from_bits(&value).to_u32().unwrap()
2231                    }
2232                    _ => {
2233                        return Err(ScanError {
2234                            message: "expected integer for lsb".to_string(),
2235                            span: lsb_tok.span,
2236                        });
2237                    }
2238                };
2239                let cbrack_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2240                    message: "expected ']' after width".to_string(),
2241                    span: Span {
2242                        start: self.scanner.pos,
2243                        limit: self.scanner.pos,
2244                    },
2245                })?;
2246                if !matches!(cbrack_tok.payload, TokenPayload::CBrack) {
2247                    return Err(ScanError {
2248                        message: "expected ']' after width".to_string(),
2249                        span: cbrack_tok.span,
2250                    });
2251                }
2252                width = Some((msb, lsb));
2253            }
2254        }
2255        let mut net_indices = Vec::new();
2256        loop {
2257            let t = self.scanner.popt()?.ok_or_else(|| ScanError {
2258                message: "expected identifier in wire decl".to_string(),
2259                span: Span {
2260                    start: self.scanner.pos,
2261                    limit: self.scanner.pos,
2262                },
2263            })?;
2264            let name = match t.payload {
2265                TokenPayload::Identifier(s) => self.interner.get_or_intern(s),
2266                ref other => {
2267                    let line = (self.scanner.line_lookup)(t.span.start.lineno)
2268                        .unwrap_or_else(|| "<line unavailable>".to_string());
2269                    log::error!(
2270                        "expected identifier in wire decl, got {:?} at {:?}\n{}\n{}^",
2271                        other,
2272                        t.span,
2273                        line,
2274                        " ".repeat((t.span.start.colno as usize).saturating_sub(1))
2275                    );
2276                    return Err(ScanError {
2277                        message: "expected identifier in wire decl".to_string(),
2278                        span: t.span,
2279                    });
2280                }
2281            };
2282            // Ensure/dedupe nets for wires too; reconcile widths per rules above.
2283            let net_idx = self.ensure_net(name, width, t.span)?;
2284            net_indices.push(net_idx);
2285            // If next is ',', continue
2286            if let Some(next) = self.scanner.peekt()? {
2287                if matches!(next.payload, TokenPayload::Comma) {
2288                    self.scanner.popt()?; // consume ','
2289                    continue;
2290                }
2291            }
2292            // Otherwise, expect ';'
2293            let semi = self.scanner.popt()?.ok_or_else(|| ScanError {
2294                message: "expected ';' after wire decl".to_string(),
2295                span: Span {
2296                    start: self.scanner.pos,
2297                    limit: self.scanner.pos,
2298                },
2299            })?;
2300            if !matches!(semi.payload, TokenPayload::Semi) {
2301                return Err(ScanError {
2302                    message: "expected ';' after wire decl".to_string(),
2303                    span: semi.span,
2304                });
2305            }
2306            break;
2307        }
2308        Ok(net_indices)
2309    }
2310
2311    /// Parses: TypeName InstanceName ( .Port(Net), ... );
2312    pub fn parse_instance(&mut self) -> Result<NetlistInstance, ScanError> {
2313        // Type name
2314        let type_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2315            message: "expected type name".to_string(),
2316            span: Span {
2317                start: self.scanner.pos,
2318                limit: self.scanner.pos,
2319            },
2320        })?;
2321        let type_name = match type_tok.payload {
2322            TokenPayload::Identifier(s) => self.interner.get_or_intern(s),
2323            _ => {
2324                return Err(ScanError {
2325                    message: "expected identifier for type name".to_string(),
2326                    span: type_tok.span,
2327                });
2328            }
2329        };
2330        // Skip comments/annotations before instance name
2331        loop {
2332            let peek = self.scanner.peekt()?;
2333            match peek {
2334                Some(tok) => match &tok.payload {
2335                    TokenPayload::Comment(_) | TokenPayload::Annotation { .. } => {
2336                        self.scanner.popt()?;
2337                    }
2338                    _ => break,
2339                },
2340                None => break,
2341            }
2342        }
2343        // Instance name
2344        let inst_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2345            message: "expected instance name".to_string(),
2346            span: Span {
2347                start: self.scanner.pos,
2348                limit: self.scanner.pos,
2349            },
2350        })?;
2351        let instance_name = match inst_tok.payload {
2352            TokenPayload::Identifier(s) => self.interner.get_or_intern(s),
2353            ref other => {
2354                return Err(ScanError {
2355                    message: format!("expected identifier for instance name; got {:?}", other),
2356                    span: inst_tok.span,
2357                });
2358            }
2359        };
2360        // Expect '('
2361        let oparen = self.scanner.popt()?.ok_or_else(|| ScanError {
2362            message: "expected '(' after instance name".to_string(),
2363            span: Span {
2364                start: self.scanner.pos,
2365                limit: self.scanner.pos,
2366            },
2367        })?;
2368        if !matches!(oparen.payload, TokenPayload::OParen) {
2369            return Err(ScanError {
2370                message: "expected '(' after instance name".to_string(),
2371                span: oparen.span,
2372            });
2373        }
2374        // Allow empty port list: TypeName InstanceName ( );
2375        // Skip comments/annotations immediately inside the parentheses
2376        loop {
2377            let peek = self.scanner.peekt()?;
2378            match peek {
2379                Some(tok) => match &tok.payload {
2380                    TokenPayload::Comment(_) | TokenPayload::Annotation { .. } => {
2381                        self.scanner.popt()?;
2382                    }
2383                    _ => break,
2384                },
2385                None => break,
2386            }
2387        }
2388        if let Some(tok) = self.scanner.peekt()? {
2389            if matches!(tok.payload, TokenPayload::CParen) {
2390                self.scanner.popt()?; // consume ')'
2391                let semi = self.scanner.popt()?.ok_or_else(|| ScanError {
2392                    message: "expected ';' after instance".to_string(),
2393                    span: Span {
2394                        start: self.scanner.pos,
2395                        limit: self.scanner.pos,
2396                    },
2397                })?;
2398                if !matches!(semi.payload, TokenPayload::Semi) {
2399                    return Err(ScanError {
2400                        message: "expected ';' after instance".to_string(),
2401                        span: semi.span,
2402                    });
2403                }
2404                return Ok(NetlistInstance {
2405                    type_name,
2406                    instance_name,
2407                    connections: Vec::new(),
2408                    inst_lineno: inst_tok.span.start.lineno,
2409                    inst_colno: inst_tok.span.start.colno,
2410                });
2411            }
2412        }
2413        let mut connections = Vec::new();
2414        loop {
2415            // Expect .Port
2416            let dot = self.scanner.popt()?.ok_or_else(|| ScanError {
2417                message: "expected '.' before port name".to_string(),
2418                span: Span {
2419                    start: self.scanner.pos,
2420                    limit: self.scanner.pos,
2421                },
2422            })?;
2423            if !matches!(dot.payload, TokenPayload::Dot) {
2424                return Err(ScanError {
2425                    message: "expected '.' before port name".to_string(),
2426                    span: dot.span,
2427                });
2428            }
2429            let port_tok = self.scanner.popt()?.ok_or_else(|| ScanError {
2430                message: "expected port name".to_string(),
2431                span: Span {
2432                    start: self.scanner.pos,
2433                    limit: self.scanner.pos,
2434                },
2435            })?;
2436            let port = match port_tok.payload {
2437                TokenPayload::Identifier(s) => self.interner.get_or_intern(s),
2438                _ => {
2439                    return Err(ScanError {
2440                        message: "expected identifier for port name".to_string(),
2441                        span: port_tok.span,
2442                    });
2443                }
2444            };
2445            // Expect '('
2446            let oparen2 = self.scanner.popt()?.ok_or_else(|| ScanError {
2447                message: "expected '(' before net name".to_string(),
2448                span: Span {
2449                    start: self.scanner.pos,
2450                    limit: self.scanner.pos,
2451                },
2452            })?;
2453            if !matches!(oparen2.payload, TokenPayload::OParen) {
2454                return Err(ScanError {
2455                    message: "expected '(' before net name".to_string(),
2456                    span: oparen2.span,
2457                });
2458            }
2459            // Net name or net reference expression (or unconnected)
2460            // Skip any inline comments/annotations inside the parentheses
2461            loop {
2462                let peek = self.scanner.peekt()?;
2463                match peek {
2464                    Some(tok) => match &tok.payload {
2465                        TokenPayload::Comment(_) | TokenPayload::Annotation { .. } => {
2466                            self.scanner.popt()?;
2467                        }
2468                        _ => break,
2469                    },
2470                    None => break,
2471                }
2472            }
2473            // Allow immediate ')' to denote an unconnected port
2474            let is_unconnected = matches!(self.scanner.peekt()?, Some(tok) if matches!(tok.payload, TokenPayload::CParen));
2475            let net_ref = if is_unconnected {
2476                NetRef::Unconnected
2477            } else {
2478                self.parse_netref_expr()?
2479            };
2480            // Expect ')'
2481            let cparen2 = self.scanner.popt()?.ok_or_else(|| ScanError {
2482                message: "expected ')' after net name".to_string(),
2483                span: Span {
2484                    start: self.scanner.pos,
2485                    limit: self.scanner.pos,
2486                },
2487            })?;
2488            if !matches!(cparen2.payload, TokenPayload::CParen) {
2489                return Err(ScanError {
2490                    message: "expected ')' after net name".to_string(),
2491                    span: cparen2.span,
2492                });
2493            }
2494            connections.push((port, net_ref));
2495            // If next is ',', continue
2496            if let Some(next) = self.scanner.peekt()? {
2497                if matches!(next.payload, TokenPayload::Comma) {
2498                    self.scanner.popt()?; // consume ','
2499                    continue;
2500                }
2501            }
2502            // Otherwise, expect ')'
2503            let cparen = self.scanner.popt()?.ok_or_else(|| ScanError {
2504                message: "expected ')' after instance connections".to_string(),
2505                span: Span {
2506                    start: self.scanner.pos,
2507                    limit: self.scanner.pos,
2508                },
2509            })?;
2510            if !matches!(cparen.payload, TokenPayload::CParen) {
2511                return Err(ScanError {
2512                    message: "expected ')' after instance connections".to_string(),
2513                    span: cparen.span,
2514                });
2515            }
2516            break;
2517        }
2518        // Expect ';'
2519        let semi = self.scanner.popt()?.ok_or_else(|| ScanError {
2520            message: "expected ';' after instance".to_string(),
2521            span: Span {
2522                start: self.scanner.pos,
2523                limit: self.scanner.pos,
2524            },
2525        })?;
2526        if !matches!(semi.payload, TokenPayload::Semi) {
2527            return Err(ScanError {
2528                message: "expected ';' after instance".to_string(),
2529                span: semi.span,
2530            });
2531        }
2532        Ok(NetlistInstance {
2533            type_name,
2534            instance_name,
2535            connections,
2536            inst_lineno: inst_tok.span.start.lineno,
2537            inst_colno: inst_tok.span.start.colno,
2538        })
2539    }
2540
2541    /// Get the source line for a given line number (1-based), or None if
2542    /// unavailable.
2543    pub fn get_line(&self, lineno: u32) -> Option<String> {
2544        (self.scanner.line_lookup)(lineno)
2545    }
2546}
2547
2548#[cfg(test)]
2549mod tests {
2550    use super::*;
2551    // use std::io::Cursor;
2552
2553    #[test]
2554    fn test_token_scanner_oparen() {
2555        let input = "(";
2556        let mut scanner = TokenScanner::from_str(input);
2557        let t = scanner.popt().expect("no scan error").unwrap();
2558        assert!(matches!(t.payload, TokenPayload::OParen));
2559        assert!(scanner.popt().expect("no scan error").is_none());
2560    }
2561
2562    #[test]
2563    fn test_token_scanner_cparen() {
2564        let input = ")";
2565        let mut scanner = TokenScanner::from_str(input);
2566        let t = scanner.popt().expect("no scan error").unwrap();
2567        assert!(matches!(t.payload, TokenPayload::CParen));
2568        assert!(scanner.popt().expect("no scan error").is_none());
2569    }
2570
2571    #[test]
2572    fn test_token_scanner_semi() {
2573        let input = ";";
2574        let mut scanner = TokenScanner::from_str(input);
2575        let t = scanner.popt().expect("no scan error").unwrap();
2576        assert!(matches!(t.payload, TokenPayload::Semi));
2577        assert!(scanner.popt().expect("no scan error").is_none());
2578    }
2579
2580    #[test]
2581    fn test_token_scanner_line_comment() {
2582        let input = "// hello world\n";
2583        let mut scanner = TokenScanner::from_str(input);
2584        let t = scanner.popt().expect("no scan error").unwrap();
2585        assert!(matches!(t.payload, TokenPayload::Comment(ref s) if s == " hello world"));
2586        assert!(scanner.popt().expect("no scan error").is_none());
2587    }
2588
2589    #[test]
2590    fn test_parse_module_with_assign_literal_bit() {
2591        let src = r#"
2592module m(a, out);
2593  input a;
2594  output [3:0] out;
2595  assign out[1] = 1'b0;
2596endmodule
2597"#;
2598        let mut parser = Parser::new(TokenScanner::from_str(src));
2599        let modules = parser.parse_file().expect("parse ok");
2600        assert_eq!(modules.len(), 1);
2601        assert_eq!(modules[0].assigns.len(), 1);
2602        assert_eq!(modules[0].assigns[0].kind, NetlistAssignKind::Continuous);
2603        assert!(matches!(modules[0].assigns[0].lhs, NetRef::BitSelect(_, 1)));
2604        assert!(matches!(
2605            modules[0].assigns[0].rhs,
2606            AssignExpr::Leaf(NetRef::Literal(_))
2607        ));
2608    }
2609
2610    #[test]
2611    fn test_parse_module_accepts_unnamed_tran() {
2612        let src = r#"
2613module m(a, y);
2614  input a;
2615  output y;
2616  wire a;
2617  wire y;
2618  tran(y, a);
2619endmodule
2620"#;
2621        let mut parser = Parser::new(TokenScanner::from_str(src));
2622        let modules = parser.parse_file().expect("tran should parse");
2623        assert_eq!(modules[0].assigns.len(), 1);
2624        assert_eq!(modules[0].assigns[0].kind, NetlistAssignKind::Tran);
2625        assert!(matches!(modules[0].assigns[0].lhs, NetRef::Simple(_)));
2626        assert!(matches!(
2627            modules[0].assigns[0].rhs,
2628            AssignExpr::Leaf(NetRef::Simple(_))
2629        ));
2630    }
2631
2632    #[test]
2633    fn test_parse_module_accepts_named_arrayed_tran() {
2634        let src = r#"
2635module m(a, y);
2636  input [1:0] a;
2637  output [1:0] y;
2638  wire [1:0] a;
2639  wire [1:0] y;
2640  tran t[1:0]({y[1], y[0]}, {a[1], a[0]});
2641endmodule
2642"#;
2643        let mut parser = Parser::new(TokenScanner::from_str(src));
2644        let modules = parser.parse_file().expect("arrayed tran should parse");
2645        assert_eq!(modules[0].assigns.len(), 1);
2646        assert_eq!(modules[0].assigns[0].kind, NetlistAssignKind::Tran);
2647        assert!(matches!(modules[0].assigns[0].lhs, NetRef::Concat(_)));
2648        assert!(matches!(
2649            modules[0].assigns[0].rhs,
2650            AssignExpr::Leaf(NetRef::Concat(_))
2651        ));
2652    }
2653
2654    #[test]
2655    fn test_parse_module_with_assign_literal_partselect() {
2656        let src = r#"
2657module m(a, out);
2658  input a;
2659  output [7:0] out;
2660  assign out[7:4] = 4'h0;
2661endmodule
2662"#;
2663        let mut parser = Parser::new(TokenScanner::from_str(src));
2664        let modules = parser.parse_file().expect("parse ok");
2665        assert_eq!(modules.len(), 1);
2666        assert_eq!(modules[0].assigns.len(), 1);
2667        assert!(matches!(
2668            modules[0].assigns[0].lhs,
2669            NetRef::PartSelect(_, 7, 4)
2670        ));
2671    }
2672
2673    #[test]
2674    fn test_parse_module_with_unary_assign_expression() {
2675        let src = r#"
2676module m(a, y);
2677  input a;
2678  output y;
2679  assign y = ~a;
2680endmodule
2681"#;
2682        let mut parser = Parser::new(TokenScanner::from_str(src));
2683        let modules = parser.parse_file().expect("parse ok");
2684        assert_eq!(modules.len(), 1);
2685        assert_eq!(modules[0].assigns.len(), 1);
2686        match &modules[0].assigns[0].rhs {
2687            AssignExpr::Not(inner) => {
2688                assert!(matches!(
2689                    inner.as_ref(),
2690                    AssignExpr::Leaf(NetRef::Simple(_))
2691                ));
2692            }
2693            other => panic!("expected unary-not assign, got {:?}", other),
2694        }
2695    }
2696
2697    #[test]
2698    fn test_parse_module_with_operator_precedence_in_assign_expression() {
2699        let src = r#"
2700module m(a, b, c, d, y);
2701  input a;
2702  input b;
2703  input c;
2704  input d;
2705  output y;
2706  assign y = a | b ^ c & ~d;
2707endmodule
2708"#;
2709        let mut parser = Parser::new(TokenScanner::from_str(src));
2710        let modules = parser.parse_file().expect("parse ok");
2711        let rhs = &modules[0].assigns[0].rhs;
2712        match rhs {
2713            AssignExpr::Or(lhs, rhs) => {
2714                assert!(matches!(lhs.as_ref(), AssignExpr::Leaf(NetRef::Simple(_))));
2715                match rhs.as_ref() {
2716                    AssignExpr::Xor(xor_lhs, xor_rhs) => {
2717                        assert!(matches!(
2718                            xor_lhs.as_ref(),
2719                            AssignExpr::Leaf(NetRef::Simple(_))
2720                        ));
2721                        match xor_rhs.as_ref() {
2722                            AssignExpr::And(and_lhs, and_rhs) => {
2723                                assert!(matches!(
2724                                    and_lhs.as_ref(),
2725                                    AssignExpr::Leaf(NetRef::Simple(_))
2726                                ));
2727                                match and_rhs.as_ref() {
2728                                    AssignExpr::Not(not_inner) => {
2729                                        assert!(matches!(
2730                                            not_inner.as_ref(),
2731                                            AssignExpr::Leaf(NetRef::Simple(_))
2732                                        ));
2733                                    }
2734                                    other => panic!("expected unary-not, got {:?}", other),
2735                                }
2736                            }
2737                            other => panic!("expected and-expression, got {:?}", other),
2738                        }
2739                    }
2740                    other => panic!("expected xor-expression, got {:?}", other),
2741                }
2742            }
2743            other => panic!("expected top-level or-expression, got {:?}", other),
2744        }
2745    }
2746
2747    #[test]
2748    fn test_parse_module_with_parenthesized_assign_expression() {
2749        let src = r#"
2750module m(a, b, c, y);
2751  input a;
2752  input b;
2753  input c;
2754  output y;
2755  assign y = (a | b) ^ c;
2756endmodule
2757"#;
2758        let mut parser = Parser::new(TokenScanner::from_str(src));
2759        let modules = parser.parse_file().expect("parse ok");
2760        let rhs = &modules[0].assigns[0].rhs;
2761        match rhs {
2762            AssignExpr::Xor(lhs, rhs) => {
2763                match lhs.as_ref() {
2764                    AssignExpr::Or(or_lhs, or_rhs) => {
2765                        assert!(matches!(
2766                            or_lhs.as_ref(),
2767                            AssignExpr::Leaf(NetRef::Simple(_))
2768                        ));
2769                        assert!(matches!(
2770                            or_rhs.as_ref(),
2771                            AssignExpr::Leaf(NetRef::Simple(_))
2772                        ));
2773                    }
2774                    other => panic!("expected parenthesized or-expression, got {:?}", other),
2775                }
2776                assert!(matches!(rhs.as_ref(), AssignExpr::Leaf(NetRef::Simple(_))));
2777            }
2778            other => panic!("expected top-level xor-expression, got {:?}", other),
2779        }
2780    }
2781
2782    #[test]
2783    fn test_parse_module_with_selects_in_assign_expression() {
2784        let src = r#"
2785module m(a, b, y);
2786  input [7:0] a;
2787  input [3:0] b;
2788  output [3:0] y;
2789  assign y[3:0] = a[7:4] & b[3:0];
2790endmodule
2791"#;
2792        let mut parser = Parser::new(TokenScanner::from_str(src));
2793        let modules = parser.parse_file().expect("parse ok");
2794        assert!(matches!(
2795            modules[0].assigns[0].lhs,
2796            NetRef::PartSelect(_, 3, 0)
2797        ));
2798        match &modules[0].assigns[0].rhs {
2799            AssignExpr::And(lhs, rhs) => {
2800                assert!(matches!(
2801                    lhs.as_ref(),
2802                    AssignExpr::Leaf(NetRef::PartSelect(_, 7, 4))
2803                ));
2804                assert!(matches!(
2805                    rhs.as_ref(),
2806                    AssignExpr::Leaf(NetRef::PartSelect(_, 3, 0))
2807                ));
2808            }
2809            other => panic!(
2810                "expected and-expression with part-select leaves, got {:?}",
2811                other
2812            ),
2813        }
2814    }
2815
2816    #[test]
2817    fn test_parse_module_accepts_concat_in_assign_expression() {
2818        let src = r#"
2819module m(a, b, y);
2820  input a;
2821  input b;
2822  output [1:0] y;
2823  assign y = {a, b};
2824endmodule
2825"#;
2826        let mut parser = Parser::new(TokenScanner::from_str(src));
2827        let modules = parser.parse_file().expect("concat assign should parse");
2828        match &modules[0].assigns[0].rhs {
2829            AssignExpr::Leaf(NetRef::Concat(elems)) => assert_eq!(elems.len(), 2),
2830            other => panic!("expected concat assign RHS, got {:?}", other),
2831        }
2832    }
2833
2834    #[test]
2835    fn test_parse_module_accepts_concat_on_assign_lhs() {
2836        let src = r#"
2837module m(a, b, y);
2838  input a;
2839  input b;
2840  output [1:0] y;
2841  assign {y[1], y[0]} = {a, b};
2842endmodule
2843"#;
2844        let mut parser = Parser::new(TokenScanner::from_str(src));
2845        let modules = parser.parse_file().expect("concat assign lhs should parse");
2846        match &modules[0].assigns[0].lhs {
2847            NetRef::Concat(elems) => assert_eq!(elems.len(), 2),
2848            other => panic!("expected concat assign LHS, got {:?}", other),
2849        }
2850    }
2851
2852    #[test]
2853    fn test_parse_module_accepts_unknown_literal_in_assign() {
2854        let src = r#"
2855module m(y);
2856  output y;
2857  assign y = 1'hx;
2858endmodule
2859"#;
2860        let mut parser = Parser::new(TokenScanner::from_str(src));
2861        let modules = parser
2862            .parse_file()
2863            .expect("unknown literal assign should parse");
2864        assert!(matches!(
2865            modules[0].assigns[0].rhs,
2866            AssignExpr::Leaf(NetRef::UnknownLiteral(1))
2867        ));
2868    }
2869
2870    #[test]
2871    fn test_instance_source_position_is_captured() {
2872        let src = r#"
2873module m(a, b);
2874  input a;
2875  output b;
2876  wire a, b;
2877  INV u1 (.A(a), .Y(b));
2878endmodule
2879"#;
2880        let mut parser = Parser::new(TokenScanner::from_str(src));
2881        let modules = parser.parse_file().expect("parse ok");
2882        assert_eq!(modules.len(), 1);
2883        let m = &modules[0];
2884        assert_eq!(m.instances.len(), 1);
2885        let inst = &m.instances[0];
2886        // 'u1' starts on line 6 (leading newline in the raw string), after two spaces +
2887        // 'INV' (3 chars) + one space => column 7.
2888        assert_eq!(inst.inst_lineno, 6);
2889        assert_eq!(inst.inst_colno, 7);
2890    }
2891
2892    #[test]
2893    fn test_parse_instance_with_empty_port_list() {
2894        let src = r#"
2895module top();
2896  DUMMY_CELL u0 ();
2897endmodule
2898"#;
2899        let mut parser = Parser::new(TokenScanner::from_str(src));
2900        let modules = parser.parse_file().expect("parse ok");
2901        assert_eq!(modules.len(), 1);
2902        let m = &modules[0];
2903        assert_eq!(m.instances.len(), 1);
2904        assert!(m.instances[0].connections.is_empty());
2905    }
2906
2907    #[test]
2908    fn test_parse_instance_with_concat_expr() {
2909        let src = r#"
2910module top(a, b, c);
2911  input a;
2912  input b;
2913  input c;
2914  wire a, b, c;
2915  TYPE u1 (.MY_PORT({a, b, c}));
2916endmodule
2917"#;
2918        let mut parser = Parser::new(TokenScanner::from_str(src));
2919        let modules = parser.parse_file().expect("parse ok");
2920        assert_eq!(modules.len(), 1);
2921        let insts = &modules[0].instances;
2922        assert_eq!(insts.len(), 1);
2923        let (_p, netref) = &insts[0].connections[0];
2924        match netref {
2925            NetRef::Concat(v) => {
2926                assert_eq!(v.len(), 3);
2927                for r in v {
2928                    match r {
2929                        NetRef::Simple(_) => {}
2930                        other => panic!("expected Simple, got {:?}", other),
2931                    }
2932                }
2933            }
2934            other => panic!("expected Concat, got {:?}", other),
2935        }
2936    }
2937
2938    #[test]
2939    fn test_token_scanner_block_comment_only() {
2940        let input = "/* block comment */";
2941        let mut scanner = TokenScanner::from_str(input);
2942        assert!(scanner.popt().expect("no scan error").is_none());
2943    }
2944
2945    #[test]
2946    fn test_token_scanner_block_comment_between_tokens() {
2947        let input = "( /* block comment */ )";
2948        let mut scanner = TokenScanner::from_str(input);
2949        let t1 = scanner.popt().expect("no scan error").unwrap();
2950        assert!(matches!(t1.payload, TokenPayload::OParen));
2951        let t2 = scanner.popt().expect("no scan error").unwrap();
2952        assert!(matches!(t2.payload, TokenPayload::CParen));
2953        assert!(scanner.popt().expect("no scan error").is_none());
2954    }
2955
2956    #[test]
2957    #[should_panic(expected = "ScanError")] // Should panic on unknown token
2958    fn test_token_scanner_error_on_unknown() {
2959        let input = "@";
2960        let mut scanner = TokenScanner::from_str(input);
2961        scanner.popt().unwrap();
2962    }
2963
2964    #[test]
2965    fn test_token_scanner_annotation() {
2966        let input = r#"(* src = "foo.sv" *)"#;
2967        let mut scanner = TokenScanner::from_str(input);
2968        let t = scanner.popt().expect("no scan error").unwrap();
2969        match t.payload {
2970            TokenPayload::Annotation { ref key, ref value } => {
2971                assert_eq!(key, "src");
2972                match value {
2973                    AnnotationValue::String(s) => assert_eq!(s, "foo.sv"),
2974                    _ => panic!("Expected string annotation value"),
2975                }
2976            }
2977            _ => panic!("Expected annotation token"),
2978        }
2979        assert!(scanner.popt().expect("no scan error").is_none());
2980    }
2981
2982    #[test]
2983    fn test_token_scanner_keyword() {
2984        let input = "module";
2985        let mut scanner = TokenScanner::from_str(input);
2986        let t = scanner.popt().expect("no scan error").unwrap();
2987        assert!(matches!(t.payload, TokenPayload::Keyword(Keyword::Module)));
2988        assert!(scanner.popt().expect("no scan error").is_none());
2989    }
2990
2991    #[test]
2992    fn test_token_scanner_identifier() {
2993        let input = "foo_bar123";
2994        let mut scanner = TokenScanner::from_str(input);
2995        let t = scanner.popt().expect("no scan error").unwrap();
2996        assert!(matches!(t.payload, TokenPayload::Identifier(ref s) if s == "foo_bar123"));
2997        assert!(scanner.popt().expect("no scan error").is_none());
2998    }
2999
3000    #[test]
3001    fn test_token_scanner_comma() {
3002        let input = ",";
3003        let mut scanner = TokenScanner::from_str(input);
3004        let t = scanner.popt().expect("no scan error").unwrap();
3005        assert!(matches!(t.payload, TokenPayload::Comma));
3006        assert!(scanner.popt().expect("no scan error").is_none());
3007    }
3008
3009    #[test]
3010    fn test_token_scanner_annotation_binary() {
3011        let input = r#"(* force_downto = 32'b00000000000000000000000000000001 *)"#;
3012        let mut scanner = TokenScanner::from_str(input);
3013        let t = scanner.popt().expect("no scan error").unwrap();
3014        match t.payload {
3015            TokenPayload::Annotation { ref key, ref value } => {
3016                assert_eq!(key, "force_downto");
3017                match value {
3018                    AnnotationValue::VerilogInt { width, value } => {
3019                        assert_eq!(*width, Some(32));
3020                        assert_eq!(value.get_bit_count(), 32);
3021                        assert_eq!(value.to_string(), "bits[32]:1");
3022                    }
3023                    _ => panic!("Expected VerilogInt annotation value"),
3024                }
3025            }
3026            _ => panic!("Expected annotation token"),
3027        }
3028        assert!(scanner.popt().expect("no scan error").is_none());
3029    }
3030
3031    #[test]
3032    fn test_token_scanner_verilog_int_annotation() {
3033        let input = r#"(* force_downto = 32'b00000000000000000000000000000001 *)"#;
3034        let mut scanner = TokenScanner::from_str(input);
3035        let t = scanner.popt().expect("no scan error").unwrap();
3036        match t.payload {
3037            TokenPayload::Annotation { ref key, ref value } => {
3038                assert_eq!(key, "force_downto");
3039                match value {
3040                    AnnotationValue::VerilogInt { width, value } => {
3041                        assert_eq!(*width, Some(32));
3042                        assert_eq!(value.get_bit_count(), 32);
3043                        assert_eq!(value.to_string(), "bits[32]:1");
3044                    }
3045                    _ => panic!("Expected VerilogInt annotation value"),
3046                }
3047            }
3048            _ => panic!("Expected annotation token"),
3049        }
3050        assert!(scanner.popt().expect("no scan error").is_none());
3051    }
3052
3053    #[test]
3054    fn test_token_scanner_stub() {
3055        let input = "( ) ; // comment\n)";
3056        let mut scanner = TokenScanner::from_str(input);
3057        let t1 = scanner.popt().expect("no scan error").unwrap();
3058        assert!(matches!(t1.payload, TokenPayload::OParen));
3059        let t2 = scanner.popt().expect("no scan error").unwrap();
3060        assert!(matches!(t2.payload, TokenPayload::CParen));
3061        let t3 = scanner.popt().expect("no scan error").unwrap();
3062        assert!(matches!(t3.payload, TokenPayload::Semi));
3063        let t4 = scanner.popt().expect("no scan error").unwrap();
3064        assert!(matches!(t4.payload, TokenPayload::Comment(ref s) if s == " comment"));
3065        let t5 = scanner.popt().expect("no scan error").unwrap();
3066        assert!(matches!(t5.payload, TokenPayload::CParen));
3067        assert!(scanner.popt().expect("no scan error").is_none());
3068    }
3069
3070    #[test]
3071    fn test_token_scanner_input_declaration() {
3072        let input = "input [255:0] a;";
3073        let mut scanner = TokenScanner::from_str(input);
3074        // 'input' should be a keyword (if supported), otherwise identifier
3075        let t1 = scanner.popt().expect("no scan error").unwrap();
3076        match t1.payload {
3077            TokenPayload::Identifier(ref s) => assert_eq!(s, "input"),
3078            TokenPayload::Keyword(_) => assert_eq!(format!("{:?}", t1.payload), "Keyword(Input)"),
3079            _ => panic!("Expected identifier or keyword for 'input'"),
3080        }
3081        let t2 = scanner.popt().expect("no scan error").unwrap();
3082        assert!(matches!(t2.payload, TokenPayload::OBrack));
3083        let t3 = scanner.popt().expect("no scan error").unwrap();
3084        match t3.payload {
3085            TokenPayload::VerilogInt { width, ref value } => {
3086                assert_eq!(width, None);
3087                assert_eq!(value.get_bit_count(), 32); // Default width for IrBits is now 32
3088                assert_eq!(value.to_string(), "bits[32]:255");
3089            }
3090            _ => panic!("Expected VerilogInt for 255"),
3091        }
3092        // Colon not yet supported
3093        let t4 = scanner.popt().expect("no scan error").unwrap();
3094        assert!(matches!(t4.payload, TokenPayload::Colon));
3095        let t5 = scanner.popt().expect("no scan error").unwrap();
3096        match t5.payload {
3097            TokenPayload::VerilogInt { width, ref value } => {
3098                assert_eq!(width, None);
3099                assert_eq!(value.get_bit_count(), 32); // Default width for IrBits is now 32
3100                assert_eq!(value.to_string(), "bits[32]:0");
3101            }
3102            _ => panic!("Expected VerilogInt for 0"),
3103        }
3104        let t6 = scanner.popt().expect("no scan error").unwrap();
3105        assert!(matches!(t6.payload, TokenPayload::CBrack));
3106        let t7 = scanner.popt().expect("no scan error").unwrap();
3107        assert!(matches!(t7.payload, TokenPayload::Identifier(ref s) if s == "a"));
3108        let t8 = scanner.popt().expect("no scan error").unwrap();
3109        assert!(matches!(t8.payload, TokenPayload::Semi));
3110        assert!(scanner.popt().expect("no scan error").is_none());
3111    }
3112
3113    #[test]
3114    fn test_token_scanner_annotation_tokenization() {
3115        let input = r#"(* foo = "bar" *)"#;
3116        let mut scanner = TokenScanner::from_str(input);
3117        let t = scanner.popt().expect("no scan error").unwrap();
3118        match t.payload {
3119            TokenPayload::Annotation { ref key, ref value } => {
3120                assert_eq!(key, "foo");
3121                match value {
3122                    AnnotationValue::String(s) => assert_eq!(s, "bar"),
3123                    _ => panic!("Expected string annotation value"),
3124                }
3125            }
3126            _ => panic!("Expected annotation token"),
3127        }
3128        assert!(scanner.popt().expect("no scan error").is_none());
3129    }
3130
3131    #[test]
3132    fn test_token_scanner_annotation_with_leading_whitespace() {
3133        let input = r#"  (* foo = "bar" *)"#;
3134        let mut scanner = TokenScanner::from_str(input);
3135        let t = scanner.popt().expect("no scan error").unwrap();
3136        match t.payload {
3137            TokenPayload::Annotation { ref key, ref value } => {
3138                assert_eq!(key, "foo");
3139                match value {
3140                    AnnotationValue::String(s) => assert_eq!(s, "bar"),
3141                    _ => panic!("Expected string annotation value"),
3142                }
3143            }
3144            _ => panic!("Expected annotation token"),
3145        }
3146        assert!(scanner.popt().expect("no scan error").is_none());
3147    }
3148
3149    #[test]
3150    fn test_skip_timescale_at_top() {
3151        let src = "`timescale 10ps/10ps\nmodule m(); endmodule\n";
3152        let mut parser = Parser::new(TokenScanner::from_str(src));
3153        let modules = parser.parse_file().expect("parse ok");
3154        assert_eq!(modules.len(), 1);
3155    }
3156
3157    #[test]
3158    fn test_skip_timescale_with_leading_spaces() {
3159        let src = "   `timescale 1ns/1ps\nmodule m(); endmodule\n";
3160        let mut parser = Parser::new(TokenScanner::from_str(src));
3161        let modules = parser.parse_file().expect("parse ok");
3162        assert_eq!(modules.len(), 1);
3163    }
3164
3165    #[test]
3166    fn test_skip_timescale_after_comment() {
3167        let src = "// comment\n`timescale 1ns/1ps\nmodule m(); endmodule\n";
3168        let mut parser = Parser::new(TokenScanner::from_str(src));
3169        let modules = parser.parse_file().expect("parse ok");
3170        assert_eq!(modules.len(), 1);
3171    }
3172
3173    #[test]
3174    fn test_skip_timescale_inside_module() {
3175        let src = "module m();\n`timescale 1ns/1ps\nendmodule\n";
3176        let mut parser = Parser::new(TokenScanner::from_str(src));
3177        let modules = parser.parse_file().expect("parse ok");
3178        assert_eq!(modules.len(), 1);
3179    }
3180
3181    #[test]
3182    fn test_token_scanner_endmodule_keyword() {
3183        let input = "endmodule";
3184        let mut scanner = TokenScanner::from_str(input);
3185        let t = scanner.popt().expect("no scan error").unwrap();
3186        assert!(matches!(
3187            t.payload,
3188            TokenPayload::Keyword(Keyword::Endmodule)
3189        ));
3190        assert!(scanner.popt().expect("no scan error").is_none());
3191    }
3192
3193    #[test]
3194    fn test_parse_wire_decl_with_width() {
3195        let input = "wire [7:0] foo, bar;";
3196        let mut parser = Parser::new(TokenScanner::from_str(input));
3197        let net_indices = parser.parse_wire_decl().expect("no error");
3198        assert_eq!(net_indices.len(), 2);
3199        let foo = &parser.nets[net_indices[0].0];
3200        let bar = &parser.nets[net_indices[1].0];
3201        assert_eq!(foo.width, Some((7, 0)));
3202        assert_eq!(bar.width, Some((7, 0)));
3203    }
3204
3205    #[test]
3206    fn test_parse_wire_decl_without_width() {
3207        let input = "wire foo, bar;";
3208        let mut parser = Parser::new(TokenScanner::from_str(input));
3209        let net_indices = parser.parse_wire_decl().expect("no error");
3210        assert_eq!(net_indices.len(), 2);
3211        let foo = &parser.nets[net_indices[0].0];
3212        let bar = &parser.nets[net_indices[1].0];
3213        assert_eq!(foo.width, None);
3214        assert_eq!(bar.width, None);
3215    }
3216
3217    #[test]
3218    fn test_parse_wire_decl_real_world_examples() {
3219        // Example: wire [255:0] a;
3220        let input = "wire [255:0] a;";
3221        let mut parser = Parser::new(TokenScanner::from_str(input));
3222        let net_indices = parser.parse_wire_decl().expect("no error");
3223        assert_eq!(net_indices.len(), 1);
3224        let a = &parser.nets[net_indices[0].0];
3225        assert_eq!(a.width, Some((255, 0)));
3226
3227        // Example: wire clk;
3228        let input = "wire clk;";
3229        let mut parser = Parser::new(TokenScanner::from_str(input));
3230        let net_indices = parser.parse_wire_decl().expect("no error");
3231        assert_eq!(net_indices.len(), 1);
3232        let clk = &parser.nets[net_indices[0].0];
3233        assert_eq!(clk.width, None);
3234
3235        // Example: wire [26:0] out;
3236        let input = "wire [26:0] out;";
3237        let mut parser = Parser::new(TokenScanner::from_str(input));
3238        let net_indices = parser.parse_wire_decl().expect("no error");
3239        assert_eq!(net_indices.len(), 1);
3240        let out = &parser.nets[net_indices[0].0];
3241        assert_eq!(out.width, Some((26, 0)));
3242
3243        // Example: wire \p0_umul_8[0] ;
3244        let input = "wire \\p0_umul_8[0] ;";
3245        let mut parser = Parser::new(TokenScanner::from_str(input));
3246        let net_indices = parser.parse_wire_decl().expect("no error");
3247        assert_eq!(net_indices.len(), 1);
3248        let p0_net = &parser.nets[net_indices[0].0];
3249        assert_eq!(
3250            parser.interner.resolve(p0_net.name).unwrap(),
3251            "p0_umul_8[0]"
3252        );
3253        assert_eq!(p0_net.width, None);
3254    }
3255
3256    #[test]
3257    fn test_parse_wire_decl_with_annotation_between() {
3258        // Example with annotation before wire decl
3259        let input = r#"(* src = "foo.sv:3.22-3.23" *)
3260wire [255:0] a;"#;
3261        let mut parser = Parser::new(TokenScanner::from_str(input));
3262        // Skip annotation tokens before wire decl
3263        loop {
3264            let peek = parser.scanner.peekt().expect("no scan error");
3265            match peek {
3266                Some(tok) => match &tok.payload {
3267                    TokenPayload::Annotation { .. } | TokenPayload::Comment(_) => {
3268                        parser.scanner.popt().expect("no scan error");
3269                    }
3270                    TokenPayload::Keyword(Keyword::Wire) => break,
3271                    _ => panic!("unexpected token before wire decl: {:?}", tok.payload),
3272                },
3273                None => panic!("unexpected EOF before wire decl"),
3274            }
3275        }
3276        // Now parse the wire decl
3277        let net_indices = parser.parse_wire_decl().expect("no error");
3278        assert_eq!(net_indices.len(), 1);
3279        let a = &parser.nets[net_indices[0].0];
3280        assert_eq!(a.width, Some((255, 0)));
3281    }
3282
3283    #[test]
3284    fn test_parse_instance_with_bit_and_part_select() {
3285        // Setup: declare wire [7:0] b;
3286        let mut parser = Parser::new(TokenScanner::from_str("wire [7:0] b;"));
3287        parser.parse_wire_decl().expect("no error");
3288        // Instance with .A(b)
3289        let input = "TypeName inst (.A(b));";
3290        let mut parser2 = Parser::new(TokenScanner::from_str(input));
3291        parser2.nets = parser.nets.clone();
3292        parser2.interner = parser.interner.clone();
3293        let inst = parser2.parse_instance().expect("no error");
3294        assert_eq!(inst.connections.len(), 1);
3295        match &inst.connections[0].1 {
3296            NetRef::Simple(_) => {}
3297            _ => panic!("Expected Simple net ref"),
3298        }
3299        // Instance with .A(b[5])
3300        let input = "TypeName inst (.A(b[5]));";
3301        let mut parser3 = Parser::new(TokenScanner::from_str(input));
3302        parser3.nets = parser.nets.clone();
3303        parser3.interner = parser.interner.clone();
3304        let inst = parser3.parse_instance().expect("no error");
3305        assert_eq!(inst.connections.len(), 1);
3306        match &inst.connections[0].1 {
3307            NetRef::BitSelect(_, idx) => assert_eq!(*idx, 5),
3308            _ => panic!("Expected BitSelect net ref"),
3309        }
3310        // Instance with .A(b[7:0])
3311        let input = "TypeName inst (.A(b[7:0]));";
3312        let mut parser4 = Parser::new(TokenScanner::from_str(input));
3313        parser4.nets = parser.nets.clone();
3314        parser4.interner = parser.interner.clone();
3315        let inst = parser4.parse_instance().expect("no error");
3316        assert_eq!(inst.connections.len(), 1);
3317        match &inst.connections[0].1 {
3318            NetRef::PartSelect(_, msb, lsb) => {
3319                assert_eq!((*msb, *lsb), (7, 0));
3320            }
3321            _ => panic!("Expected PartSelect net ref"),
3322        }
3323    }
3324
3325    #[test]
3326    fn test_token_scanner_escaped_identifier() {
3327        let input = "\\escaped-ident ;";
3328        let mut scanner = TokenScanner::from_str(input);
3329        let t1 = scanner.popt().expect("no scan error").unwrap();
3330        assert!(matches!(t1.payload, TokenPayload::Identifier(ref s) if s == "escaped-ident"));
3331        let t2 = scanner.popt().expect("no scan error").unwrap();
3332        assert!(matches!(t2.payload, TokenPayload::Semi));
3333        assert!(scanner.popt().expect("no scan error").is_none());
3334    }
3335
3336    #[test]
3337    fn test_token_scanner_escaped_identifier_with_special_chars() {
3338        let input = "\\!@#$%^&*()-+= ;";
3339        let mut scanner = TokenScanner::from_str(input);
3340        let t1 = scanner.popt().expect("no scan error").unwrap();
3341        assert!(matches!(t1.payload, TokenPayload::Identifier(ref s) if s == "!@#$%^&*()-+="));
3342        let t2 = scanner.popt().expect("no scan error").unwrap();
3343        assert!(matches!(t2.payload, TokenPayload::Semi));
3344        assert!(scanner.popt().expect("no scan error").is_none());
3345    }
3346
3347    #[test]
3348    fn test_token_scanner_escaped_identifier_named_like_keyword() {
3349        let input = "\\module ;";
3350        let mut scanner = TokenScanner::from_str(input);
3351        let t1 = scanner.popt().expect("no scan error").unwrap();
3352        assert!(matches!(t1.payload, TokenPayload::Identifier(ref s) if s == "module"));
3353        let t2 = scanner.popt().expect("no scan error").unwrap();
3354        assert!(matches!(t2.payload, TokenPayload::Semi));
3355        assert!(scanner.popt().expect("no scan error").is_none());
3356    }
3357
3358    #[test]
3359    fn test_token_scanner_escaped_identifier_leading_backslash_from_error() {
3360        // This test is based on the error message provided:
3361        // wire \p0_umul_8[0] ;
3362        let input = "\\p0_umul_8[0] ;";
3363        let mut scanner = TokenScanner::from_str(input);
3364        let t = scanner.popt().expect("no scan error").unwrap();
3365        assert!(matches!(t.payload, TokenPayload::Identifier(ref s) if s == "p0_umul_8[0]"));
3366        assert!(scanner.popt().expect("no scan error").unwrap().payload == TokenPayload::Semi);
3367        assert!(scanner.popt().expect("no scan error").is_none());
3368    }
3369
3370    #[test]
3371    fn test_parse_instance_with_literal_tieoff() {
3372        // Setup: declare wire b;
3373        let mut parser = Parser::new(TokenScanner::from_str("wire b;"));
3374        parser.parse_wire_decl().expect("no error");
3375        // Instance with .A(1'b0)
3376        let input = "TypeName inst (.A(1'b0));";
3377        let mut parser2 = Parser::new(TokenScanner::from_str(input));
3378        parser2.nets = parser.nets.clone();
3379        parser2.interner = parser.interner.clone();
3380        let inst = parser2
3381            .parse_instance()
3382            .expect("should parse instance with literal tie-off");
3383        assert_eq!(inst.connections.len(), 1);
3384        let (port, netref) = &inst.connections[0];
3385        let port_name = parser2.interner.resolve(*port).unwrap();
3386        assert_eq!(port_name, "A");
3387        match netref {
3388            NetRef::Literal(bits) => {
3389                // Should be 1'b0, i.e. width=1, value=0
3390                assert_eq!(bits.get_bit_count(), 1);
3391                assert_eq!(bits.to_string(), "bits[1]:0");
3392            }
3393            _ => panic!("Expected Literal net ref, got {:?}", netref),
3394        }
3395    }
3396
3397    #[test]
3398    fn test_token_scanner_verilog_style_literal() {
3399        let input = "1'b0 8'hFF 16'd42";
3400        let mut scanner = TokenScanner::from_str(input);
3401        let t1 = scanner.popt().expect("no scan error").unwrap();
3402        match t1.payload {
3403            TokenPayload::VerilogInt { width, ref value } => {
3404                assert_eq!(width, Some(1));
3405                assert_eq!(value.get_bit_count(), 1);
3406                assert_eq!(value.to_string(), "bits[1]:0");
3407            }
3408            _ => panic!("Expected VerilogInt for 1'b0"),
3409        }
3410        let t2 = scanner.popt().expect("no scan error").unwrap();
3411        match t2.payload {
3412            TokenPayload::VerilogInt { width, ref value } => {
3413                assert_eq!(width, Some(8));
3414                assert_eq!(value.get_bit_count(), 8);
3415                assert_eq!(value.to_string(), "bits[8]:255");
3416            }
3417            _ => panic!("Expected VerilogInt for 8'hFF"),
3418        }
3419
3420        let t3 = scanner.popt().expect("no scan error").unwrap();
3421        match t3.payload {
3422            TokenPayload::VerilogInt { width, ref value } => {
3423                assert_eq!(width, Some(16));
3424                assert_eq!(value.get_bit_count(), 16);
3425                assert_eq!(value.to_string(), "bits[16]:42");
3426            }
3427            _ => panic!("Expected VerilogInt for 16'd42"),
3428        }
3429        assert!(scanner.popt().expect("no scan error").is_none());
3430    }
3431
3432    #[test]
3433    fn test_implicit_net_created_on_use_in_instance_connection() {
3434        // Module where a net is only used in instance connections (no explicit
3435        // wire declaration). With implicit nets enabled, parsing should succeed
3436        // and the net should appear in the global nets array.
3437        let src = r#"
3438module m(a, y);
3439  input a;
3440  output y;
3441  DummyCell u0 (.in_valid(a), .in(a), .out_valid(data_valid_d));
3442  DummyCell u1 (.in_valid(data_valid_d), .in(a), .out_valid(y));
3443endmodule
3444"#;
3445        let mut parser = Parser::new(TokenScanner::from_str(src));
3446        let modules = parser.parse_file().expect("parse ok");
3447        assert_eq!(modules.len(), 1);
3448        // There should be a net named data_valid_d created implicitly.
3449        let dv_nets: Vec<&Net> = parser
3450            .nets
3451            .iter()
3452            .filter(|n| parser.interner.resolve(n.name).unwrap() == "data_valid_d")
3453            .collect();
3454        assert_eq!(dv_nets.len(), 1);
3455        assert_eq!(dv_nets[0].width, Some((0, 0)));
3456    }
3457
3458    #[test]
3459    fn test_assign_use_before_vector_port_declaration_upgrades_implicit_widths() {
3460        let src = r#"
3461module m(a, y);
3462  assign y[3:0] = a;
3463  input [3:0] a;
3464  output [3:0] y;
3465endmodule
3466"#;
3467        let mut parser = Parser::new(TokenScanner::from_str(src));
3468        let modules = parser.parse_file().expect("parse ok");
3469        assert_eq!(modules.len(), 1);
3470
3471        let a_nets: Vec<&Net> = parser
3472            .nets
3473            .iter()
3474            .filter(|n| parser.interner.resolve(n.name).unwrap() == "a")
3475            .collect();
3476        let y_nets: Vec<&Net> = parser
3477            .nets
3478            .iter()
3479            .filter(|n| parser.interner.resolve(n.name).unwrap() == "y")
3480            .collect();
3481        assert_eq!(a_nets.len(), 1);
3482        assert_eq!(y_nets.len(), 1);
3483        assert_eq!(a_nets[0].width, Some((3, 0)));
3484        assert_eq!(y_nets[0].width, Some((3, 0)));
3485    }
3486
3487    #[test]
3488    fn test_assign_use_before_vector_wire_declaration_upgrades_implicit_widths() {
3489        let src = r#"
3490module m(y);
3491  assign y = tmp;
3492  wire [3:0] tmp;
3493  output [3:0] y;
3494endmodule
3495"#;
3496        let mut parser = Parser::new(TokenScanner::from_str(src));
3497        let modules = parser.parse_file().expect("parse ok");
3498        assert_eq!(modules.len(), 1);
3499
3500        let tmp_nets: Vec<&Net> = parser
3501            .nets
3502            .iter()
3503            .filter(|n| parser.interner.resolve(n.name).unwrap() == "tmp")
3504            .collect();
3505        let y_nets: Vec<&Net> = parser
3506            .nets
3507            .iter()
3508            .filter(|n| parser.interner.resolve(n.name).unwrap() == "y")
3509            .collect();
3510        assert_eq!(tmp_nets.len(), 1);
3511        assert_eq!(y_nets.len(), 1);
3512        assert_eq!(tmp_nets[0].width, Some((3, 0)));
3513        assert_eq!(y_nets[0].width, Some((3, 0)));
3514    }
3515
3516    #[test]
3517    fn test_concat_with_trailing_verilog_literal_and_close_brace() {
3518        // Regression test: previously, the '}' in "3'b0})" could be swallowed
3519        // into the VerilogInt token, causing the concat parser to fail with
3520        // "expected '}' to close concatenation".
3521        let src = r#"
3522module m(a, y);
3523  input [1:0] a;
3524  output [3:0] y;
3525  MYCELL u1 (.Y({a, 3'b0}));
3526endmodule
3527"#;
3528        let mut parser = Parser::new(TokenScanner::from_str(src));
3529        let modules = parser.parse_file().expect("parse ok");
3530        assert_eq!(modules.len(), 1);
3531        let insts = &modules[0].instances;
3532        assert_eq!(insts.len(), 1);
3533        let (port_sym, netref) = &insts[0].connections[0];
3534        let port_name = parser.interner.resolve(*port_sym).unwrap();
3535        assert_eq!(port_name, "Y");
3536        match netref {
3537            NetRef::Concat(elems) => {
3538                assert_eq!(elems.len(), 2);
3539            }
3540            other => panic!("expected Concat for .Y connection, got {:?}", other),
3541        }
3542    }
3543
3544    #[test]
3545    fn test_unknown_net_errors_when_implicit_nets_disabled() {
3546        // When implicit nets are disabled, undeclared nets should still
3547        // produce a clear error rather than being synthesized implicitly.
3548        let src = r#"
3549module m(a, y);
3550  input a;
3551  output y;
3552  DummyCell u0 (.in_valid(a), .in(a), .out_valid(data_valid_d));
3553endmodule
3554"#;
3555        let mut parser = Parser::new_with_options(
3556            TokenScanner::from_str(src),
3557            /* allow_implicit_nets= */ false,
3558        );
3559        let err = parser
3560            .parse_file()
3561            .expect_err("should error on unknown net");
3562        assert!(
3563            err.message.contains("not declared as wire"),
3564            "unexpected error message: {}",
3565            err.message
3566        );
3567    }
3568
3569    #[test]
3570    fn test_parse_instance_unconnected_output() {
3571        let src = r#"
3572module m(a, y);
3573  input a;
3574  output y;
3575  wire a, y;
3576  INVX1 u1 (.A(a), .Y());
3577endmodule
3578"#;
3579        let mut parser = Parser::new(TokenScanner::from_str(src));
3580        let modules = parser.parse_file().expect("parse ok");
3581        assert_eq!(modules.len(), 1);
3582        let insts = &modules[0].instances;
3583        assert_eq!(insts.len(), 1);
3584        let conn = &insts[0].connections[1].1;
3585        match conn {
3586            NetRef::Unconnected => {}
3587            _ => panic!("expected Unconnected for .Y()"),
3588        }
3589    }
3590
3591    #[test]
3592    fn test_parse_instance_unconnected_input_and_comments() {
3593        let src = r#"
3594module m(y);
3595  output y;
3596  wire y;
3597  AND2 u1 (.A(/*c*/ ), .B( // c
3598 ), .Y(y));
3599endmodule
3600"#;
3601        let mut parser = Parser::new(TokenScanner::from_str(src));
3602        let modules = parser.parse_file().expect("parse ok");
3603        assert_eq!(modules.len(), 1);
3604        let insts = &modules[0].instances;
3605        assert_eq!(insts.len(), 1);
3606        // .A() should be Unconnected
3607        match &insts[0].connections[0].1 {
3608            NetRef::Unconnected => {}
3609            other => panic!("expected Unconnected for .A(), got {:?}", other),
3610        }
3611        // .B() should be Unconnected
3612        match &insts[0].connections[1].1 {
3613            NetRef::Unconnected => {}
3614            other => panic!("expected Unconnected for .B(), got {:?}", other),
3615        }
3616    }
3617
3618    #[test]
3619    fn test_non_ansi_body_ports_create_nets() {
3620        let src = r#"
3621module m(a, y);
3622  input a;
3623  output y;
3624  INVX1 u1 (.A(a), .Y(y));
3625endmodule
3626"#;
3627        let mut parser = Parser::new(TokenScanner::from_str(src));
3628        let modules = parser.parse_file().expect("parse ok");
3629        assert_eq!(modules.len(), 1);
3630        // Ensure nets for a and y exist with no widths
3631        let a_net = parser
3632            .nets
3633            .iter()
3634            .find(|n| parser.interner.resolve(n.name).unwrap() == "a")
3635            .unwrap();
3636        let y_net = parser
3637            .nets
3638            .iter()
3639            .find(|n| parser.interner.resolve(n.name).unwrap() == "y")
3640            .unwrap();
3641        assert_eq!(a_net.width, None);
3642        assert_eq!(y_net.width, None);
3643    }
3644
3645    #[test]
3646    fn test_non_ansi_ports_are_ordered_by_header_list() {
3647        let src = r#"
3648module m(p0, p1, p2, p3, p4, p5, p6);
3649  input p0, p1, p3;
3650  input [57:0] p2;
3651  input [14:0] p4, p5;
3652  output [18:0] p6;
3653endmodule
3654"#;
3655        let mut parser = Parser::new(TokenScanner::from_str(src));
3656        let modules = parser.parse_file().expect("parse ok");
3657        assert_eq!(modules.len(), 1);
3658        let port_names: Vec<String> = modules[0]
3659            .ports
3660            .iter()
3661            .map(|p| parser.interner.resolve(p.name).unwrap().to_string())
3662            .collect();
3663        assert_eq!(port_names, vec!["p0", "p1", "p2", "p3", "p4", "p5", "p6",]);
3664        assert_eq!(modules[0].ports[2].width, Some((57, 0)));
3665        assert_eq!(modules[0].ports[6].width, Some((18, 0)));
3666    }
3667
3668    #[test]
3669    fn test_non_ansi_vector_output_and_bitselect() {
3670        let src = r#"
3671module m(a, out);
3672  input a;
3673  output [7:0] out;
3674  Type inst (.A(out[3]));
3675endmodule
3676"#;
3677        let mut parser = Parser::new(TokenScanner::from_str(src));
3678        let modules = parser.parse_file().expect("parse ok");
3679        assert_eq!(modules.len(), 1);
3680        let out_net = parser
3681            .nets
3682            .iter()
3683            .find(|n| parser.interner.resolve(n.name).unwrap() == "out")
3684            .unwrap();
3685        assert_eq!(out_net.width, Some((7, 0)));
3686        // And the instance should have one connection with a BitSelect
3687        let inst = &modules[0].instances[0];
3688        match inst.connections[0].1 {
3689            NetRef::BitSelect(_, idx) => assert_eq!(idx, 3),
3690            ref other => panic!("expected BitSelect, got {:?}", other),
3691        }
3692    }
3693
3694    #[test]
3695    fn test_duplicate_wire_after_port_decl_same_width_ok() {
3696        let src = r#"
3697module m(a);
3698  input [3:0] a;
3699  wire [3:0] a;
3700endmodule
3701"#;
3702        let mut parser = Parser::new(TokenScanner::from_str(src));
3703        let modules = parser.parse_file().expect("parse ok");
3704        assert_eq!(modules.len(), 1);
3705        let a_nets: Vec<&Net> = parser
3706            .nets
3707            .iter()
3708            .filter(|n| parser.interner.resolve(n.name).unwrap() == "a")
3709            .collect();
3710        assert_eq!(a_nets.len(), 1);
3711        assert_eq!(a_nets[0].width, Some((3, 0)));
3712    }
3713
3714    #[test]
3715    fn test_conflicting_widths_error() {
3716        let src = r#"
3717module m(out);
3718  output [7:0] out;
3719  wire [3:0] out;
3720endmodule
3721"#;
3722        let mut parser = Parser::new(TokenScanner::from_str(src));
3723        let err = parser
3724            .parse_file()
3725            .expect_err("should error on width conflict");
3726        assert!(err.message.contains("conflicting widths for net 'out'"));
3727        assert!(
3728            err.message.contains("previously determined width was"),
3729            "error message should mention previous width location for diagnostics: {}",
3730            err.message
3731        );
3732    }
3733
3734    #[test]
3735    fn test_conflicting_widths_across_modules_are_ok() {
3736        // Two separate modules may legitimately use the same net name with
3737        // different widths; width consistency is a per-module property, not
3738        // global to the file.
3739        let src = r#"
3740module m1(gen_in);
3741  input [7:0] gen_in;
3742endmodule
3743
3744module m2(gen_in);
3745  input [3:0] gen_in;
3746endmodule
3747"#;
3748        let mut parser = Parser::new(TokenScanner::from_str(src));
3749        let modules = parser.parse_file().expect("parse should succeed");
3750        assert_eq!(modules.len(), 2);
3751        // Sanity check: each module sees exactly one net named gen_in, with
3752        // its own width.
3753        let gen_in_nets: Vec<&Net> = parser
3754            .nets
3755            .iter()
3756            .filter(|n| parser.interner.resolve(n.name).unwrap() == "gen_in")
3757            .collect();
3758        assert_eq!(gen_in_nets.len(), 2);
3759        assert!(gen_in_nets.iter().any(|n| n.width == Some((7, 0))));
3760        assert!(gen_in_nets.iter().any(|n| n.width == Some((3, 0))));
3761    }
3762}