ptx_parser/
parse.rs

1use crate::{
2    r#type::{
3        AddressBase, AddressDisplacement, AddressDisplacementKind, AddressSign,
4        AddressSizeDirective, ArraySpecifier, AsyncGroupModifier, AtomicOperationModifier,
5        CacheModifier, CallModifier, ConditionModifier, DwarfDirective, EntryFunction,
6        FileDirective, FuncFunction, FunctionAlias, FunctionBody, FunctionDeclarationKind,
7        FunctionDim3, FunctionEntryDirective, FunctionHeaderDirective, FunctionKernelDirective,
8        FunctionLinkage, FunctionStatement, FunctionVisibility, GenericFunctionDeclaration,
9        GlobalAddressSpace, GlobalInitializer, GlobalLinkage, GlobalMutability, GlobalVisibility,
10        InitializerValue, Instruction, LinkingDirective, LinkingDirectiveKind, LocationDirective,
11        MathModeModifier, MemoryOperand, MemoryOrderModifier, MemoryScopeModifier, ModifierKind,
12        Module, ModuleDebugDirective, ModuleDirective, ModuleDirectiveKind,
13        ModuleVariableDirective, NumericLiteral, OpcodeKind, Operand, Parameter,
14        ParameterQualifiers, ParameterSpecifier, ParameterStorage, PointerAddressSpace,
15        PointerQualifier, PragmaDirective, PtxParseError, RegisterDeclaration, RegisterSpecifier,
16        RegisterType, RoundingModifier, ScalarType, SectionDirective, ShuffleModifier,
17        StateSpaceModifier, StatementDirective, StatementSectionDirective, SynchronizationModifier,
18        TargetDirective, TypeModifier, VariableDirective, VariableQualifier, VersionDirective,
19    },
20    InstructionOpcode,
21};
22
23use std::collections::VecDeque;
24
25/// Parse a PTX source string into a lightweight abstract syntax tree (AST).
26///
27/// The parser performs a tolerant, line-oriented pass that recognises
28/// module-level directives and function definitions. Inside functions it
29/// categorises labels, directives, and instructions, providing a structured
30/// representation without attempting full semantic validation of PTX.
31pub fn parse(source: &str) -> Result<Module, PtxParseError> {
32    Parser::new(source).parse()
33}
34
35/// Parse a single directive line without building a full module AST.
36pub fn parse_module_directive(
37    line: &str,
38    line_number: usize,
39) -> Result<ModuleDirective, PtxParseError> {
40    let stripped = strip_comments(line);
41    let trimmed = stripped.trim();
42    if trimmed.is_empty() {
43        return Err(PtxParseError::InvalidDirective {
44            line: line_number,
45            message: "empty directive".into(),
46        });
47    }
48    if trimmed.starts_with(".tex") {
49        let tex = parse_tex_directive(trimmed, line_number)?;
50        return Ok(ModuleDirective::ModuleVariable(
51            ModuleVariableDirective::Tex(tex),
52        ));
53    }
54
55    if likely_state_space(trimmed) {
56        if let Some(variable) = parse_state_space_variable(trimmed, line_number)? {
57            return Ok(ModuleDirective::ModuleVariable(variable));
58        }
59    }
60    parse_module_directive_internal(trimmed, line_number)
61}
62
63pub fn parse_entry_directive(line: &str) -> Result<FunctionEntryDirective, PtxParseError> {
64    let mut in_declaration = true;
65    match parse_function_directive_stmt(line.trim(), None, 1, &mut in_declaration)? {
66        FunctionBodyItem::Entry(entry) => Ok(entry),
67        _ => Err(PtxParseError::InvalidDirective {
68            line: 1,
69            message: "expected entry directive".into(),
70        }),
71    }
72}
73
74pub fn parse_stmt_directive(line: &str) -> Result<FunctionStatement, PtxParseError> {
75    let mut in_declaration = false;
76    match parse_function_directive_stmt(line.trim(), None, 1, &mut in_declaration)? {
77        FunctionBodyItem::Statement(stmt) => Ok(stmt),
78        _ => Err(PtxParseError::InvalidDirective {
79            line: 1,
80            message: "expected statement directive".into(),
81        }),
82    }
83}
84
85pub fn parse_instruction_line(line: &str) -> Result<Instruction, PtxParseError> {
86    parse_instruction(line.trim(), None, 1)
87}
88
89struct Parser<'a> {
90    lines: Vec<&'a str>,
91    index: usize,
92}
93
94enum FunctionBodyItem {
95    Entry(FunctionEntryDirective),
96    Statement(FunctionStatement),
97}
98
99enum ParsedFunction {
100    Entry(EntryFunction),
101    Func(FuncFunction),
102    Linking(LinkingDirective),
103}
104
105impl<'a> Parser<'a> {
106    fn new(source: &'a str) -> Self {
107        Self {
108            lines: source.lines().collect(),
109            index: 0,
110        }
111    }
112
113    fn parse(mut self) -> Result<Module, PtxParseError> {
114        let mut module = Module {
115            directives: Vec::new(),
116        };
117
118        while self.index < self.lines.len() {
119            let raw_line = self.lines[self.index];
120            let trimmed = strip_comments(raw_line);
121            self.index += 1;
122
123            if trimmed.is_empty() {
124                continue;
125            }
126
127            if trimmed == "{" || trimmed == "}" {
128                continue;
129            }
130
131            if trimmed.starts_with(".alias") {
132                let alias = parse_alias(&trimmed, self.index)?;
133                module.directives.push(ModuleDirective::FunctionKernel(
134                    FunctionKernelDirective::Alias(alias),
135                ));
136                continue;
137            }
138
139            if contains_keyword(&trimmed, ".entry") || contains_keyword(&trimmed, ".func") {
140                match self.parse_function(trimmed)? {
141                    ParsedFunction::Entry(function) => {
142                        module.directives.push(ModuleDirective::FunctionKernel(
143                            FunctionKernelDirective::Entry(function),
144                        ));
145                    }
146                    ParsedFunction::Func(function) => {
147                        module.directives.push(ModuleDirective::FunctionKernel(
148                            FunctionKernelDirective::Func(function),
149                        ));
150                    }
151                    ParsedFunction::Linking(linking) => {
152                        module.directives.push(ModuleDirective::Linking(linking));
153                    }
154                }
155                continue;
156            }
157
158            let line_number = self.index;
159            if let Some(state_space) = self.try_parse_state_space(trimmed.clone(), line_number)? {
160                module
161                    .directives
162                    .push(ModuleDirective::ModuleVariable(state_space));
163                continue;
164            }
165
166            let directive = parse_module_directive_internal(&trimmed, self.index)?;
167            module.directives.push(directive);
168        }
169
170        Ok(module)
171    }
172
173    fn parse_function(&mut self, header_line: String) -> Result<ParsedFunction, PtxParseError> {
174        let header_start_line = self.index; // 1-based line number after initial increment
175        let mut header = header_line;
176        let mut is_declaration = header.contains(';');
177
178        while !header.contains('{') && !is_declaration {
179            let next_line = self
180                .lines
181                .get(self.index)
182                .ok_or(PtxParseError::UnexpectedEof {
183                    context: "function header",
184                    line: header_start_line,
185                })?;
186            self.index += 1;
187            let trimmed = strip_comments(next_line);
188            if trimmed.is_empty() {
189                continue;
190            }
191            header.push(' ');
192            header.push_str(&trimmed);
193            if trimmed.contains(';') {
194                is_declaration = true;
195            }
196        }
197
198        if is_declaration && !header.contains('{') {
199            if let Some(linking) = parse_function_prototype_linking(&header) {
200                return Ok(ParsedFunction::Linking(linking));
201            }
202
203            panic!(
204                "Unsupported function declaration without body at line {}",
205                header_start_line
206            );
207        }
208
209        let mut header_split = header.splitn(2, '{');
210        let header_part = header_split.next().unwrap_or_default().trim();
211        let after_brace = header_split.next().map(str::trim).unwrap_or("");
212
213        let keyword = if header_part.contains(".entry") {
214            ".entry"
215        } else if header_part.contains(".func") {
216            ".func"
217        } else {
218            return Err(PtxParseError::InvalidFunctionHeader {
219                line: header_start_line,
220                message: "missing .entry or .func keyword".into(),
221            });
222        };
223
224        let (directives, name, params, return_param) =
225            parse_function_header(header_part, keyword, header_start_line)?;
226
227        let mut entry_directives = Vec::new();
228        let mut body_statements = Vec::new();
229        let mut brace_depth: i32 = 1; // account for the opening brace consumed with the header
230        let mut stmt_buffer = String::new();
231        let mut pending_comment: Option<String> = None;
232        let mut in_declaration = true;
233
234        if !after_brace.is_empty() {
235            if process_body_segment(
236                after_brace,
237                None,
238                header_start_line,
239                &mut brace_depth,
240                &mut stmt_buffer,
241                &mut pending_comment,
242                &mut in_declaration,
243                &mut entry_directives,
244                &mut body_statements,
245            )? {
246                if !stmt_buffer.trim().is_empty() {
247                    return Err(PtxParseError::InvalidInstruction {
248                        line: header_start_line,
249                        message: "unterminated instruction in function body".into(),
250                    });
251                }
252
253                let body = FunctionBody {
254                    entry_directives,
255                    statements: body_statements,
256                };
257                return Ok(match keyword {
258                    ".entry" => ParsedFunction::Entry(EntryFunction {
259                        name,
260                        directives,
261                        params,
262                        body,
263                    }),
264                    ".func" => ParsedFunction::Func(FuncFunction {
265                        name,
266                        directives,
267                        return_param,
268                        params,
269                        body,
270                    }),
271                    _ => unreachable!("validated keyword"),
272                });
273            }
274        }
275
276        while self.index < self.lines.len() {
277            let raw_line = self.lines[self.index];
278            self.index += 1;
279
280            let trimmed_line = raw_line.trim();
281            if trimmed_line.is_empty() {
282                continue;
283            }
284            if trimmed_line.starts_with("//") || trimmed_line.starts_with('#') {
285                continue;
286            }
287
288            let (content, comment) = split_comment(trimmed_line);
289            if content.trim().is_empty() {
290                if let Some(comment) = comment {
291                    pending_comment = Some(comment);
292                }
293                continue;
294            }
295
296            if process_body_segment(
297                &content,
298                comment,
299                self.index,
300                &mut brace_depth,
301                &mut stmt_buffer,
302                &mut pending_comment,
303                &mut in_declaration,
304                &mut entry_directives,
305                &mut body_statements,
306            )? {
307                break;
308            }
309        }
310
311        if brace_depth != 0 {
312            return Err(PtxParseError::UnexpectedEof {
313                context: "function body",
314                line: header_start_line,
315            });
316        }
317
318        if !stmt_buffer.trim().is_empty() {
319            return Err(PtxParseError::InvalidInstruction {
320                line: header_start_line,
321                message: "unterminated instruction in function body".into(),
322            });
323        }
324
325        let body = FunctionBody {
326            entry_directives,
327            statements: body_statements,
328        };
329        Ok(match keyword {
330            ".entry" => ParsedFunction::Entry(EntryFunction {
331                name,
332                directives,
333                params,
334                body,
335            }),
336            ".func" => ParsedFunction::Func(FuncFunction {
337                name,
338                directives,
339                return_param,
340                params,
341                body,
342            }),
343            _ => unreachable!("validated keyword"),
344        })
345    }
346
347    fn try_parse_state_space(
348        &mut self,
349        mut current: String,
350        line_number: usize,
351    ) -> Result<Option<ModuleVariableDirective>, PtxParseError> {
352        if !likely_state_space(&current) {
353            return Ok(None);
354        }
355
356        let trimmed = current.trim_start();
357        if trimmed.starts_with(".tex") {
358            while !current.trim_end().ends_with(';') {
359                let next_line = self
360                    .lines
361                    .get(self.index)
362                    .ok_or(PtxParseError::UnexpectedEof {
363                        context: "texture declaration",
364                        line: line_number,
365                    })?;
366                self.index += 1;
367                let trimmed = strip_comments(next_line);
368                if trimmed.is_empty() {
369                    continue;
370                }
371                if !current
372                    .chars()
373                    .last()
374                    .map(|ch| ch.is_whitespace())
375                    .unwrap_or(true)
376                {
377                    current.push(' ');
378                }
379                current.push_str(&trimmed);
380            }
381
382            let tex = parse_tex_directive(&current, line_number)?;
383            return Ok(Some(ModuleVariableDirective::Tex(tex)));
384        }
385
386        while !current.trim_end().ends_with(';') {
387            let next_line = self
388                .lines
389                .get(self.index)
390                .ok_or(PtxParseError::UnexpectedEof {
391                    context: "state space declaration",
392                    line: line_number,
393                })?;
394            self.index += 1;
395            let trimmed = strip_comments(next_line);
396            if trimmed.is_empty() {
397                continue;
398            }
399            if !current
400                .chars()
401                .last()
402                .map(|ch| ch.is_whitespace())
403                .unwrap_or(true)
404            {
405                current.push(' ');
406            }
407            current.push_str(&trimmed);
408        }
409
410        parse_state_space_variable(&current, line_number)
411    }
412}
413
414fn process_body_segment(
415    segment: &str,
416    comment: Option<String>,
417    line_number: usize,
418    brace_depth: &mut i32,
419    stmt_buffer: &mut String,
420    pending_comment: &mut Option<String>,
421    in_declaration: &mut bool,
422    entry_directives: &mut Vec<FunctionEntryDirective>,
423    statements: &mut Vec<FunctionStatement>,
424) -> Result<bool, PtxParseError> {
425    let open = count_occurrences(segment, '{') as i32;
426    let close = count_occurrences(segment, '}') as i32;
427    *brace_depth += open;
428    *brace_depth -= close;
429
430    if *brace_depth < 0 {
431        return Err(PtxParseError::InvalidFunctionHeader {
432            line: line_number,
433            message: "mismatched braces in function body".into(),
434        });
435    }
436
437    let sanitized = segment
438        .replace('{', " ")
439        .replace('}', " ")
440        .trim()
441        .to_string();
442
443    if sanitized.is_empty() {
444        if let Some(comment) = comment {
445            *pending_comment = Some(comment);
446        }
447        return Ok(*brace_depth == 0);
448    }
449
450    if sanitized.ends_with(':') || sanitized.starts_with('.') || sanitized.starts_with("@@") {
451        match parse_function_stmt(&sanitized, comment, line_number, in_declaration)? {
452            FunctionBodyItem::Entry(entry) => entry_directives.push(entry),
453            FunctionBodyItem::Statement(stmt) => statements.push(stmt),
454        }
455        stmt_buffer.clear();
456        *pending_comment = None;
457        return Ok(*brace_depth == 0);
458    }
459
460    if !stmt_buffer.is_empty() {
461        stmt_buffer.push(' ');
462    }
463    stmt_buffer.push_str(&sanitized);
464
465    if let Some(comment) = comment {
466        *pending_comment = Some(comment);
467    }
468
469    while let Some(idx) = stmt_buffer.find(';') {
470        let (statement, rest) = stmt_buffer.split_at(idx + 1);
471        let statement = statement.trim();
472        if !statement.is_empty() {
473            match parse_function_stmt(
474                statement,
475                pending_comment.take(),
476                line_number,
477                in_declaration,
478            )? {
479                FunctionBodyItem::Entry(entry) => entry_directives.push(entry),
480                FunctionBodyItem::Statement(stmt) => statements.push(stmt),
481            };
482        } else {
483            pending_comment.take();
484        }
485        *stmt_buffer = rest.trim_start().to_string();
486    }
487
488    Ok(*brace_depth == 0)
489}
490
491fn parse_function_prototype_linking(header: &str) -> Option<LinkingDirective> {
492    let trimmed = header.trim();
493    if trimmed.is_empty() {
494        return None;
495    }
496
497    let mut tokens = trimmed.split_whitespace();
498    let first = tokens.next()?;
499
500    let kind = match first {
501        ".extern" => LinkingDirectiveKind::Extern,
502        ".visible" => LinkingDirectiveKind::Visible,
503        ".weak" => LinkingDirectiveKind::Weak,
504        ".common" => LinkingDirectiveKind::Common,
505        _ => return None,
506    };
507
508    let prototype = trimmed[first.len()..]
509        .trim()
510        .trim_end_matches(';')
511        .trim()
512        .to_string();
513    let raw = trimmed.to_string();
514
515    Some(LinkingDirective {
516        kind,
517        prototype,
518        raw,
519    })
520}
521
522fn parse_module_directive_internal(
523    line: &str,
524    line_number: usize,
525) -> Result<ModuleDirective, PtxParseError> {
526    let trimmed = line.trim();
527
528    if trimmed.starts_with(".version") {
529        let parts: Vec<_> = trimmed.split_whitespace().collect();
530        if parts.len() < 2 {
531            return Err(PtxParseError::InvalidDirective {
532                line: line_number,
533                message: "missing version number".into(),
534            });
535        }
536        let mut version_iter = parts[1].split('.');
537        let major = version_iter
538            .next()
539            .and_then(|s| s.parse::<u32>().ok())
540            .ok_or_else(|| PtxParseError::InvalidDirective {
541                line: line_number,
542                message: "invalid major version".into(),
543            })?;
544        let minor = version_iter
545            .next()
546            .and_then(|s| s.parse::<u32>().ok())
547            .ok_or_else(|| PtxParseError::InvalidDirective {
548                line: line_number,
549                message: "invalid minor version".into(),
550            })?;
551        return Ok(ModuleDirective::Module(ModuleDirectiveKind::Version(
552            VersionDirective { major, minor },
553        )));
554    }
555
556    if trimmed.starts_with(".target") {
557        let rest = trimmed.trim_start_matches(".target").trim();
558        let directive = parse_target_directive(rest, line_number)?;
559        return Ok(ModuleDirective::Module(ModuleDirectiveKind::Target(
560            directive,
561        )));
562    }
563
564    if trimmed.starts_with(".address_size") {
565        let rest = trimmed.trim_start_matches(".address_size").trim();
566        let size = rest
567            .split_whitespace()
568            .next()
569            .and_then(|s| s.parse::<u32>().ok())
570            .ok_or_else(|| PtxParseError::InvalidDirective {
571                line: line_number,
572                message: "invalid address size".into(),
573            })?;
574        return Ok(ModuleDirective::Module(ModuleDirectiveKind::AddressSize(
575            AddressSizeDirective { size },
576        )));
577    }
578
579    if trimmed.starts_with(".file") {
580        let rest = trimmed.trim_start_matches(".file").trim();
581        if rest.is_empty() {
582            return Err(PtxParseError::InvalidDirective {
583                line: line_number,
584                message: "missing index for .file directive".into(),
585            });
586        }
587
588        let index_len = rest.find(char::is_whitespace).unwrap_or(rest.len());
589        let (index_part, remainder) = rest.split_at(index_len);
590        let index = index_part
591            .parse::<u32>()
592            .map_err(|_| PtxParseError::InvalidDirective {
593                line: line_number,
594                message: format!("invalid .file index '{index_part}'"),
595            })?;
596
597        let path_part = remainder.trim();
598        if path_part.is_empty() {
599            return Err(PtxParseError::InvalidDirective {
600                line: line_number,
601                message: "missing path in .file directive".into(),
602            });
603        }
604
605        let path = if let Some(stripped) = path_part.strip_prefix('"') {
606            if let Some(end_idx) = stripped.find('"') {
607                let path = stripped[..end_idx].to_string();
608                if !stripped[end_idx + 1..].trim().is_empty() {
609                    return Err(PtxParseError::InvalidDirective {
610                        line: line_number,
611                        message: "unexpected tokens after .file path".into(),
612                    });
613                }
614                path
615            } else {
616                return Err(PtxParseError::InvalidDirective {
617                    line: line_number,
618                    message: "unterminated quoted path in .file directive".into(),
619                });
620            }
621        } else {
622            path_part.to_string()
623        };
624
625        return Ok(ModuleDirective::Debug(ModuleDebugDirective::File(
626            FileDirective { index, path },
627        )));
628    }
629
630    if trimmed.starts_with(".section") {
631        let rest = trimmed.trim_start_matches(".section").trim();
632        if rest.is_empty() {
633            return Err(PtxParseError::InvalidDirective {
634                line: line_number,
635                message: "missing section name".into(),
636            });
637        }
638
639        let mut parts = rest.split_whitespace();
640        let name = parts
641            .next()
642            .ok_or_else(|| PtxParseError::InvalidDirective {
643                line: line_number,
644                message: "missing section name".into(),
645            })?
646            .to_string();
647        let attributes = parts.map(|part| part.trim().to_string()).collect();
648        return Ok(ModuleDirective::Debug(ModuleDebugDirective::Section(
649            SectionDirective { name, attributes },
650        )));
651    }
652
653    if trimmed.starts_with("@@") {
654        let mut parts = trimmed.split_whitespace();
655        let keyword = parts.next().unwrap_or_default();
656        let arguments = parts.map(|token| token.to_string()).collect();
657        let directive = DwarfDirective {
658            keyword: keyword.to_string(),
659            arguments,
660            comment: None,
661            raw: trimmed.to_string(),
662        };
663        return Ok(ModuleDirective::Debug(ModuleDebugDirective::Dwarf(
664            directive,
665        )));
666    }
667
668    if let Some(linking) = parse_module_linking(trimmed, line_number)? {
669        return Ok(ModuleDirective::Linking(linking));
670    }
671
672    Err(PtxParseError::InvalidDirective {
673        line: line_number,
674        message: format!("unsupported directive '{line}'"),
675    })
676}
677
678fn parse_module_linking(
679    trimmed: &str,
680    line_number: usize,
681) -> Result<Option<LinkingDirective>, PtxParseError> {
682    for (keyword, kind) in [
683        (".extern", LinkingDirectiveKind::Extern),
684        (".visible", LinkingDirectiveKind::Visible),
685        (".weak", LinkingDirectiveKind::Weak),
686        (".common", LinkingDirectiveKind::Common),
687    ] {
688        if trimmed.starts_with(keyword) {
689            let remainder = trimmed[keyword.len()..].trim();
690            if remainder.is_empty() {
691                return Err(PtxParseError::InvalidDirective {
692                    line: line_number,
693                    message: format!("missing prototype for {keyword} directive"),
694                });
695            }
696            let prototype = remainder.trim_end_matches(';').trim().to_string();
697            return Ok(Some(LinkingDirective {
698                kind,
699                prototype,
700                raw: trimmed.to_string(),
701            }));
702        }
703    }
704
705    Ok(None)
706}
707
708fn parse_tex_directive(line: &str, line_number: usize) -> Result<VariableDirective, PtxParseError> {
709    let trimmed = line.trim();
710    if !trimmed.starts_with(".tex") {
711        return Err(PtxParseError::InvalidDirective {
712            line: line_number,
713            message: "expected .tex directive".into(),
714        });
715    }
716
717    let without_semicolon = trimmed.trim_end_matches(';').trim();
718    let mut tokens: Vec<String> = without_semicolon
719        .split_whitespace()
720        .map(|tok| tok.trim().to_string())
721        .filter(|tok| !tok.is_empty())
722        .collect();
723
724    if tokens.is_empty() || tokens[0] != ".tex" {
725        return Err(PtxParseError::InvalidDirective {
726            line: line_number,
727            message: "malformed .tex directive".into(),
728        });
729    }
730
731    tokens.remove(0);
732    if tokens.is_empty() {
733        return Err(PtxParseError::InvalidDirective {
734            line: line_number,
735            message: "missing identifier in .tex directive".into(),
736        });
737    }
738
739    let name_token = tokens.pop().unwrap();
740    if name_token.starts_with('.') {
741        return Err(PtxParseError::InvalidDirective {
742            line: line_number,
743            message: "invalid texture identifier".into(),
744        });
745    }
746
747    let name = name_token.trim_end_matches(';').to_string();
748    let mut qualifiers = Vec::new();
749    let mut ty = None;
750    for token in tokens {
751        if let Some(scalar) = parse_scalar_type(&token) {
752            if ty.is_some() {
753                return Err(PtxParseError::InvalidDirective {
754                    line: line_number,
755                    message: "multiple type specifiers in .tex directive".into(),
756                });
757            }
758            ty = Some(scalar);
759            continue;
760        }
761
762        qualifiers.push(variable_qualifier_from_token(
763            &token,
764            line_number,
765            VariableQualifierContext::Tex,
766        )?);
767    }
768
769    Ok(VariableDirective {
770        visibility: None,
771        linkages: Vec::new(),
772        address_space: None,
773        mutability: None,
774        alignment: None,
775        ty,
776        qualifiers,
777        name,
778        array: None,
779        initializer: None,
780        raw: line.trim().to_string(),
781    })
782}
783
784fn parse_state_space_variable(
785    line: &str,
786    line_number: usize,
787) -> Result<Option<ModuleVariableDirective>, PtxParseError> {
788    if !line.trim_end().ends_with(';') {
789        return Ok(None);
790    }
791
792    let without_semicolon = line.trim_end().trim_end_matches(';').trim_end();
793    if without_semicolon.is_empty() {
794        return Ok(None);
795    }
796
797    let (decl_part, initializer_part) = split_initializer(without_semicolon);
798    let initializer = initializer_part.and_then(|init| {
799        let trimmed = init.trim();
800        if trimmed.is_empty() {
801            None
802        } else {
803            Some(trimmed.to_string())
804        }
805    });
806
807    let mut tokens: Vec<String> = decl_part
808        .split_whitespace()
809        .map(|tok| tok.trim().trim_matches(',').to_string())
810        .filter(|tok| !tok.is_empty())
811        .collect();
812
813    if tokens.len() < 2 {
814        return Ok(None);
815    }
816
817    let name_token = tokens.pop().unwrap();
818    if name_token.starts_with('.') {
819        return Ok(None);
820    }
821
822    let (name, array) = split_name_token(&name_token, line_number)?;
823
824    let mut visibility = None;
825    let mut linkages = Vec::new();
826    let mut address_space = None;
827    let mut mutability = None;
828    let mut ty = None;
829    let mut qualifiers: Vec<VariableQualifier> = Vec::new();
830    let mut alignment = None;
831    let mut idx = 0;
832    while idx < tokens.len() {
833        let token = tokens[idx].clone();
834        if token == ".align" {
835            if idx + 1 < tokens.len() {
836                if let Ok(value) = tokens[idx + 1].parse::<u32>() {
837                    alignment = Some(value);
838                    idx += 2;
839                    continue;
840                }
841            }
842            return Err(PtxParseError::InvalidGlobal {
843                line: line_number,
844                message: "malformed .align qualifier on global variable".into(),
845            });
846        }
847
848        if let Some(parsed) = parse_global_visibility(&token) {
849            visibility = Some(parsed);
850        } else if let Some(linkage) = parse_global_linkage(&token) {
851            if !linkages.contains(&linkage) {
852                linkages.push(linkage);
853            }
854        } else if let Some(space) = parse_global_address_space(&token) {
855            address_space = Some(space);
856        } else if token == ".volatile" {
857            return Err(PtxParseError::InvalidGlobal {
858                line: line_number,
859                message: "module-level state space declarations cannot use '.volatile'".into(),
860            });
861        } else if let Some(mutable) = parse_global_mutability(&token) {
862            mutability = Some(mutable);
863        } else if let Some(scalar) = parse_scalar_type(&token) {
864            ty = Some(scalar);
865        } else {
866            qualifiers.push(variable_qualifier_from_token(
867                &token,
868                line_number,
869                VariableQualifierContext::StateSpace,
870            )?);
871        }
872        idx += 1;
873    }
874
875    let parsed_initializer = if let Some(raw_init) = initializer {
876        Some(parse_global_initializer(&raw_init, line_number)?)
877    } else {
878        None
879    };
880
881    let data = ParsedStateVariable {
882        visibility,
883        linkages,
884        address_space,
885        mutability,
886        alignment,
887        ty,
888        qualifiers,
889        name,
890        array,
891        initializer: parsed_initializer,
892        raw: line.trim().to_string(),
893    };
894
895    let directive = match data.address_space {
896        Some(GlobalAddressSpace::Const) => {
897            ModuleVariableDirective::Const(data.into_variable(GlobalAddressSpace::Const))
898        }
899        Some(GlobalAddressSpace::Shared) => {
900            ModuleVariableDirective::Shared(data.into_variable(GlobalAddressSpace::Shared))
901        }
902        Some(GlobalAddressSpace::Global) => {
903            ModuleVariableDirective::Global(data.into_variable(GlobalAddressSpace::Global))
904        }
905        _ => return Ok(None),
906    };
907
908    Ok(Some(directive))
909}
910
911fn parse_function_header(
912    header: &str,
913    keyword: &str,
914    line_number: usize,
915) -> Result<
916    (
917        Vec<FunctionHeaderDirective>,
918        String,
919        Vec<Parameter>,
920        Option<Parameter>,
921    ),
922    PtxParseError,
923> {
924    let kind_pos = header
925        .find(keyword)
926        .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
927            line: line_number,
928            message: format!("missing {keyword} keyword"),
929        })?;
930
931    let qualifiers_part = header[..kind_pos].trim();
932    let mut directives = Vec::new();
933    parse_function_header_directives(qualifiers_part, &mut directives, line_number)?;
934
935    let mut after_kind = header[kind_pos + keyword.len()..].trim();
936    let mut return_param = None;
937    if keyword == ".func" {
938        loop {
939            let trimmed = after_kind.trim_start();
940            after_kind = trimmed;
941            if !after_kind.starts_with('(') {
942                break;
943            }
944
945            let (section, remainder) = extract_parenthesized_section(after_kind, line_number)?;
946
947            let looks_like_return_param = section
948                .split(|ch: char| ch.is_whitespace() || ch == ',')
949                .filter(|tok| !tok.is_empty())
950                .any(|tok| {
951                    matches!(
952                        tok.trim_start_matches('.').to_ascii_lowercase().as_str(),
953                        "param" | "reg"
954                    )
955                });
956
957            if looks_like_return_param && return_param.is_none() {
958                let params = parse_parameters(&section, line_number)?;
959                if params.len() > 1 {
960                    return Err(PtxParseError::InvalidFunctionHeader {
961                        line: line_number,
962                        message: "multiple return values are not supported".into(),
963                    });
964                }
965                if let Some(param) = params.into_iter().next() {
966                    return_param = Some(param);
967                    after_kind = remainder;
968                    continue;
969                }
970            }
971
972            after_kind = remainder;
973        }
974    }
975
976    let after_kind = after_kind.trim();
977    let (name, params, trailing_after_params) = if let Some(open_paren) = after_kind.find('(') {
978        let signature_part = after_kind[..open_paren].trim();
979        let name = signature_part
980            .split_whitespace()
981            .last()
982            .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
983                line: line_number,
984                message: "missing function name".into(),
985            })?
986            .to_string();
987
988        let close_paren =
989            after_kind
990                .rfind(')')
991                .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
992                    line: line_number,
993                    message: "missing ')' in function header".into(),
994                })?;
995
996        if close_paren <= open_paren {
997            return Err(PtxParseError::InvalidFunctionHeader {
998                line: line_number,
999                message: "malformed parameter list".into(),
1000            });
1001        }
1002
1003        let params_raw = &after_kind[open_paren + 1..close_paren];
1004        let params = parse_parameters(params_raw, line_number)?;
1005        let trailing_slice = after_kind[close_paren + 1..].trim();
1006        let trailing = if trailing_slice.is_empty() {
1007            None
1008        } else {
1009            Some(trailing_slice.to_string())
1010        };
1011        (name, params, trailing)
1012    } else {
1013        if keyword == ".func" {
1014            return Err(PtxParseError::InvalidFunctionHeader {
1015                line: line_number,
1016                message: ".func directive requires parameter list".into(),
1017            });
1018        }
1019        let mut parts = after_kind.split_whitespace();
1020        let name_token = parts
1021            .next()
1022            .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1023                line: line_number,
1024                message: "missing kernel name".into(),
1025            })?;
1026        let remainder: Vec<&str> = parts.collect();
1027        let trailing = if remainder.is_empty() {
1028            None
1029        } else {
1030            Some(remainder.join(" "))
1031        };
1032        (name_token.to_string(), Vec::new(), trailing)
1033    };
1034
1035    if let Some(extra_tokens) = trailing_after_params {
1036        parse_function_header_directives(&extra_tokens, &mut directives, line_number)?;
1037    }
1038
1039    Ok((directives, name, params, return_param))
1040}
1041
1042fn extract_parenthesized_section<'a>(
1043    input: &'a str,
1044    line_number: usize,
1045) -> Result<(String, &'a str), PtxParseError> {
1046    debug_assert!(input.starts_with('('));
1047
1048    let mut depth = 0i32;
1049    let mut end_idx = None;
1050    for (idx, ch) in input.char_indices() {
1051        match ch {
1052            '(' => depth += 1,
1053            ')' => {
1054                depth -= 1;
1055                if depth == 0 {
1056                    end_idx = Some(idx);
1057                    break;
1058                }
1059            }
1060            _ => {}
1061        }
1062    }
1063
1064    let end_idx = end_idx.ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1065        line: line_number,
1066        message: "unbalanced parentheses in function header".into(),
1067    })?;
1068
1069    let section = input[1..end_idx].to_string();
1070    let remainder = &input[end_idx + 1..];
1071    Ok((section, remainder))
1072}
1073
1074#[derive(Default)]
1075struct LinkageAccumulator {
1076    seen_extern: bool,
1077    seen_weak: bool,
1078}
1079
1080fn parse_function_header_directives(
1081    segment: &str,
1082    directives: &mut Vec<FunctionHeaderDirective>,
1083    line_number: usize,
1084) -> Result<(), PtxParseError> {
1085    if segment.trim().is_empty() {
1086        return Ok(());
1087    }
1088
1089    let mut tokens: VecDeque<String> = segment
1090        .split_whitespace()
1091        .map(|tok| tok.to_string())
1092        .collect();
1093    let mut linkage = LinkageAccumulator::default();
1094
1095    while let Some(token) = tokens.pop_front() {
1096        let trimmed = token.trim();
1097        if trimmed.is_empty() {
1098            continue;
1099        }
1100
1101        if !trimmed.starts_with('.') {
1102            let cleaned = strip_trailing_delimiters(trimmed);
1103            return Err(PtxParseError::InvalidFunctionHeader {
1104                line: line_number,
1105                message: format!("unexpected token '{}' in function header", cleaned),
1106            });
1107        }
1108
1109        let mut directive_token = trimmed.trim_start_matches('.').to_string();
1110        let ends_with_semicolon = directive_token.contains(';');
1111        directive_token = strip_trailing_delimiters(&directive_token);
1112        let lower = directive_token.to_ascii_lowercase();
1113
1114        match lower.as_str() {
1115            "visible" => {
1116                flush_linkage(&mut linkage, directives);
1117                directives.push(FunctionHeaderDirective::Visibility(
1118                    FunctionVisibility::Visible,
1119                ));
1120            }
1121            "hidden" => {
1122                flush_linkage(&mut linkage, directives);
1123                directives.push(FunctionHeaderDirective::Visibility(
1124                    FunctionVisibility::Hidden,
1125                ));
1126            }
1127            "extern" => {
1128                linkage.seen_extern = true;
1129            }
1130            "weak" => {
1131                linkage.seen_weak = true;
1132            }
1133            "weakextern" => {
1134                flush_linkage(&mut linkage, directives);
1135                directives.push(FunctionHeaderDirective::Linkage(
1136                    FunctionLinkage::WeakExtern,
1137                ));
1138            }
1139            "noreturn" => {
1140                flush_linkage(&mut linkage, directives);
1141                directives.push(FunctionHeaderDirective::NoReturn);
1142            }
1143            "abi_preserve" => {
1144                flush_linkage(&mut linkage, directives);
1145                let value = parse_numeric_argument(&mut tokens, line_number, ".abi_preserve")?;
1146                directives.push(FunctionHeaderDirective::AbiPreserve(value));
1147            }
1148            "abi_preserve_control" => {
1149                flush_linkage(&mut linkage, directives);
1150                let value =
1151                    parse_numeric_argument(&mut tokens, line_number, ".abi_preserve_control")?;
1152                directives.push(FunctionHeaderDirective::AbiPreserveControl(value));
1153            }
1154            "maxclusterrank" => {
1155                flush_linkage(&mut linkage, directives);
1156                let value = parse_numeric_argument(&mut tokens, line_number, ".maxclusterrank")?;
1157                directives.push(FunctionHeaderDirective::MaxClusterRank(value));
1158            }
1159            "blocksareclusters" => {
1160                flush_linkage(&mut linkage, directives);
1161                directives.push(FunctionHeaderDirective::BlocksAreClusters);
1162            }
1163            "explicitcluster" => {
1164                flush_linkage(&mut linkage, directives);
1165                let dims = parse_dim3_arguments(&mut tokens, line_number, ".explicitcluster")?;
1166                directives.push(FunctionHeaderDirective::ExplicitCluster(dims));
1167            }
1168            "reqnctapercluster" => {
1169                flush_linkage(&mut linkage, directives);
1170                let dims = parse_dim3_arguments(&mut tokens, line_number, ".reqnctapercluster")?;
1171                directives.push(FunctionHeaderDirective::ReqNctaPerCluster(dims));
1172            }
1173            "maxnreg" => {
1174                flush_linkage(&mut linkage, directives);
1175                let value = parse_numeric_argument(&mut tokens, line_number, ".maxnreg")?;
1176                directives.push(FunctionHeaderDirective::MaxNReg(value));
1177            }
1178            "maxntid" => {
1179                flush_linkage(&mut linkage, directives);
1180                let dims = parse_dim3_arguments(&mut tokens, line_number, ".maxntid")?;
1181                directives.push(FunctionHeaderDirective::MaxNTid(dims));
1182            }
1183            "minnctapersm" => {
1184                flush_linkage(&mut linkage, directives);
1185                let value = parse_numeric_argument(&mut tokens, line_number, ".minnctapersm")?;
1186                directives.push(FunctionHeaderDirective::MinNCtaPerSm(value));
1187            }
1188            "reqntid" => {
1189                flush_linkage(&mut linkage, directives);
1190                let dims = parse_dim3_arguments(&mut tokens, line_number, ".reqntid")?;
1191                directives.push(FunctionHeaderDirective::ReqNTid(dims));
1192            }
1193            "maxnctapersm" => {
1194                flush_linkage(&mut linkage, directives);
1195                let value = parse_numeric_argument(&mut tokens, line_number, ".maxnctapersm")?;
1196                directives.push(FunctionHeaderDirective::MaxNCtaPerSm(value));
1197            }
1198            "pragma" => {
1199                flush_linkage(&mut linkage, directives);
1200                let args = parse_pragma_arguments(&mut tokens, line_number)?;
1201                directives.push(FunctionHeaderDirective::Pragma(args));
1202            }
1203            _ => {
1204                let mut original = String::from(".");
1205                original.push_str(&directive_token);
1206                return Err(PtxParseError::InvalidFunctionHeader {
1207                    line: line_number,
1208                    message: format!("unrecognised function header directive '{}'", original),
1209                });
1210            }
1211        }
1212
1213        if ends_with_semicolon {
1214            flush_linkage(&mut linkage, directives);
1215        }
1216    }
1217
1218    flush_linkage(&mut linkage, directives);
1219    Ok(())
1220}
1221
1222fn flush_linkage(linkage: &mut LinkageAccumulator, directives: &mut Vec<FunctionHeaderDirective>) {
1223    if linkage.seen_extern || linkage.seen_weak {
1224        let directive = match (linkage.seen_extern, linkage.seen_weak) {
1225            (true, true) => FunctionLinkage::WeakExtern,
1226            (true, false) => FunctionLinkage::Extern,
1227            (false, true) => FunctionLinkage::Weak,
1228            (false, false) => return,
1229        };
1230        directives.push(FunctionHeaderDirective::Linkage(directive));
1231        *linkage = LinkageAccumulator::default();
1232    }
1233}
1234
1235fn strip_trailing_delimiters(value: &str) -> String {
1236    value
1237        .trim_end_matches(|ch| ch == ',' || ch == ';')
1238        .to_string()
1239}
1240
1241fn parse_numeric_argument(
1242    tokens: &mut VecDeque<String>,
1243    line_number: usize,
1244    directive: &str,
1245) -> Result<u32, PtxParseError> {
1246    let token = tokens
1247        .pop_front()
1248        .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1249            line: line_number,
1250            message: format!("missing numeric argument for {directive}"),
1251        })?;
1252    let (value, has_comma) = parse_numeric_token(&token, line_number, directive)?;
1253    if has_comma {
1254        return Err(PtxParseError::InvalidFunctionHeader {
1255            line: line_number,
1256            message: format!("unexpected ',' after argument for {directive}"),
1257        });
1258    }
1259    Ok(value)
1260}
1261
1262fn parse_dim3_arguments(
1263    tokens: &mut VecDeque<String>,
1264    line_number: usize,
1265    directive: &str,
1266) -> Result<FunctionDim3, PtxParseError> {
1267    let first = tokens
1268        .pop_front()
1269        .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1270            line: line_number,
1271            message: format!("missing arguments for {directive}"),
1272        })?;
1273    let (x, mut expect_more) = parse_numeric_token(&first, line_number, directive)?;
1274    let mut y = None;
1275    let mut z = None;
1276
1277    if expect_more {
1278        let second = tokens
1279            .pop_front()
1280            .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1281                line: line_number,
1282                message: format!("missing 'y' argument for {directive}"),
1283            })?;
1284        let (value, more) = parse_numeric_token(&second, line_number, directive)?;
1285        y = Some(value);
1286        expect_more = more;
1287    } else {
1288        expect_more = false;
1289    }
1290
1291    if expect_more {
1292        let third = tokens
1293            .pop_front()
1294            .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1295                line: line_number,
1296                message: format!("missing 'z' argument for {directive}"),
1297            })?;
1298        let (value, more) = parse_numeric_token(&third, line_number, directive)?;
1299        if more {
1300            return Err(PtxParseError::InvalidFunctionHeader {
1301                line: line_number,
1302                message: format!("unexpected extra arguments for {directive}"),
1303            });
1304        }
1305        z = Some(value);
1306    }
1307
1308    Ok(FunctionDim3 { x, y, z })
1309}
1310
1311fn parse_numeric_token(
1312    token: &str,
1313    line_number: usize,
1314    directive: &str,
1315) -> Result<(u32, bool), PtxParseError> {
1316    let mut trimmed = token.trim();
1317    let mut has_semicolon = false;
1318    if trimmed.ends_with(';') {
1319        has_semicolon = true;
1320        trimmed = trimmed.trim_end_matches(';').trim_end();
1321    }
1322
1323    let mut has_comma = false;
1324    if trimmed.ends_with(',') {
1325        has_comma = true;
1326        trimmed = trimmed.trim_end_matches(',').trim_end();
1327    }
1328
1329    if trimmed.is_empty() {
1330        return Err(PtxParseError::InvalidFunctionHeader {
1331            line: line_number,
1332            message: format!("expected numeric value for {directive}"),
1333        });
1334    }
1335
1336    let value = trimmed
1337        .parse::<u32>()
1338        .map_err(|_| PtxParseError::InvalidFunctionHeader {
1339            line: line_number,
1340            message: format!("invalid numeric value '{}' in {}", trimmed, directive),
1341        })?;
1342
1343    if has_semicolon && has_comma {
1344        return Err(PtxParseError::InvalidFunctionHeader {
1345            line: line_number,
1346            message: format!(
1347                "unexpected ';' after comma-separated value in {}",
1348                directive
1349            ),
1350        });
1351    }
1352
1353    Ok((value, has_comma))
1354}
1355
1356fn parse_pragma_arguments(
1357    tokens: &mut VecDeque<String>,
1358    line_number: usize,
1359) -> Result<Vec<String>, PtxParseError> {
1360    let mut args = Vec::new();
1361    let mut found_semicolon = false;
1362
1363    while let Some(token) = tokens.pop_front() {
1364        let trimmed = token.trim();
1365        if trimmed.is_empty() {
1366            continue;
1367        }
1368        let has_semicolon = trimmed.contains(';');
1369        let cleaned = trimmed.trim_end_matches(';').to_string();
1370        if !cleaned.is_empty() {
1371            args.push(cleaned);
1372        }
1373        if has_semicolon {
1374            found_semicolon = true;
1375            break;
1376        }
1377    }
1378
1379    if !found_semicolon {
1380        return Err(PtxParseError::InvalidFunctionHeader {
1381            line: line_number,
1382            message: ".pragma directive missing terminating ';'".into(),
1383        });
1384    }
1385
1386    if args.is_empty() {
1387        return Err(PtxParseError::InvalidFunctionHeader {
1388            line: line_number,
1389            message: "expected arguments after .pragma".into(),
1390        });
1391    }
1392
1393    Ok(args)
1394}
1395
1396fn parse_parameters(raw: &str, line_number: usize) -> Result<Vec<Parameter>, PtxParseError> {
1397    let mut params = Vec::new();
1398    let mut current = String::new();
1399    let mut depth = 0i32;
1400
1401    for ch in raw.chars() {
1402        match ch {
1403            '(' => {
1404                depth += 1;
1405                current.push(ch);
1406            }
1407            ')' => {
1408                depth -= 1;
1409                current.push(ch);
1410            }
1411            ',' if depth == 0 => {
1412                if let Some(param) = finalize_param(&current, line_number)? {
1413                    params.push(param);
1414                }
1415                current.clear();
1416            }
1417            _ => current.push(ch),
1418        }
1419    }
1420
1421    if !current.trim().is_empty() {
1422        if let Some(param) = finalize_param(&current, line_number)? {
1423            params.push(param);
1424        }
1425    }
1426
1427    if depth != 0 {
1428        return Err(PtxParseError::InvalidFunctionHeader {
1429            line: line_number,
1430            message: "unbalanced parentheses in parameter list".into(),
1431        });
1432    }
1433
1434    Ok(params)
1435}
1436
1437fn finalize_param(raw: &str, line_number: usize) -> Result<Option<Parameter>, PtxParseError> {
1438    let trimmed = raw.trim();
1439    if trimmed.is_empty() {
1440        return Ok(None);
1441    }
1442
1443    let mut tokens: Vec<String> = trimmed
1444        .split_whitespace()
1445        .map(|tok| tok.trim_matches(',').trim_end_matches(';').to_string())
1446        .filter(|tok| !tok.is_empty())
1447        .collect();
1448
1449    if tokens.is_empty() {
1450        return Err(PtxParseError::InvalidFunctionHeader {
1451            line: line_number,
1452            message: format!("unable to parse parameter '{}': missing name", raw.trim()),
1453        });
1454    }
1455
1456    let name_token = tokens.pop().unwrap();
1457    let specifiers_list = tokens.clone();
1458    let (base_name, array_from_name) = split_name_token(&name_token, line_number)?;
1459
1460    let mut storage = None;
1461    let mut alignment = None;
1462    let mut ty = None;
1463    let mut qualifiers = ParameterQualifiers::default();
1464    let mut pointer: Option<PointerQualifier> = None;
1465    let mut idx = 0;
1466
1467    while idx < tokens.len() {
1468        let token = tokens[idx].clone();
1469        let trimmed = token.trim();
1470        let lower = trimmed.trim_start_matches('.').to_ascii_lowercase();
1471
1472        match lower.as_str() {
1473            "param" => {
1474                storage = Some(ParameterStorage::Param);
1475                idx += 1;
1476                continue;
1477            }
1478            "align" => {
1479                let next =
1480                    tokens
1481                        .get(idx + 1)
1482                        .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1483                            line: line_number,
1484                            message: "expected alignment value after .align".into(),
1485                        })?;
1486                let value = next.trim_matches(',').parse::<u32>().map_err(|_| {
1487                    PtxParseError::InvalidFunctionHeader {
1488                        line: line_number,
1489                        message: format!("invalid alignment value '{}'", next),
1490                    }
1491                })?;
1492                alignment = Some(value);
1493                idx += 2;
1494                continue;
1495            }
1496            "const" => {
1497                if let Some(ptr) = pointer.as_mut() {
1498                    if ptr.address_space.is_none() {
1499                        ptr.address_space = Some(PointerAddressSpace::Const);
1500                        idx += 1;
1501                        continue;
1502                    }
1503                }
1504                qualifiers.is_const = true;
1505                idx += 1;
1506                continue;
1507            }
1508            "volatile" => {
1509                qualifiers.is_volatile = true;
1510                idx += 1;
1511                continue;
1512            }
1513            "restrict" => {
1514                qualifiers.is_restrict = true;
1515                idx += 1;
1516                continue;
1517            }
1518            "noalias" => {
1519                qualifiers.is_noalias = true;
1520                idx += 1;
1521                continue;
1522            }
1523            "ptr" => {
1524                pointer.get_or_insert_with(PointerQualifier::default);
1525                idx += 1;
1526                continue;
1527            }
1528            "global" => {
1529                if let Some(ptr) = pointer.as_mut() {
1530                    ptr.address_space = Some(PointerAddressSpace::Global);
1531                    idx += 1;
1532                    continue;
1533                }
1534            }
1535            "shared" => {
1536                if let Some(ptr) = pointer.as_mut() {
1537                    ptr.address_space = Some(PointerAddressSpace::Shared);
1538                    idx += 1;
1539                    continue;
1540                }
1541            }
1542            "local" => {
1543                if let Some(ptr) = pointer.as_mut() {
1544                    ptr.address_space = Some(PointerAddressSpace::Local);
1545                    idx += 1;
1546                    continue;
1547                }
1548            }
1549            "reg" => {
1550                idx += 1;
1551                continue;
1552            }
1553            _ => {}
1554        }
1555
1556        if let Some(scalar) = parse_scalar_type(&token) {
1557            ty = Some(scalar);
1558        } else {
1559            return Err(PtxParseError::InvalidFunctionHeader {
1560                line: line_number,
1561                message: format!("unrecognised parameter qualifier '{}'", token),
1562            });
1563        }
1564        idx += 1;
1565    }
1566
1567    if let Some(ptr) = pointer {
1568        qualifiers.pointer = Some(ptr);
1569    }
1570
1571    Ok(Some(Parameter {
1572        name: base_name,
1573        storage,
1574        alignment,
1575        ty,
1576        qualifiers,
1577        array: array_from_name,
1578        specifiers: specifiers_list
1579            .into_iter()
1580            .map(ParameterSpecifier::new)
1581            .collect(),
1582        raw: trimmed.to_string(),
1583    }))
1584}
1585
1586fn parse_function_stmt(
1587    content: &str,
1588    comment: Option<String>,
1589    line_number: usize,
1590    in_declaration: &mut bool,
1591) -> Result<FunctionBodyItem, PtxParseError> {
1592    let trimmed = content.trim();
1593
1594    if trimmed.ends_with(':') {
1595        *in_declaration = false;
1596        return Ok(FunctionBodyItem::Statement(FunctionStatement::Label(
1597            trimmed.trim_end_matches(':').to_string(),
1598        )));
1599    }
1600
1601    if trimmed.starts_with('.') || trimmed.starts_with("@@") {
1602        return parse_function_directive_stmt(trimmed, comment, line_number, in_declaration);
1603    }
1604
1605    let instruction = parse_instruction(trimmed, comment, line_number)?;
1606    *in_declaration = false;
1607    Ok(FunctionBodyItem::Statement(FunctionStatement::Instruction(
1608        instruction,
1609    )))
1610}
1611
1612fn parse_function_directive_stmt(
1613    line: &str,
1614    comment: Option<String>,
1615    line_number: usize,
1616    in_declaration: &mut bool,
1617) -> Result<FunctionBodyItem, PtxParseError> {
1618    let trimmed = line.trim();
1619    let without_semicolon = trimmed
1620        .strip_suffix(';')
1621        .map(|s| s.trim_end())
1622        .unwrap_or(trimmed);
1623
1624    if without_semicolon.is_empty() {
1625        return Err(PtxParseError::InvalidDirective {
1626            line: line_number,
1627            message: "empty directive".into(),
1628        });
1629    }
1630
1631    let mut parts = without_semicolon.split_whitespace();
1632    let keyword = parts.next().unwrap();
1633    let normalized = keyword.trim_start_matches('.').to_ascii_lowercase();
1634    let arguments: Vec<String> = parts.map(|tok| tok.to_string()).collect();
1635
1636    if keyword.starts_with("@@") {
1637        let directive = DwarfDirective {
1638            keyword: keyword.to_string(),
1639            arguments,
1640            comment,
1641            raw: trimmed.to_string(),
1642        };
1643        if *in_declaration {
1644            return Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Dwarf(
1645                directive,
1646            )));
1647        }
1648        *in_declaration = false;
1649        return Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1650            StatementDirective::Dwarf(directive),
1651        )));
1652    }
1653
1654    match normalized.as_str() {
1655        "reg" => {
1656            let directive = parse_register_declaration(
1657                keyword,
1658                without_semicolon,
1659                comment,
1660                trimmed.to_string(),
1661                line_number,
1662            )?;
1663            Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Reg(
1664                directive,
1665            )))
1666        }
1667        "local" | "param" | "shared" => {
1668            let kind = match normalized.as_str() {
1669                "local" => FunctionDeclarationKind::Local,
1670                "param" => FunctionDeclarationKind::Param,
1671                "shared" => FunctionDeclarationKind::Shared,
1672                _ => unreachable!(),
1673            };
1674
1675            let directive = GenericFunctionDeclaration {
1676                kind,
1677                keyword: keyword.to_string(),
1678                arguments,
1679                comment,
1680                raw: trimmed.to_string(),
1681            };
1682
1683            let entry = match kind {
1684                FunctionDeclarationKind::Local => FunctionEntryDirective::Local(directive),
1685                FunctionDeclarationKind::Param => FunctionEntryDirective::Param(directive),
1686                FunctionDeclarationKind::Shared => FunctionEntryDirective::Shared(directive),
1687                _ => unreachable!(),
1688            };
1689
1690            Ok(FunctionBodyItem::Entry(entry))
1691        }
1692        "pragma" => {
1693            let mut pragma_arguments = Vec::new();
1694            for token in &arguments {
1695                pragma_arguments.push(unquote_string(token, line_number)?);
1696            }
1697
1698            let directive = PragmaDirective {
1699                arguments: pragma_arguments,
1700                comment,
1701                raw: trimmed.to_string(),
1702            };
1703
1704            if *in_declaration {
1705                Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Pragma(
1706                    directive,
1707                )))
1708            } else {
1709                *in_declaration = false;
1710                Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1711                    StatementDirective::Pragma(directive),
1712                )))
1713            }
1714        }
1715        "loc" => {
1716            if arguments.len() < 3 {
1717                return Err(PtxParseError::InvalidDirective {
1718                    line: line_number,
1719                    message: ".loc directive expects file, line, and column arguments".into(),
1720                });
1721            }
1722
1723            let file_index =
1724                arguments[0]
1725                    .parse::<u32>()
1726                    .map_err(|_| PtxParseError::InvalidDirective {
1727                        line: line_number,
1728                        message: format!("invalid .loc file index '{}'", arguments[0]),
1729                    })?;
1730            let line_value =
1731                arguments[1]
1732                    .parse::<u32>()
1733                    .map_err(|_| PtxParseError::InvalidDirective {
1734                        line: line_number,
1735                        message: format!("invalid .loc line '{}'", arguments[1]),
1736                    })?;
1737            let column_value =
1738                arguments[2]
1739                    .parse::<u32>()
1740                    .map_err(|_| PtxParseError::InvalidDirective {
1741                        line: line_number,
1742                        message: format!("invalid .loc column '{}'", arguments[2]),
1743                    })?;
1744
1745            let options = if arguments.len() > 3 {
1746                arguments[3..].to_vec()
1747            } else {
1748                Vec::new()
1749            };
1750
1751            let directive = LocationDirective {
1752                file_index,
1753                line: line_value,
1754                column: column_value,
1755                options,
1756                comment,
1757                raw: trimmed.to_string(),
1758            };
1759
1760            if *in_declaration {
1761                Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Loc(
1762                    directive,
1763                )))
1764            } else {
1765                *in_declaration = false;
1766                Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1767                    StatementDirective::Loc(directive),
1768                )))
1769            }
1770        }
1771        "section" => {
1772            if arguments.is_empty() {
1773                return Err(PtxParseError::InvalidDirective {
1774                    line: line_number,
1775                    message: ".section directive requires a name".into(),
1776                });
1777            }
1778            let name = arguments[0].clone();
1779            let rest = if arguments.len() > 1 {
1780                arguments[1..].to_vec()
1781            } else {
1782                Vec::new()
1783            };
1784            let directive = StatementSectionDirective {
1785                name,
1786                arguments: rest,
1787                comment,
1788                raw: trimmed.to_string(),
1789            };
1790            *in_declaration = false;
1791            Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1792                StatementDirective::Section(directive),
1793            )))
1794        }
1795        _ => Err(PtxParseError::InvalidDirective {
1796            line: line_number,
1797            message: format!("unsupported directive '{keyword}'"),
1798        }),
1799    }
1800}
1801
1802fn parse_instruction(
1803    line: &str,
1804    comment: Option<String>,
1805    line_number: usize,
1806) -> Result<Instruction, PtxParseError> {
1807    let raw_line = line.trim().to_string();
1808    let mut stmt = line.trim_end_matches(';').trim();
1809    if stmt.is_empty() {
1810        return Err(PtxParseError::InvalidInstruction {
1811            line: line_number,
1812            message: "empty instruction".into(),
1813        });
1814    }
1815
1816    let mut predicate = None;
1817    if stmt.starts_with('@') {
1818        if let Some((pred, rest)) = stmt.split_once(' ') {
1819            predicate = Some(pred.trim_start_matches('@').to_string());
1820            stmt = rest.trim();
1821        } else {
1822            return Err(PtxParseError::InvalidInstruction {
1823                line: line_number,
1824                message: "predicate missing instruction".into(),
1825            });
1826        }
1827    }
1828
1829    let mut parts = stmt.split_whitespace();
1830    let opcode_raw = parts
1831        .next()
1832        .ok_or_else(|| PtxParseError::InvalidInstruction {
1833            line: line_number,
1834            message: "missing opcode".into(),
1835        })?;
1836
1837    let opcode_text = opcode_raw
1838        .split('.')
1839        .next()
1840        .unwrap_or(opcode_raw)
1841        .to_string();
1842
1843    let modifier_texts: Vec<String> = opcode_raw
1844        .split('.')
1845        .skip(1)
1846        .filter(|s| !s.is_empty())
1847        .map(|s| s.to_string())
1848        .collect();
1849
1850    let opcode_kind =
1851        classify_opcode(&opcode_text).ok_or_else(|| PtxParseError::InvalidInstruction {
1852            line: line_number,
1853            message: format!("unknown opcode '{opcode_text}'"),
1854        })?;
1855    let modifiers = parse_instruction_modifiers(&modifier_texts, line_number)?;
1856
1857    let operands_raw = parts.collect::<Vec<_>>().join(" ");
1858    let operands = parse_operands(&operands_raw, line_number)?;
1859
1860    Ok(Instruction {
1861        predicate,
1862        opcode: InstructionOpcode {
1863            kind: opcode_kind,
1864            modifiers,
1865        },
1866        operands,
1867        comment,
1868        raw: raw_line,
1869    })
1870}
1871
1872fn parse_instruction_modifiers(
1873    modifier_texts: &[String],
1874    line_number: usize,
1875) -> Result<Vec<ModifierKind>, PtxParseError> {
1876    let mut modifiers = Vec::new();
1877    let mut index = 0;
1878
1879    while index < modifier_texts.len() {
1880        let current = &modifier_texts[index];
1881        let trimmed = current.trim_start_matches('.');
1882        let lower = trimmed.to_ascii_lowercase();
1883        let primary = match lower.split("::").next() {
1884            Some(token) if !token.is_empty() => token,
1885            _ => lower.as_str(),
1886        };
1887
1888        if trimmed.eq_ignore_ascii_case("to") {
1889            if let Some(next) = modifier_texts.get(index + 1) {
1890                let next_trimmed = next.trim_start_matches('.');
1891                if let Some(space) =
1892                    parse_address_space_modifier(&next_trimmed.to_ascii_lowercase())
1893                {
1894                    modifiers.push(ModifierKind::Conversion(space));
1895                    index += 2;
1896                    continue;
1897                }
1898            }
1899
1900            return Err(PtxParseError::InvalidInstruction {
1901                line: line_number,
1902                message: "conversion modifier missing address space".into(),
1903            });
1904        }
1905
1906        if let Some(modifier) = classify_modifier(primary) {
1907            modifiers.push(modifier);
1908            index += 1;
1909            continue;
1910        }
1911
1912        if let Some(width) = parse_vector_width_modifier(primary) {
1913            modifiers.push(ModifierKind::VectorWidth(width));
1914            index += 1;
1915            continue;
1916        }
1917
1918        if primary == "wide" {
1919            modifiers.push(ModifierKind::Wide);
1920            index += 1;
1921            continue;
1922        }
1923
1924        return Err(PtxParseError::InvalidInstruction {
1925            line: line_number,
1926            message: format!("unrecognised modifier '{}'", current),
1927        });
1928    }
1929
1930    Ok(modifiers)
1931}
1932
1933fn parse_operands(raw: &str, line_number: usize) -> Result<Vec<Operand>, PtxParseError> {
1934    if raw.trim().is_empty() {
1935        return Ok(Vec::new());
1936    }
1937
1938    let mut operands = Vec::new();
1939    let mut current = String::new();
1940    let mut paren_depth = 0usize;
1941    let mut bracket_depth = 0usize;
1942
1943    let push_current =
1944        |store: &mut Vec<Operand>, token: &mut String| -> Result<(), PtxParseError> {
1945            let trimmed = token.trim().trim_end_matches(';').trim();
1946            if !trimmed.is_empty() {
1947                store.push(parse_operand(trimmed, line_number)?);
1948            }
1949            token.clear();
1950            Ok(())
1951        };
1952
1953    for ch in raw.chars() {
1954        match ch {
1955            ',' if paren_depth == 0 && bracket_depth == 0 => {
1956                push_current(&mut operands, &mut current)?;
1957            }
1958            '(' => {
1959                paren_depth += 1;
1960                current.push(ch);
1961            }
1962            ')' => {
1963                if paren_depth == 0 {
1964                    return Err(PtxParseError::InvalidInstruction {
1965                        line: line_number,
1966                        message: "unmatched ')' in operand list".into(),
1967                    });
1968                }
1969                paren_depth -= 1;
1970                current.push(ch);
1971            }
1972            '[' => {
1973                bracket_depth += 1;
1974                current.push(ch);
1975            }
1976            ']' => {
1977                if bracket_depth == 0 {
1978                    return Err(PtxParseError::InvalidInstruction {
1979                        line: line_number,
1980                        message: "unmatched ']' in operand list".into(),
1981                    });
1982                }
1983                bracket_depth -= 1;
1984                current.push(ch);
1985            }
1986            _ => current.push(ch),
1987        }
1988    }
1989
1990    if paren_depth != 0 {
1991        return Err(PtxParseError::InvalidInstruction {
1992            line: line_number,
1993            message: "unterminated '(' in operand list".into(),
1994        });
1995    }
1996
1997    if bracket_depth != 0 {
1998        return Err(PtxParseError::InvalidInstruction {
1999            line: line_number,
2000            message: "unterminated '[' in operand list".into(),
2001        });
2002    }
2003
2004    push_current(&mut operands, &mut current)?;
2005
2006    Ok(operands)
2007}
2008
2009fn parse_operand(token: &str, line_number: usize) -> Result<Operand, PtxParseError> {
2010    if token.is_empty() {
2011        return Err(PtxParseError::InvalidInstruction {
2012            line: line_number,
2013            message: "empty operand".into(),
2014        });
2015    }
2016
2017    if token.starts_with('[') && token.ends_with(']') {
2018        return parse_memory_operand(token, line_number);
2019    }
2020
2021    if token.starts_with('(') && token.ends_with(')') {
2022        let inner = token[1..token.len() - 1].trim();
2023        let items = if inner.is_empty() {
2024            Vec::new()
2025        } else {
2026            inner
2027                .split(',')
2028                .map(|part| part.trim())
2029                .filter(|part| !part.is_empty())
2030                .map(|part| part.to_string())
2031                .collect()
2032        };
2033        return Ok(Operand::Parenthesized(items));
2034    }
2035
2036    if let Some(paren_index) = token.find('(') {
2037        if paren_index > 0 && token.ends_with(')') {
2038            let name_part = token[..paren_index].trim();
2039            if is_symbol_token(name_part) {
2040                let args_inner = token[paren_index + 1..token.len() - 1].trim();
2041                let arguments = if args_inner.is_empty() {
2042                    Vec::new()
2043                } else {
2044                    args_inner
2045                        .split(',')
2046                        .map(|arg| arg.trim())
2047                        .filter(|arg| !arg.is_empty())
2048                        .map(|arg| arg.to_string())
2049                        .collect()
2050                };
2051                return Ok(Operand::CallTarget {
2052                    name: name_part.to_string(),
2053                    arguments,
2054                });
2055            }
2056        }
2057    }
2058
2059    if token.starts_with('%') {
2060        return Ok(Operand::Register(token.to_string()));
2061    }
2062
2063    if is_numeric_literal(token) {
2064        return Ok(Operand::Immediate(token.to_string()));
2065    }
2066
2067    if is_symbol_token(token) {
2068        return Ok(Operand::Symbol(token.to_string()));
2069    }
2070
2071    Err(PtxParseError::InvalidInstruction {
2072        line: line_number,
2073        message: format!("unknown operand '{token}'"),
2074    })
2075}
2076
2077fn parse_memory_operand(token: &str, line_number: usize) -> Result<Operand, PtxParseError> {
2078    if token.len() < 2 {
2079        return Err(PtxParseError::InvalidInstruction {
2080            line: line_number,
2081            message: format!("invalid memory operand: {token}"),
2082        });
2083    }
2084
2085    let inner = token[1..token.len() - 1].trim();
2086    if inner.is_empty() {
2087        return Err(PtxParseError::InvalidInstruction {
2088            line: line_number,
2089            message: "memory operand missing address expression".into(),
2090        });
2091    }
2092
2093    let tokens = tokenize_address(inner, line_number)?;
2094    let memory = build_memory_operand(&tokens, line_number)?;
2095
2096    Ok(Operand::Memory(memory))
2097}
2098
2099#[derive(Debug, Clone)]
2100enum AddressToken {
2101    Register(String),
2102    Symbol(String),
2103    Immediate(String),
2104    Plus,
2105    Minus,
2106    Star,
2107}
2108
2109fn tokenize_address(expr: &str, line_number: usize) -> Result<Vec<AddressToken>, PtxParseError> {
2110    let mut tokens = Vec::new();
2111    let mut current = String::new();
2112
2113    let flush_current =
2114        |current: &mut String, tokens: &mut Vec<AddressToken>| -> Result<(), PtxParseError> {
2115            if current.is_empty() {
2116                return Ok(());
2117            }
2118            let token = classify_address_token(current.as_str(), line_number)?;
2119            tokens.push(token);
2120            current.clear();
2121            Ok(())
2122        };
2123
2124    for ch in expr.chars() {
2125        match ch {
2126            '+' => {
2127                flush_current(&mut current, &mut tokens)?;
2128                tokens.push(AddressToken::Plus);
2129            }
2130            '-' => {
2131                flush_current(&mut current, &mut tokens)?;
2132                tokens.push(AddressToken::Minus);
2133            }
2134            '*' => {
2135                flush_current(&mut current, &mut tokens)?;
2136                tokens.push(AddressToken::Star);
2137            }
2138            ch if ch.is_whitespace() => {
2139                flush_current(&mut current, &mut tokens)?;
2140            }
2141            _ => current.push(ch),
2142        }
2143    }
2144
2145    flush_current(&mut current, &mut tokens)?;
2146
2147    if tokens.is_empty() {
2148        return Err(PtxParseError::InvalidInstruction {
2149            line: line_number,
2150            message: "empty memory address expression".into(),
2151        });
2152    }
2153
2154    Ok(tokens)
2155}
2156
2157fn classify_address_token(token: &str, line_number: usize) -> Result<AddressToken, PtxParseError> {
2158    if token.starts_with('%') && token.len() > 1 {
2159        return Ok(AddressToken::Register(token.to_string()));
2160    }
2161    if is_numeric_literal(token) {
2162        return Ok(AddressToken::Immediate(token.to_string()));
2163    }
2164    if is_symbol_token(token) {
2165        return Ok(AddressToken::Symbol(token.to_string()));
2166    }
2167
2168    Err(PtxParseError::InvalidInstruction {
2169        line: line_number,
2170        message: format!("invalid memory address token '{token}'"),
2171    })
2172}
2173
2174fn build_memory_operand(
2175    tokens: &[AddressToken],
2176    line_number: usize,
2177) -> Result<MemoryOperand, PtxParseError> {
2178    let mut base: Option<AddressBase> = None;
2179    let mut displacements = Vec::new();
2180
2181    let mut pending_sign = AddressSign::Positive;
2182    let mut sign_set = false;
2183
2184    let mut index = 0;
2185    while index < tokens.len() {
2186        match &tokens[index] {
2187            AddressToken::Plus => {
2188                if sign_set {
2189                    // multiple plus operators keep the current sign
2190                } else {
2191                    pending_sign = AddressSign::Positive;
2192                    sign_set = true;
2193                }
2194                index += 1;
2195            }
2196            AddressToken::Minus => {
2197                if sign_set {
2198                    pending_sign = pending_sign.negate();
2199                } else {
2200                    pending_sign = AddressSign::Negative;
2201                    sign_set = true;
2202                }
2203                index += 1;
2204            }
2205            AddressToken::Star => {
2206                return Err(PtxParseError::InvalidInstruction {
2207                    line: line_number,
2208                    message: "unexpected '*' in memory address expression".into(),
2209                });
2210            }
2211            AddressToken::Register(name) => {
2212                let sign = if sign_set {
2213                    pending_sign
2214                } else {
2215                    AddressSign::Positive
2216                };
2217                pending_sign = AddressSign::Positive;
2218                sign_set = false;
2219
2220                let mut scale = None;
2221                if index + 1 < tokens.len() {
2222                    if matches!(tokens[index + 1], AddressToken::Star) {
2223                        if index + 2 >= tokens.len() {
2224                            return Err(PtxParseError::InvalidInstruction {
2225                                line: line_number,
2226                                message: "missing scale after '*' in memory address".into(),
2227                            });
2228                        }
2229                        match &tokens[index + 2] {
2230                            AddressToken::Immediate(value) => {
2231                                scale = Some(value.clone());
2232                                index += 2;
2233                            }
2234                            _ => {
2235                                return Err(PtxParseError::InvalidInstruction {
2236                                    line: line_number,
2237                                    message: "expected immediate scale after '*' in memory address"
2238                                        .into(),
2239                                });
2240                            }
2241                        }
2242                    }
2243                }
2244
2245                let displacement = AddressDisplacement {
2246                    sign,
2247                    kind: AddressDisplacementKind::Register {
2248                        register: name.clone(),
2249                        scale: scale.clone(),
2250                    },
2251                };
2252
2253                let can_be_base =
2254                    base.is_none() && matches!(sign, AddressSign::Positive) && scale.is_none();
2255                if can_be_base {
2256                    base = Some(AddressBase::Register(name.clone()));
2257                } else {
2258                    displacements.push(displacement);
2259                }
2260
2261                index += 1;
2262            }
2263            AddressToken::Symbol(name) => {
2264                let sign = if sign_set {
2265                    pending_sign
2266                } else {
2267                    AddressSign::Positive
2268                };
2269                pending_sign = AddressSign::Positive;
2270                sign_set = false;
2271
2272                let displacement = AddressDisplacement {
2273                    sign,
2274                    kind: AddressDisplacementKind::Symbol(name.clone()),
2275                };
2276
2277                if base.is_none() && matches!(sign, AddressSign::Positive) {
2278                    base = Some(AddressBase::Symbol(name.clone()));
2279                } else {
2280                    displacements.push(displacement);
2281                }
2282
2283                index += 1;
2284            }
2285            AddressToken::Immediate(value) => {
2286                let sign = if sign_set {
2287                    pending_sign
2288                } else {
2289                    AddressSign::Positive
2290                };
2291                pending_sign = AddressSign::Positive;
2292                sign_set = false;
2293
2294                displacements.push(AddressDisplacement {
2295                    sign,
2296                    kind: AddressDisplacementKind::Immediate(value.clone()),
2297                });
2298
2299                index += 1;
2300            }
2301        }
2302    }
2303
2304    if sign_set {
2305        return Err(PtxParseError::InvalidInstruction {
2306            line: line_number,
2307            message: "dangling sign in memory address expression".into(),
2308        });
2309    }
2310
2311    Ok(MemoryOperand {
2312        base,
2313        displacements,
2314    })
2315}
2316
2317fn classify_opcode(opcode: &str) -> Option<OpcodeKind> {
2318    match opcode.to_ascii_lowercase().as_str() {
2319        "abs" => Some(OpcodeKind::Abs),
2320        "activemask" => Some(OpcodeKind::Activemask),
2321        "add" => Some(OpcodeKind::Add),
2322        "addc" => Some(OpcodeKind::Addc),
2323        "alloca" => Some(OpcodeKind::Alloca),
2324        "and" => Some(OpcodeKind::And),
2325        "applypriority" => Some(OpcodeKind::Applypriority),
2326        "atom" => Some(OpcodeKind::Atom),
2327        "bar" => Some(OpcodeKind::Bar),
2328        "barrier" => Some(OpcodeKind::Barrier),
2329        "bfe" => Some(OpcodeKind::Bfe),
2330        "bfi" => Some(OpcodeKind::Bfi),
2331        "bfind" => Some(OpcodeKind::Bfind),
2332        "bmsk" => Some(OpcodeKind::Bmsk),
2333        "brev" => Some(OpcodeKind::Brev),
2334        "bra" => Some(OpcodeKind::Bra),
2335        "brkpt" => Some(OpcodeKind::Brkpt),
2336        "brx" => Some(OpcodeKind::Brx),
2337        "call" => Some(OpcodeKind::Call),
2338        "clz" => Some(OpcodeKind::Clz),
2339        "clusterlaunchcontrol" => Some(OpcodeKind::Clusterlaunchcontrol),
2340        "cnot" => Some(OpcodeKind::Cnot),
2341        "copysign" => Some(OpcodeKind::Copysign),
2342        "cos" => Some(OpcodeKind::Cos),
2343        "cp" => Some(OpcodeKind::Cp),
2344        "createpolicy" => Some(OpcodeKind::Createpolicy),
2345        "cvt" => Some(OpcodeKind::Cvt),
2346        "cvta" => Some(OpcodeKind::Cvta),
2347        "div" => Some(OpcodeKind::Div),
2348        "discard" => Some(OpcodeKind::Discard),
2349        "dp2a" => Some(OpcodeKind::Dp2a),
2350        "dp4a" => Some(OpcodeKind::Dp4a),
2351        "elect" => Some(OpcodeKind::Elect),
2352        "ex2" => Some(OpcodeKind::Ex2),
2353        "exit" => Some(OpcodeKind::Exit),
2354        "fence" => Some(OpcodeKind::Fence),
2355        "fma" => Some(OpcodeKind::Fma),
2356        "fns" => Some(OpcodeKind::Fns),
2357        "getctarank" => Some(OpcodeKind::Getctarank),
2358        "griddepcontrol" => Some(OpcodeKind::Griddepcontrol),
2359        "isspacep" => Some(OpcodeKind::Isspacep),
2360        "istypep" => Some(OpcodeKind::Istypep),
2361        "ld" => Some(OpcodeKind::Ld),
2362        "ldmatrix" => Some(OpcodeKind::Ldmatrix),
2363        "ldu" => Some(OpcodeKind::Ldu),
2364        "lg2" => Some(OpcodeKind::Lg2),
2365        "lop3" => Some(OpcodeKind::Lop3),
2366        "mad" => Some(OpcodeKind::Mad),
2367        "mad24" => Some(OpcodeKind::Mad24),
2368        "madc" => Some(OpcodeKind::Madc),
2369        "mapa" => Some(OpcodeKind::Mapa),
2370        "match" => Some(OpcodeKind::Match),
2371        "max" => Some(OpcodeKind::Max),
2372        "mbarrier" => Some(OpcodeKind::Mbarrier),
2373        "membar" => Some(OpcodeKind::Membar),
2374        "min" => Some(OpcodeKind::Min),
2375        "mov" => Some(OpcodeKind::Mov),
2376        "movmatrix" => Some(OpcodeKind::Movmatrix),
2377        "mma" => Some(OpcodeKind::Mma),
2378        "mul" => Some(OpcodeKind::Mul),
2379        "mul24" => Some(OpcodeKind::Mul24),
2380        "multimem" => Some(OpcodeKind::Multimem),
2381        "nanosleep" => Some(OpcodeKind::Nanosleep),
2382        "neg" => Some(OpcodeKind::Neg),
2383        "not" => Some(OpcodeKind::Not),
2384        "or" => Some(OpcodeKind::Or),
2385        "pmevent" => Some(OpcodeKind::Pmevent),
2386        "popc" => Some(OpcodeKind::Popc),
2387        "prefetch" => Some(OpcodeKind::Prefetch),
2388        "prefetchu" => Some(OpcodeKind::Prefetchu),
2389        "prmt" => Some(OpcodeKind::Prmt),
2390        "rcp" => Some(OpcodeKind::Rcp),
2391        "red" => Some(OpcodeKind::Red),
2392        "redux" => Some(OpcodeKind::Redux),
2393        "rem" => Some(OpcodeKind::Rem),
2394        "rsqrt" => Some(OpcodeKind::Rsqrt),
2395        "sad" => Some(OpcodeKind::Sad),
2396        "selp" => Some(OpcodeKind::Selp),
2397        "set" => Some(OpcodeKind::Set),
2398        "setmaxnreg" => Some(OpcodeKind::Setmaxnreg),
2399        "setp" => Some(OpcodeKind::Setp),
2400        "shf" => Some(OpcodeKind::Shf),
2401        "shfl" => Some(OpcodeKind::Shfl),
2402        "shl" => Some(OpcodeKind::Shl),
2403        "shr" => Some(OpcodeKind::Shr),
2404        "sin" => Some(OpcodeKind::Sin),
2405        "slct" => Some(OpcodeKind::Slct),
2406        "sqrt" => Some(OpcodeKind::Sqrt),
2407        "stackrestore" => Some(OpcodeKind::Stackrestore),
2408        "stacksave" => Some(OpcodeKind::Stacksave),
2409        "st" => Some(OpcodeKind::St),
2410        "stmatrix" => Some(OpcodeKind::Stmatrix),
2411        "sub" => Some(OpcodeKind::Sub),
2412        "subc" => Some(OpcodeKind::Subc),
2413        "suq" => Some(OpcodeKind::Suq),
2414        "suld" => Some(OpcodeKind::Suld),
2415        "sured" => Some(OpcodeKind::Sured),
2416        "sust" => Some(OpcodeKind::Sust),
2417        "szext" => Some(OpcodeKind::Szext),
2418        "tanh" => Some(OpcodeKind::Tanh),
2419        "tcgen05" => Some(OpcodeKind::Tcgen05),
2420        "tensormap" => Some(OpcodeKind::Tensormap),
2421        "tex" => Some(OpcodeKind::Tex),
2422        "testp" => Some(OpcodeKind::Testp),
2423        "tld4" => Some(OpcodeKind::Tld4),
2424        "trap" => Some(OpcodeKind::Trap),
2425        "txq" => Some(OpcodeKind::Txq),
2426        "vabsdiff" => Some(OpcodeKind::Vabsdiff),
2427        "vabsdiff2" => Some(OpcodeKind::Vabsdiff2),
2428        "vabsdiff4" => Some(OpcodeKind::Vabsdiff4),
2429        "vadd" => Some(OpcodeKind::Vadd),
2430        "vadd2" => Some(OpcodeKind::Vadd2),
2431        "vadd4" => Some(OpcodeKind::Vadd4),
2432        "vavrg2" => Some(OpcodeKind::Vavrg2),
2433        "vavrg4" => Some(OpcodeKind::Vavrg4),
2434        "vmad" => Some(OpcodeKind::Vmad),
2435        "vmax" => Some(OpcodeKind::Vmax),
2436        "vmax2" => Some(OpcodeKind::Vmax2),
2437        "vmax4" => Some(OpcodeKind::Vmax4),
2438        "vmin" => Some(OpcodeKind::Vmin),
2439        "vmin2" => Some(OpcodeKind::Vmin2),
2440        "vmin4" => Some(OpcodeKind::Vmin4),
2441        "vset" => Some(OpcodeKind::Vset),
2442        "vset2" => Some(OpcodeKind::Vset2),
2443        "vset4" => Some(OpcodeKind::Vset4),
2444        "vshl" => Some(OpcodeKind::Vshl),
2445        "vshr" => Some(OpcodeKind::Vshr),
2446        "vsub" => Some(OpcodeKind::Vsub),
2447        "vsub2" => Some(OpcodeKind::Vsub2),
2448        "vsub4" => Some(OpcodeKind::Vsub4),
2449        "vote" => Some(OpcodeKind::Vote),
2450        "wgmma" => Some(OpcodeKind::Wgmma),
2451        "wmma" => Some(OpcodeKind::Wmma),
2452        "xor" => Some(OpcodeKind::Xor),
2453        "ret" => Some(OpcodeKind::Ret),
2454        _ => None,
2455    }
2456}
2457
2458fn classify_modifier(lower: &str) -> Option<ModifierKind> {
2459    if let Some(space) = parse_address_space_modifier(lower) {
2460        return Some(ModifierKind::AddressSpace(space));
2461    }
2462    if let Some(ty) = parse_type_modifier(lower) {
2463        return Some(ModifierKind::Type(ty));
2464    }
2465    if let Some(cond) = parse_condition_modifier(lower) {
2466        return Some(ModifierKind::Condition(cond));
2467    }
2468    if let Some(rounding) = parse_rounding_modifier(lower) {
2469        return Some(ModifierKind::Rounding(rounding));
2470    }
2471    if let Some(mode) = parse_math_mode_modifier(lower) {
2472        return Some(ModifierKind::MathMode(mode));
2473    }
2474    if let Some(sync) = parse_synchronization_modifier(lower) {
2475        return Some(ModifierKind::Synchronization(sync));
2476    }
2477    if let Some(group) = parse_async_group_modifier(lower) {
2478        return Some(ModifierKind::AsyncGroup(group));
2479    }
2480    if let Some(shuffle) = parse_shuffle_modifier(lower) {
2481        return Some(ModifierKind::Shuffle(shuffle));
2482    }
2483    if let Some(cache) = parse_cache_modifier(lower) {
2484        return Some(ModifierKind::Cache(cache));
2485    }
2486    if let Some(scope) = parse_scope_modifier(lower) {
2487        return Some(ModifierKind::Scope(scope));
2488    }
2489    if let Some(atom) = parse_atomic_modifier(lower) {
2490        return Some(ModifierKind::Atomic(atom));
2491    }
2492    if let Some(call) = parse_call_modifier(lower) {
2493        return Some(ModifierKind::Call(call));
2494    }
2495    if let Some(order) = parse_memory_order_modifier(lower) {
2496        return Some(ModifierKind::MemoryOrder(order));
2497    }
2498
2499    None
2500}
2501
2502fn parse_type_modifier(lower: &str) -> Option<TypeModifier> {
2503    match lower {
2504        "f16" => Some(TypeModifier::F16),
2505        "f32" => Some(TypeModifier::F32),
2506        "f64" => Some(TypeModifier::F64),
2507        "f128" => Some(TypeModifier::F128),
2508        "b8" => Some(TypeModifier::B8),
2509        "b16" => Some(TypeModifier::B16),
2510        "b32" => Some(TypeModifier::B32),
2511        "b64" => Some(TypeModifier::B64),
2512        "s8" => Some(TypeModifier::S8),
2513        "s16" => Some(TypeModifier::S16),
2514        "s32" => Some(TypeModifier::S32),
2515        "s64" => Some(TypeModifier::S64),
2516        "u8" => Some(TypeModifier::U8),
2517        "u16" => Some(TypeModifier::U16),
2518        "u32" => Some(TypeModifier::U32),
2519        "u64" => Some(TypeModifier::U64),
2520        "pred" => Some(TypeModifier::Pred),
2521        _ => None,
2522    }
2523}
2524
2525fn parse_condition_modifier(lower: &str) -> Option<ConditionModifier> {
2526    match lower {
2527        "eq" => Some(ConditionModifier::Eq),
2528        "ne" => Some(ConditionModifier::Ne),
2529        "lt" => Some(ConditionModifier::Lt),
2530        "le" => Some(ConditionModifier::Le),
2531        "gt" => Some(ConditionModifier::Gt),
2532        "ge" => Some(ConditionModifier::Ge),
2533        "lo" => Some(ConditionModifier::Lo),
2534        "hi" => Some(ConditionModifier::Hi),
2535        "ls" => Some(ConditionModifier::Ls),
2536        "hs" => Some(ConditionModifier::Hs),
2537        _ => None,
2538    }
2539}
2540
2541fn parse_address_space_modifier(lower: &str) -> Option<StateSpaceModifier> {
2542    match lower {
2543        "param" => Some(StateSpaceModifier::Param),
2544        "global" => Some(StateSpaceModifier::Global),
2545        "local" => Some(StateSpaceModifier::Local),
2546        "shared" => Some(StateSpaceModifier::Shared),
2547        "const" => Some(StateSpaceModifier::Const),
2548        "generic" => Some(StateSpaceModifier::Generic),
2549        _ => None,
2550    }
2551}
2552
2553fn parse_rounding_modifier(lower: &str) -> Option<RoundingModifier> {
2554    match lower {
2555        "rn" => Some(RoundingModifier::Rn),
2556        "rz" => Some(RoundingModifier::Rz),
2557        "rm" => Some(RoundingModifier::Rm),
2558        "rp" => Some(RoundingModifier::Rp),
2559        _ => None,
2560    }
2561}
2562
2563fn parse_math_mode_modifier(lower: &str) -> Option<MathModeModifier> {
2564    match lower {
2565        "approx" => Some(MathModeModifier::Approx),
2566        "full" => Some(MathModeModifier::Full),
2567        _ => None,
2568    }
2569}
2570
2571fn parse_synchronization_modifier(lower: &str) -> Option<SynchronizationModifier> {
2572    match lower {
2573        "sync" => Some(SynchronizationModifier::Sync),
2574        "async" => Some(SynchronizationModifier::Async),
2575        _ => None,
2576    }
2577}
2578
2579fn parse_async_group_modifier(lower: &str) -> Option<AsyncGroupModifier> {
2580    match lower {
2581        "commit_group" => Some(AsyncGroupModifier::CommitGroup),
2582        "wait_group" => Some(AsyncGroupModifier::WaitGroup),
2583        _ => None,
2584    }
2585}
2586
2587fn parse_shuffle_modifier(lower: &str) -> Option<ShuffleModifier> {
2588    match lower {
2589        "bfly" => Some(ShuffleModifier::Bfly),
2590        "down" => Some(ShuffleModifier::Down),
2591        "up" => Some(ShuffleModifier::Up),
2592        "idx" => Some(ShuffleModifier::Idx),
2593        _ => None,
2594    }
2595}
2596
2597fn parse_cache_modifier(lower: &str) -> Option<CacheModifier> {
2598    match lower {
2599        "nc" => Some(CacheModifier::Nc),
2600        "ca" => Some(CacheModifier::Ca),
2601        "cg" => Some(CacheModifier::Cg),
2602        "cs" => Some(CacheModifier::Cs),
2603        "lu" => Some(CacheModifier::Lu),
2604        _ => None,
2605    }
2606}
2607
2608fn parse_scope_modifier(lower: &str) -> Option<MemoryScopeModifier> {
2609    match lower {
2610        "cta" => Some(MemoryScopeModifier::Cta),
2611        "gl" => Some(MemoryScopeModifier::Gl),
2612        "gpu" => Some(MemoryScopeModifier::Gpu),
2613        "sys" => Some(MemoryScopeModifier::Sys),
2614        _ => None,
2615    }
2616}
2617
2618fn parse_atomic_modifier(lower: &str) -> Option<AtomicOperationModifier> {
2619    match lower {
2620        "cas" => Some(AtomicOperationModifier::Cas),
2621        "add" => Some(AtomicOperationModifier::Add),
2622        "inc" => Some(AtomicOperationModifier::Inc),
2623        "dec" => Some(AtomicOperationModifier::Dec),
2624        "exch" => Some(AtomicOperationModifier::Exch),
2625        "min" => Some(AtomicOperationModifier::Min),
2626        "max" => Some(AtomicOperationModifier::Max),
2627        "and" => Some(AtomicOperationModifier::And),
2628        "or" => Some(AtomicOperationModifier::Or),
2629        "xor" => Some(AtomicOperationModifier::Xor),
2630        _ => None,
2631    }
2632}
2633
2634fn parse_call_modifier(lower: &str) -> Option<CallModifier> {
2635    match lower {
2636        "uni" => Some(CallModifier::Uni),
2637        _ => None,
2638    }
2639}
2640
2641fn parse_vector_width_modifier(lower: &str) -> Option<u32> {
2642    lower
2643        .strip_prefix('v')
2644        .and_then(|value| value.parse::<u32>().ok())
2645}
2646
2647fn parse_memory_order_modifier(lower: &str) -> Option<MemoryOrderModifier> {
2648    match lower {
2649        "relaxed" => Some(MemoryOrderModifier::Relaxed),
2650        "acquire" => Some(MemoryOrderModifier::Acquire),
2651        "release" => Some(MemoryOrderModifier::Release),
2652        "acq_rel" | "acqrel" => Some(MemoryOrderModifier::AcqRel),
2653        "sc" => Some(MemoryOrderModifier::Sc),
2654        _ => None,
2655    }
2656}
2657
2658fn is_numeric_literal(token: &str) -> bool {
2659    if token.is_empty() {
2660        return false;
2661    }
2662
2663    if token.starts_with("0x") || token.starts_with("0X") {
2664        return token.len() > 2 && token[2..].chars().all(|c| c.is_ascii_hexdigit());
2665    }
2666
2667    if token.len() > 2 {
2668        let prefix = &token[..2];
2669        if matches!(prefix, "0d" | "0D" | "0f" | "0F") {
2670            return token[2..].chars().all(|c| c.is_ascii_hexdigit());
2671        }
2672    }
2673
2674    if token.starts_with('-') || token.starts_with('+') {
2675        return is_numeric_literal(&token[1..]);
2676    }
2677
2678    token.parse::<i64>().is_ok() || token.parse::<u64>().is_ok() || token.parse::<f64>().is_ok()
2679}
2680
2681fn is_symbol_token(token: &str) -> bool {
2682    if token.is_empty() {
2683        return false;
2684    }
2685
2686    let mut chars = token.chars();
2687    match chars.next() {
2688        Some(ch) if ch.is_ascii_alphabetic() || ch == '_' || ch == '$' || ch == '.' => {}
2689        _ => return false,
2690    }
2691
2692    chars.all(|ch| {
2693        ch.is_ascii_alphanumeric() || matches!(ch, '_' | '$' | '.' | ':' | '<' | '>' | '?' | '@')
2694    })
2695}
2696
2697fn split_comment(line: &str) -> (String, Option<String>) {
2698    if let Some(pos) = line.find("//") {
2699        let (content, comment) = line.split_at(pos);
2700        (
2701            content.trim_end().to_string(),
2702            Some(comment[2..].trim().to_string()),
2703        )
2704    } else if let Some(pos) = line.find('#') {
2705        let (content, comment) = line.split_at(pos);
2706        (
2707            content.trim_end().to_string(),
2708            Some(comment[1..].trim().to_string()),
2709        )
2710    } else {
2711        (line.to_string(), None)
2712    }
2713}
2714
2715fn strip_comments(line: &str) -> String {
2716    if let Some(pos) = line.find("//") {
2717        line[..pos].trim().to_string()
2718    } else if let Some(pos) = line.find('#') {
2719        line[..pos].trim().to_string()
2720    } else {
2721        line.trim().to_string()
2722    }
2723}
2724
2725fn count_occurrences(line: &str, ch: char) -> usize {
2726    line.chars().filter(|c| *c == ch).count()
2727}
2728
2729fn contains_keyword(line: &str, keyword: &str) -> bool {
2730    line.split_whitespace().any(|token| token == keyword)
2731}
2732
2733fn likely_state_space(line: &str) -> bool {
2734    let trimmed = line.trim_start();
2735    trimmed.starts_with(".tex")
2736        || trimmed.contains(".global")
2737        || trimmed.contains(".const")
2738        || trimmed.contains(".shared")
2739}
2740
2741fn split_initializer(line: &str) -> (&str, Option<&str>) {
2742    if let Some(idx) = line.find('=') {
2743        let (left, right) = line.split_at(idx);
2744        (left.trim_end(), Some(right[1..].trim_start()))
2745    } else {
2746        (line, None)
2747    }
2748}
2749
2750fn parse_target_directive(
2751    rest: &str,
2752    line_number: usize,
2753) -> Result<TargetDirective, PtxParseError> {
2754    if rest.is_empty() {
2755        return Err(PtxParseError::InvalidDirective {
2756            line: line_number,
2757            message: ".target directive expects at least one argument".into(),
2758        });
2759    }
2760
2761    let mut entries = Vec::new();
2762    for entry in rest.split(',') {
2763        let token = entry.trim();
2764        if token.is_empty() {
2765            continue;
2766        }
2767        if !is_valid_target_entry(token) {
2768            return Err(PtxParseError::InvalidDirective {
2769                line: line_number,
2770                message: format!("invalid .target entry '{token}'"),
2771            });
2772        }
2773        entries.push(token.to_string());
2774    }
2775
2776    if entries.is_empty() {
2777        return Err(PtxParseError::InvalidDirective {
2778            line: line_number,
2779            message: "unable to parse arguments for .target directive".into(),
2780        });
2781    }
2782
2783    Ok(TargetDirective {
2784        entries,
2785        raw: rest.to_string(),
2786    })
2787}
2788
2789fn parse_alias(line: &str, line_number: usize) -> Result<FunctionAlias, PtxParseError> {
2790    let trimmed = line.trim();
2791    if !trimmed.ends_with(';') {
2792        return Err(PtxParseError::InvalidDirective {
2793            line: line_number,
2794            message: ".alias directive must end with ';'".into(),
2795        });
2796    }
2797
2798    let body = trimmed
2799        .trim_end_matches(';')
2800        .trim_start_matches(".alias")
2801        .trim();
2802
2803    let mut parts = body
2804        .split(',')
2805        .map(|part| part.trim())
2806        .filter(|part| !part.is_empty());
2807
2808    let alias = parts
2809        .next()
2810        .ok_or_else(|| PtxParseError::InvalidDirective {
2811            line: line_number,
2812            message: "missing alias name in .alias directive".into(),
2813        })?;
2814    let target = parts
2815        .next()
2816        .ok_or_else(|| PtxParseError::InvalidDirective {
2817            line: line_number,
2818            message: "missing aliasee in .alias directive".into(),
2819        })?;
2820
2821    if parts.next().is_some() {
2822        return Err(PtxParseError::InvalidDirective {
2823            line: line_number,
2824            message: "too many operands in .alias directive".into(),
2825        });
2826    }
2827
2828    if alias.is_empty() || target.is_empty() {
2829        return Err(PtxParseError::InvalidDirective {
2830            line: line_number,
2831            message: "invalid operands in .alias directive".into(),
2832        });
2833    }
2834
2835    Ok(FunctionAlias {
2836        alias: alias.to_string(),
2837        target: target.to_string(),
2838        raw: trimmed.to_string(),
2839    })
2840}
2841
2842fn is_valid_target_entry(entry: &str) -> bool {
2843    const BASE_ENTRIES: &[&str] = &[
2844        "sm_120a",
2845        "sm_120f",
2846        "sm_120",
2847        "sm_121a",
2848        "sm_121f",
2849        "sm_121",
2850        "sm_110a",
2851        "sm_110f",
2852        "sm_110",
2853        "sm_100a",
2854        "sm_100f",
2855        "sm_100",
2856        "sm_101a",
2857        "sm_101f",
2858        "sm_101",
2859        "sm_103a",
2860        "sm_103f",
2861        "sm_103",
2862        "sm_90a",
2863        "sm_90",
2864        "sm_80",
2865        "sm_86",
2866        "sm_87",
2867        "sm_88",
2868        "sm_89",
2869        "sm_70",
2870        "sm_72",
2871        "sm_75",
2872        "sm_60",
2873        "sm_61",
2874        "sm_62",
2875        "sm_50",
2876        "sm_52",
2877        "sm_53",
2878        "sm_30",
2879        "sm_32",
2880        "sm_35",
2881        "sm_37",
2882        "sm_20",
2883        "sm_10",
2884        "sm_11",
2885        "sm_12",
2886        "sm_13",
2887        "texmode_unified",
2888        "texmode_independent",
2889        "debug",
2890        "map_f64_to_f32",
2891    ];
2892
2893    BASE_ENTRIES.contains(&entry)
2894}
2895
2896#[derive(Debug)]
2897struct ParsedStateVariable {
2898    visibility: Option<GlobalVisibility>,
2899    linkages: Vec<GlobalLinkage>,
2900    address_space: Option<GlobalAddressSpace>,
2901    mutability: Option<GlobalMutability>,
2902    alignment: Option<u32>,
2903    ty: Option<ScalarType>,
2904    qualifiers: Vec<VariableQualifier>,
2905    name: String,
2906    array: Option<ArraySpecifier>,
2907    initializer: Option<GlobalInitializer>,
2908    raw: String,
2909}
2910
2911impl ParsedStateVariable {
2912    fn into_variable(mut self, space: GlobalAddressSpace) -> VariableDirective {
2913        self.address_space = Some(space);
2914        VariableDirective {
2915            visibility: self.visibility,
2916            linkages: self.linkages,
2917            address_space: self.address_space,
2918            mutability: self.mutability,
2919            alignment: self.alignment,
2920            ty: self.ty,
2921            qualifiers: self.qualifiers,
2922            name: self.name,
2923            array: self.array,
2924            initializer: self.initializer,
2925            raw: self.raw,
2926        }
2927    }
2928}
2929
2930fn parse_register_declaration(
2931    keyword: &str,
2932    directive_body: &str,
2933    comment: Option<String>,
2934    raw: String,
2935    line_number: usize,
2936) -> Result<RegisterDeclaration, PtxParseError> {
2937    let rest = directive_body
2938        .strip_prefix(keyword)
2939        .unwrap_or(directive_body)
2940        .trim_start();
2941
2942    if rest.is_empty() {
2943        return Err(PtxParseError::InvalidDirective {
2944            line: line_number,
2945            message: format!("missing register type in '{keyword}' directive"),
2946        });
2947    }
2948
2949    let (type_token, registers_part) =
2950        if let Some((idx, _)) = rest.char_indices().find(|(_, ch)| ch.is_whitespace()) {
2951            let (ty, remainder) = rest.split_at(idx);
2952            (ty, remainder.trim_start())
2953        } else {
2954            return Err(PtxParseError::InvalidDirective {
2955                line: line_number,
2956                message: format!("missing register list in '{keyword}' directive"),
2957            });
2958        };
2959
2960    if type_token.is_empty() {
2961        return Err(PtxParseError::InvalidDirective {
2962            line: line_number,
2963            message: format!("missing register type in '{keyword}' directive"),
2964        });
2965    }
2966
2967    if registers_part.is_empty() {
2968        return Err(PtxParseError::InvalidDirective {
2969            line: line_number,
2970            message: format!("missing registers in '{keyword}' directive"),
2971        });
2972    }
2973
2974    let ty = RegisterType {
2975        scalar: parse_scalar_type(type_token),
2976        raw: type_token.to_string(),
2977    };
2978
2979    let mut registers = Vec::new();
2980    for spec in registers_part.split(',') {
2981        let trimmed = spec.trim();
2982        if trimmed.is_empty() {
2983            continue;
2984        }
2985        registers.push(parse_register_specifier(trimmed, line_number)?);
2986    }
2987
2988    if registers.is_empty() {
2989        return Err(PtxParseError::InvalidDirective {
2990            line: line_number,
2991            message: format!("no registers listed in '{keyword}' directive"),
2992        });
2993    }
2994
2995    Ok(RegisterDeclaration {
2996        keyword: keyword.to_string(),
2997        ty,
2998        registers,
2999        comment,
3000        raw,
3001    })
3002}
3003
3004fn parse_register_specifier(
3005    token: &str,
3006    line_number: usize,
3007) -> Result<RegisterSpecifier, PtxParseError> {
3008    if let Some(start) = token.find('<') {
3009        let end = token
3010            .rfind('>')
3011            .ok_or_else(|| PtxParseError::InvalidDirective {
3012                line: line_number,
3013                message: format!("unterminated register range '{token}'"),
3014            })?;
3015
3016        if end <= start + 1 {
3017            return Err(PtxParseError::InvalidDirective {
3018                line: line_number,
3019                message: format!("empty register range '{token}'"),
3020            });
3021        }
3022
3023        let prefix = token[..start].trim();
3024        if prefix.is_empty() {
3025            return Err(PtxParseError::InvalidDirective {
3026                line: line_number,
3027                message: format!("missing prefix in register range '{token}'"),
3028            });
3029        }
3030
3031        let count_str = token[start + 1..end].trim();
3032        if count_str.is_empty() {
3033            return Err(PtxParseError::InvalidDirective {
3034                line: line_number,
3035                message: format!("missing count in register range '{token}'"),
3036            });
3037        }
3038
3039        let count = count_str
3040            .parse::<u32>()
3041            .map_err(|_| PtxParseError::InvalidDirective {
3042                line: line_number,
3043                message: format!("invalid register count '{count_str}' in '{token}'"),
3044            })?;
3045
3046        return Ok(RegisterSpecifier::Range {
3047            prefix: prefix.to_string(),
3048            count,
3049        });
3050    }
3051
3052    Ok(RegisterSpecifier::Named(token.to_string()))
3053}
3054
3055fn split_name_token(
3056    token: &str,
3057    line_number: usize,
3058) -> Result<(String, Option<ArraySpecifier>), PtxParseError> {
3059    if let Some(start) = token.find('[') {
3060        if !token.ends_with(']') {
3061            return Err(PtxParseError::InvalidGlobal {
3062                line: line_number,
3063                message: format!("unterminated array specifier in global name '{token}'"),
3064            });
3065        }
3066
3067        let name = token[..start].to_string();
3068        let spec = parse_array_specifier(&token[start..], line_number)?;
3069        Ok((name, Some(spec)))
3070    } else {
3071        Ok((token.to_string(), None))
3072    }
3073}
3074
3075fn parse_array_specifier(spec: &str, line_number: usize) -> Result<ArraySpecifier, PtxParseError> {
3076    if spec.is_empty() || !spec.starts_with('[') {
3077        return Err(PtxParseError::InvalidGlobal {
3078            line: line_number,
3079            message: format!("invalid array specifier '{spec}'"),
3080        });
3081    }
3082
3083    let mut dimensions = Vec::new();
3084    let mut idx = 0;
3085    while idx < spec.len() {
3086        let remaining = &spec[idx..];
3087        if !remaining.starts_with('[') {
3088            return Err(PtxParseError::InvalidGlobal {
3089                line: line_number,
3090                message: format!("malformed array specifier '{spec}'"),
3091            });
3092        }
3093
3094        let close_offset = remaining
3095            .find(']')
3096            .ok_or_else(|| PtxParseError::InvalidGlobal {
3097                line: line_number,
3098                message: format!("unterminated array specifier '{spec}'"),
3099            })?;
3100        let close_idx = idx + close_offset;
3101        let inner = spec[idx + 1..close_idx].trim();
3102        if inner.is_empty() {
3103            dimensions.push(None);
3104        } else {
3105            let bound = inner
3106                .parse::<u64>()
3107                .map_err(|_| PtxParseError::InvalidGlobal {
3108                    line: line_number,
3109                    message: format!("invalid array bound '{inner}'"),
3110                })?;
3111            dimensions.push(Some(bound));
3112        }
3113        idx = close_idx + 1;
3114    }
3115
3116    if dimensions.is_empty() {
3117        return Err(PtxParseError::InvalidGlobal {
3118            line: line_number,
3119            message: format!("empty array specifier '{spec}'"),
3120        });
3121    }
3122
3123    Ok(ArraySpecifier { dimensions })
3124}
3125
3126fn parse_global_visibility(token: &str) -> Option<GlobalVisibility> {
3127    match token {
3128        ".visible" => Some(GlobalVisibility::Visible),
3129        ".hidden" => Some(GlobalVisibility::Hidden),
3130        _ => None,
3131    }
3132}
3133
3134fn parse_global_linkage(token: &str) -> Option<GlobalLinkage> {
3135    match token {
3136        ".extern" => Some(GlobalLinkage::Extern),
3137        ".weak" => Some(GlobalLinkage::Weak),
3138        ".weakextern" => Some(GlobalLinkage::WeakExtern),
3139        _ => None,
3140    }
3141}
3142
3143fn parse_global_address_space(token: &str) -> Option<GlobalAddressSpace> {
3144    match token {
3145        ".global" => Some(GlobalAddressSpace::Global),
3146        ".const" => Some(GlobalAddressSpace::Const),
3147        ".shared" => Some(GlobalAddressSpace::Shared),
3148        ".local" => Some(GlobalAddressSpace::Local),
3149        _ => None,
3150    }
3151}
3152
3153fn parse_global_mutability(token: &str) -> Option<GlobalMutability> {
3154    match token {
3155        ".const" => Some(GlobalMutability::Const),
3156        _ => None,
3157    }
3158}
3159
3160fn parse_scalar_type(token: &str) -> Option<ScalarType> {
3161    if !token.starts_with('.') {
3162        return None;
3163    }
3164
3165    match token[1..].to_ascii_lowercase().as_str() {
3166        "b8" => Some(ScalarType::B8),
3167        "b16" => Some(ScalarType::B16),
3168        "b32" => Some(ScalarType::B32),
3169        "b64" => Some(ScalarType::B64),
3170        "s8" => Some(ScalarType::S8),
3171        "s16" => Some(ScalarType::S16),
3172        "s32" => Some(ScalarType::S32),
3173        "s64" => Some(ScalarType::S64),
3174        "u8" => Some(ScalarType::U8),
3175        "u16" => Some(ScalarType::U16),
3176        "u32" => Some(ScalarType::U32),
3177        "u64" => Some(ScalarType::U64),
3178        "f16" => Some(ScalarType::F16),
3179        "f32" => Some(ScalarType::F32),
3180        "f64" => Some(ScalarType::F64),
3181        "pred" => Some(ScalarType::Pred),
3182        "texref" => Some(ScalarType::TexRef),
3183        "samplerref" => Some(ScalarType::SamplerRef),
3184        "surfref" => Some(ScalarType::SurfRef),
3185        _ => None,
3186    }
3187}
3188
3189enum VariableQualifierContext {
3190    Tex,
3191    StateSpace,
3192}
3193
3194fn variable_qualifier_from_token(
3195    token: &str,
3196    line_number: usize,
3197    context: VariableQualifierContext,
3198) -> Result<VariableQualifier, PtxParseError> {
3199    if let Some(rest) = token.strip_prefix(".v") {
3200        if let Ok(width) = rest.parse::<u32>() {
3201            return Ok(VariableQualifier::Vector(width));
3202        }
3203    }
3204
3205    if matches!(context, VariableQualifierContext::Tex) && token.eq_ignore_ascii_case(".sampler") {
3206        return Ok(VariableQualifier::Sampler);
3207    }
3208
3209    let message = format!("unrecognised qualifier '{}'", token);
3210    match context {
3211        VariableQualifierContext::Tex => Err(PtxParseError::InvalidDirective {
3212            line: line_number,
3213            message,
3214        }),
3215        VariableQualifierContext::StateSpace => Err(PtxParseError::InvalidGlobal {
3216            line: line_number,
3217            message,
3218        }),
3219    }
3220}
3221
3222fn parse_global_initializer(
3223    raw: &str,
3224    line_number: usize,
3225) -> Result<GlobalInitializer, PtxParseError> {
3226    let trimmed = raw.trim();
3227    if trimmed.is_empty() {
3228        return Err(PtxParseError::InvalidGlobal {
3229            line: line_number,
3230            message: "empty global initializer".into(),
3231        });
3232    }
3233
3234    if trimmed.starts_with('{') {
3235        if !trimmed.ends_with('}') {
3236            return Err(PtxParseError::InvalidGlobal {
3237                line: line_number,
3238                message: "unterminated aggregate initializer".into(),
3239            });
3240        }
3241        let inner = &trimmed[1..trimmed.len() - 1];
3242        let mut values = Vec::new();
3243        for segment in split_top_level_commas(inner) {
3244            let token = segment.trim();
3245            if token.is_empty() {
3246                continue;
3247            }
3248            values.push(parse_global_initializer(token, line_number)?);
3249        }
3250        return Ok(GlobalInitializer::Aggregate(values));
3251    }
3252
3253    let value = parse_initializer_value(trimmed, line_number)?;
3254    Ok(GlobalInitializer::Scalar(value))
3255}
3256
3257fn split_top_level_commas(input: &str) -> Vec<&str> {
3258    let mut parts = Vec::new();
3259    let mut depth = 0i32;
3260    let mut start = 0usize;
3261    for (idx, ch) in input.char_indices() {
3262        match ch {
3263            '{' => depth += 1,
3264            '}' => {
3265                if depth > 0 {
3266                    depth -= 1;
3267                }
3268            }
3269            ',' if depth == 0 => {
3270                parts.push(&input[start..idx]);
3271                start = idx + 1;
3272            }
3273            _ => {}
3274        }
3275    }
3276    if start <= input.len() {
3277        parts.push(&input[start..]);
3278    }
3279    parts
3280}
3281
3282fn parse_initializer_value(
3283    token: &str,
3284    line_number: usize,
3285) -> Result<InitializerValue, PtxParseError> {
3286    if token.is_empty() {
3287        return Err(PtxParseError::InvalidGlobal {
3288            line: line_number,
3289            message: "empty initializer token".into(),
3290        });
3291    }
3292
3293    if token.starts_with('"') {
3294        if !token.ends_with('"') || token.len() < 2 {
3295            return Err(PtxParseError::InvalidGlobal {
3296                line: line_number,
3297                message: "unterminated string literal in initializer".into(),
3298            });
3299        }
3300        let literal = parse_string_literal(token, line_number)?;
3301        return Ok(InitializerValue::StringLiteral(literal));
3302    }
3303
3304    if let Some(numeric) = try_parse_numeric_literal(token) {
3305        return Ok(InitializerValue::Numeric(numeric));
3306    }
3307
3308    if is_symbol_token(token) {
3309        return Ok(InitializerValue::Symbol(token.to_string()));
3310    }
3311
3312    Ok(InitializerValue::Symbol(token.to_string()))
3313}
3314
3315fn parse_string_literal(token: &str, line_number: usize) -> Result<String, PtxParseError> {
3316    let inner = &token[1..token.len() - 1];
3317    let mut result = String::with_capacity(inner.len());
3318    let mut chars = inner.chars();
3319    while let Some(ch) = chars.next() {
3320        if ch == '\\' {
3321            let Some(escaped) = chars.next() else {
3322                return Err(PtxParseError::InvalidGlobal {
3323                    line: line_number,
3324                    message: "incomplete escape sequence in string literal".into(),
3325                });
3326            };
3327            match escaped {
3328                'n' => result.push('\n'),
3329                'r' => result.push('\r'),
3330                't' => result.push('\t'),
3331                '\\' => result.push('\\'),
3332                '"' => result.push('"'),
3333                other => {
3334                    result.push(other);
3335                }
3336            }
3337        } else {
3338            result.push(ch);
3339        }
3340    }
3341    Ok(result)
3342}
3343
3344fn unquote_string(token: &str, line_number: usize) -> Result<String, PtxParseError> {
3345    if token.starts_with('"') && token.ends_with('"') && token.len() >= 2 {
3346        parse_string_literal(token, line_number)
3347    } else {
3348        Ok(token.to_string())
3349    }
3350}
3351
3352fn try_parse_numeric_literal(token: &str) -> Option<NumericLiteral> {
3353    if let Some(hex) = token
3354        .strip_prefix("0d")
3355        .or_else(|| token.strip_prefix("0D"))
3356    {
3357        if let Ok(bits) = u64::from_str_radix(hex, 16) {
3358            return Some(NumericLiteral::Float64(bits));
3359        }
3360    }
3361
3362    if let Some(hex) = token
3363        .strip_prefix("0f")
3364        .or_else(|| token.strip_prefix("0F"))
3365    {
3366        if let Ok(bits) = u32::from_str_radix(hex, 16) {
3367            return Some(NumericLiteral::Float32(bits));
3368        }
3369    }
3370
3371    if let Some(hex) = token
3372        .strip_prefix("0x")
3373        .or_else(|| token.strip_prefix("0X"))
3374    {
3375        if let Ok(value) = u64::from_str_radix(hex, 16) {
3376            return Some(NumericLiteral::Unsigned(value));
3377        }
3378    }
3379
3380    if let Some(hex) = token
3381        .strip_prefix("-0x")
3382        .or_else(|| token.strip_prefix("-0X"))
3383    {
3384        if let Ok(value) = i64::from_str_radix(hex, 16) {
3385            return Some(NumericLiteral::Signed(-value));
3386        }
3387    }
3388
3389    if let Ok(value) = token.parse::<i64>() {
3390        return Some(NumericLiteral::Signed(value));
3391    }
3392
3393    if let Ok(value) = token.parse::<u64>() {
3394        return Some(NumericLiteral::Unsigned(value));
3395    }
3396
3397    if let Ok(value) = token.parse::<f64>() {
3398        return Some(NumericLiteral::Float64(value.to_bits()));
3399    }
3400
3401    if let Ok(value) = token.parse::<f32>() {
3402        return Some(NumericLiteral::Float32(value.to_bits()));
3403    }
3404
3405    None
3406}