Skip to main content

sbpf_assembler/parser/
mod.rs

1pub mod common;
2mod default;
3mod directive;
4mod llvm;
5
6use {
7    crate::{
8        SbpfArch,
9        ast::AST,
10        astnode::{ASTNode, Label},
11        dynsym::{DynamicSymbolMap, RelDynMap},
12        errors::CompileError,
13        section::{CodeSection, DataSection, DebugSection},
14    },
15    directive::{process_directive_statement, process_rodata_directive},
16    pest::{
17        Parser,
18        error::{ErrorVariant, InputLocation},
19        iterators::Pair,
20    },
21    pest_derive::Parser,
22    sbpf_common::{inst_param::Number, instruction::Instruction},
23    std::collections::HashMap,
24};
25
26#[derive(Parser)]
27#[grammar = "sbpf.pest"]
28pub struct SbpfParser;
29
30/// Which section a label belongs to.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub(crate) enum Section {
33    Text,
34    Rodata,
35}
36
37/// Context containing all mutable state during parsing
38pub(crate) struct ParseContext<'a> {
39    pub ast: &'a mut AST,
40    pub const_map: &'a mut HashMap<String, Number>,
41    pub label_spans: &'a mut HashMap<String, std::ops::Range<usize>>,
42    pub label_offset_map: &'a mut HashMap<String, (Number, Section)>,
43    pub errors: Vec<CompileError>,
44    pub rodata_phase: bool,
45    pub text_offset: u64,
46    pub rodata_offset: u64,
47    pub missing_text_directive: bool,
48    /// A rodata label on its own line, waiting for the next data directive.
49    pub pending_rodata_label: Option<(String, std::ops::Range<usize>)>,
50}
51
52/// BPF_X flag: Converts immediate variant opcodes to register variant opcodes
53const BPF_X: u8 = 0x08;
54
55/// Token types used in the AST
56#[derive(Debug, Clone)]
57pub enum Token {
58    Directive(String, std::ops::Range<usize>),
59    Identifier(String, std::ops::Range<usize>),
60    ImmediateValue(Number, std::ops::Range<usize>),
61    StringLiteral(String, std::ops::Range<usize>),
62    VectorLiteral(Vec<Number>, std::ops::Range<usize>),
63}
64
65pub struct ParseResult {
66    // TODO: parse result is basically 1. static part 2. dynamic part of the program
67    pub code_section: CodeSection,
68
69    pub data_section: DataSection,
70
71    pub dynamic_symbols: DynamicSymbolMap,
72
73    pub relocation_data: RelDynMap,
74
75    // TODO: this can be removed and dynamic-ness should just be
76    // determined by if there's any dynamic symbol
77    pub prog_is_static: bool,
78
79    pub arch: SbpfArch,
80
81    // Debug sections we came across while byteparsing
82    pub debug_sections: Vec<DebugSection>,
83}
84
85pub fn parse(source: &str, arch: SbpfArch) -> Result<ParseResult, Vec<CompileError>> {
86    let pairs = SbpfParser::parse(Rule::program, source).map_err(|e| {
87        // Extract the actual byte position from the pest error so the source
88        // map can resolve it back to the original file/line.
89        let span = match e.location {
90            InputLocation::Pos(pos) => pos..pos + 1,
91            InputLocation::Span((start, end)) => start..end,
92        };
93
94        // Build a clean message without pest's embedded source context,
95        // which would show expanded-source line numbers.
96        let message = match &e.variant {
97            ErrorVariant::ParsingError {
98                positives,
99                negatives,
100            } => {
101                let pos: Vec<String> = positives.iter().filter_map(rule_display_name).collect();
102                let neg: Vec<String> = negatives.iter().filter_map(rule_display_name).collect();
103                let mut parts = Vec::new();
104                if !pos.is_empty() {
105                    parts.push(format!("expected {}", pos.join(", ")));
106                }
107                if !neg.is_empty() {
108                    parts.push(format!("unexpected {}", neg.join(", ")));
109                }
110                if parts.is_empty() {
111                    "Parse error".to_string()
112                } else {
113                    parts.join("; ")
114                }
115            }
116            ErrorVariant::CustomError { message } => message.clone(),
117        };
118
119        vec![CompileError::ParseError {
120            error: message,
121            span,
122            custom_label: None,
123        }]
124    })?;
125
126    let mut ast = AST::new();
127    let mut const_map = HashMap::<String, Number>::new();
128    let mut label_spans = HashMap::<String, std::ops::Range<usize>>::new();
129
130    // Pass 1: collect all label offsets so forward references work in expressions.
131    let pairs_clone = pairs.clone();
132    let mut label_offset_map = collect_label_offsets(pairs_clone);
133
134    // Pass 2: full processing with label_offset_map already populated.
135    let (text_offset, rodata_offset, errors) = {
136        let mut ctx = ParseContext {
137            ast: &mut ast,
138            const_map: &mut const_map,
139            label_spans: &mut label_spans,
140            label_offset_map: &mut label_offset_map,
141            errors: Vec::new(),
142            rodata_phase: false,
143            text_offset: 0,
144            rodata_offset: 0,
145            missing_text_directive: false,
146            pending_rodata_label: None,
147        };
148
149        for pair in pairs {
150            match pair.as_rule() {
151                Rule::program_default | Rule::program_llvm => {
152                    for statement in pair.into_inner() {
153                        if statement.as_rule() == Rule::EOI {
154                            continue;
155                        }
156                        process_statement(statement, &mut ctx);
157                    }
158                }
159                _ => {}
160            }
161        }
162
163        (ctx.text_offset, ctx.rodata_offset, ctx.errors)
164    };
165
166    if !errors.is_empty() {
167        return Err(errors);
168    }
169
170    ast.set_text_size(text_offset);
171    ast.set_rodata_size(rodata_offset);
172
173    ast.build_program(arch)
174}
175
176/// Pass 1: lightweight scan of the parse tree to collect all label offsets.
177/// This enables forward references in operand expressions (e.g. rodata labels
178/// referenced from the text section that appears earlier in the source).
179fn collect_label_offsets(
180    pairs: pest::iterators::Pairs<Rule>,
181) -> HashMap<String, (Number, Section)> {
182    let mut map = HashMap::new();
183    let mut rodata_phase = false;
184    let mut text_offset: u64 = 0;
185    let mut rodata_offset: u64 = 0;
186
187    for pair in pairs {
188        match pair.as_rule() {
189            Rule::program_default | Rule::program_llvm => {
190                for statement in pair.into_inner() {
191                    if statement.as_rule() == Rule::EOI {
192                        continue;
193                    }
194                    scan_statement_for_labels(
195                        statement,
196                        &mut map,
197                        &mut rodata_phase,
198                        &mut text_offset,
199                        &mut rodata_offset,
200                    );
201                }
202            }
203            _ => {}
204        }
205    }
206    map
207}
208
209/// Scan a single statement to find labels and track offsets.
210fn scan_statement_for_labels(
211    pair: Pair<Rule>,
212    map: &mut HashMap<String, (Number, Section)>,
213    rodata_phase: &mut bool,
214    text_offset: &mut u64,
215    rodata_offset: &mut u64,
216) {
217    for inner in pair.into_inner() {
218        match inner.as_rule() {
219            Rule::label_default | Rule::label_llvm => {
220                scan_label(inner, map, rodata_phase, text_offset, rodata_offset);
221            }
222            Rule::directive => {
223                // Track section switches and standalone data directive sizes
224                for dir_inner in inner.into_inner() {
225                    let dir_inner_clone = dir_inner.clone();
226                    for dir_item in dir_inner.into_inner() {
227                        if dir_item.as_rule() == Rule::directive_section {
228                            let section_name = dir_item.as_str().trim_start_matches('.');
229                            match section_name {
230                                "text" => *rodata_phase = false,
231                                "rodata" => *rodata_phase = true,
232                                _ => {}
233                            }
234                        } else if *rodata_phase {
235                            // Standalone data directive in rodata — account for its size
236                            match dir_item.as_rule() {
237                                Rule::directive_ascii
238                                | Rule::directive_byte
239                                | Rule::directive_short
240                                | Rule::directive_word
241                                | Rule::directive_int
242                                | Rule::directive_long
243                                | Rule::directive_quad => {
244                                    *rodata_offset += rodata_directive_size(&dir_inner_clone);
245                                }
246                                _ => {}
247                            }
248                        }
249                    }
250                }
251            }
252            Rule::instr_default | Rule::instr_llvm if !*rodata_phase => {
253                let size = instr_size(&inner);
254                *text_offset += size;
255            }
256            _ => {}
257        }
258    }
259}
260
261/// Scan a label node: record its offset and account for any attached
262/// instruction/directive size.
263fn scan_label(
264    pair: Pair<Rule>,
265    map: &mut HashMap<String, (Number, Section)>,
266    rodata_phase: &mut bool,
267    text_offset: &mut u64,
268    rodata_offset: &mut u64,
269) {
270    let mut label_name = None;
271
272    for item in pair.into_inner() {
273        match item.as_rule() {
274            Rule::identifier | Rule::numeric_label => {
275                label_name = Some(item.as_str().to_string());
276            }
277            Rule::directive_inner => {
278                // Rodata directive attached to label — compute data size
279                if *rodata_phase {
280                    if let Some(ref name) = label_name {
281                        map.insert(
282                            name.clone(),
283                            (Number::Int(*rodata_offset as i64), Section::Rodata),
284                        );
285                    }
286                    let size = rodata_directive_size(&item);
287                    *rodata_offset += size;
288                }
289                return;
290            }
291            Rule::instr_default | Rule::instr_llvm => {
292                if !*rodata_phase {
293                    if let Some(ref name) = label_name {
294                        map.insert(
295                            name.clone(),
296                            (Number::Int(*text_offset as i64), Section::Text),
297                        );
298                    }
299                    let size = instr_size(&item);
300                    *text_offset += size;
301                }
302                return;
303            }
304            _ => {}
305        }
306    }
307
308    // Bare label (no directive or instruction attached)
309    if let Some(name) = label_name {
310        if *rodata_phase {
311            map.insert(name, (Number::Int(*rodata_offset as i64), Section::Rodata));
312        } else {
313            map.insert(name, (Number::Int(*text_offset as i64), Section::Text));
314        }
315    }
316}
317
318/// Determine instruction size from the parse tree (lddw = 16 bytes, all others = 8).
319fn instr_size(pair: &Pair<Rule>) -> u64 {
320    for inner in pair.clone().into_inner() {
321        match inner.as_rule() {
322            Rule::instr_lddw | Rule::instr_llvm_lddw => return 16,
323            _ => {}
324        }
325    }
326    8
327}
328
329/// Determine the byte size of a rodata directive from the parse tree.
330fn rodata_directive_size(pair: &Pair<Rule>) -> u64 {
331    for inner in pair.clone().into_inner() {
332        match inner.as_rule() {
333            Rule::directive_ascii => {
334                for ascii_inner in inner.into_inner() {
335                    if ascii_inner.as_rule() == Rule::string_literal {
336                        for content in ascii_inner.into_inner() {
337                            if content.as_rule() == Rule::string_content {
338                                return content.as_str().len() as u64;
339                            }
340                        }
341                    }
342                }
343            }
344            Rule::directive_byte => {
345                return inner
346                    .into_inner()
347                    .filter(|p| p.as_rule() == Rule::number)
348                    .count() as u64;
349            }
350            Rule::directive_short | Rule::directive_word => {
351                return inner
352                    .into_inner()
353                    .filter(|p| p.as_rule() == Rule::number)
354                    .count() as u64
355                    * 2;
356            }
357            Rule::directive_int | Rule::directive_long => {
358                return inner
359                    .into_inner()
360                    .filter(|p| p.as_rule() == Rule::number)
361                    .count() as u64
362                    * 4;
363            }
364            Rule::directive_quad => {
365                return inner
366                    .into_inner()
367                    .filter(|p| p.as_rule() == Rule::number)
368                    .count() as u64
369                    * 8;
370            }
371            _ => {}
372        }
373    }
374    0
375}
376
377/// Map internal pest rule names to human-readable descriptions for error messages.
378fn rule_display_name(rule: &Rule) -> Option<String> {
379    let name = match rule {
380        // Top-level
381        Rule::program_default | Rule::program_llvm => return None,
382        Rule::statement_default | Rule::statement_llvm => "statement",
383        Rule::label_default | Rule::label_llvm => "label",
384
385        // Directives
386        Rule::directive | Rule::directive_inner => "directive",
387        Rule::directive_globl => ".globl",
388        Rule::directive_extern => ".extern",
389        Rule::directive_equ => ".equ",
390        Rule::directive_section => "section (.text, .rodata)",
391        Rule::directive_ascii => ".ascii",
392        Rule::directive_byte => ".byte",
393        Rule::directive_short => ".short",
394        Rule::directive_word => ".word",
395        Rule::directive_int => ".int",
396        Rule::directive_long => ".long",
397        Rule::directive_quad => ".quad",
398
399        // Instructions
400        Rule::instr_default | Rule::instr_llvm => "instruction",
401        Rule::instr_lddw | Rule::instr_llvm_lddw => "lddw",
402        Rule::instr_call => "call",
403        Rule::instr_callx => "callx",
404        Rule::instr_exit => "exit",
405
406        // Operands
407        Rule::register => "register",
408        Rule::operand => "operand",
409        Rule::number => "number",
410        Rule::symbol => "symbol",
411        Rule::identifier => "identifier",
412        Rule::expression => "expression",
413        Rule::string_literal => "string literal",
414
415        // Memory
416        Rule::memory_ref | Rule::llvm_memory_ref => "memory reference",
417        Rule::jump_target => "jump target",
418
419        // Whitespace / structure
420        Rule::EOI => "end of input",
421        _ => return Some(format!("{:?}", rule)),
422    };
423    Some(name.to_string())
424}
425
426fn process_statement(pair: Pair<Rule>, ctx: &mut ParseContext) {
427    for inner in pair.into_inner() {
428        match inner.as_rule() {
429            Rule::label_default | Rule::label_llvm => {
430                process_label(inner, ctx);
431            }
432            Rule::directive => {
433                process_directive_statement(inner, ctx);
434            }
435            Rule::instr_default | Rule::instr_llvm => {
436                let span = inner.as_span();
437                let span_range = span.start()..span.end();
438                let is_llvm = inner.as_rule() == Rule::instr_llvm;
439
440                match process_instruction(inner, ctx.const_map, ctx.label_offset_map, is_llvm) {
441                    Ok(instruction) => {
442                        if !ctx.rodata_phase {
443                            let size = instruction.get_size();
444                            ctx.ast.nodes.push(ASTNode::Instruction {
445                                instruction,
446                                offset: ctx.text_offset,
447                            });
448                            ctx.text_offset += size;
449                        }
450                    }
451                    Err(e) => ctx.errors.push(e),
452                }
453
454                if ctx.rodata_phase && !ctx.missing_text_directive {
455                    ctx.missing_text_directive = true;
456                    ctx.errors.push(CompileError::MissingTextDirective {
457                        span: span_range,
458                        custom_label: None,
459                    });
460                }
461            }
462            _ => {}
463        }
464    }
465}
466
467fn process_label(pair: Pair<Rule>, ctx: &mut ParseContext) {
468    let is_llvm = pair.as_rule() == Rule::label_llvm;
469    let mut label_opt = None;
470    let mut directive_opt = None;
471    let mut instruction_opt = None;
472
473    for item in pair.into_inner() {
474        match item.as_rule() {
475            Rule::identifier | Rule::numeric_label => match extract_label_from_pair(item) {
476                Ok(label) => label_opt = Some(label),
477                Err(e) => ctx.errors.push(e),
478            },
479            Rule::directive_inner => {
480                directive_opt = Some(item);
481            }
482            Rule::instr_default | Rule::instr_llvm => {
483                instruction_opt = Some(item);
484            }
485            _ => {}
486        }
487    }
488
489    if let Some((label_name, label_span)) = label_opt {
490        // Check for duplicate labels
491        if let Some(original_span) = ctx.label_spans.get(&label_name) {
492            ctx.errors.push(CompileError::DuplicateLabel {
493                label: label_name,
494                span: label_span,
495                original_span: original_span.clone(),
496                custom_label: Some("Label already defined".to_string()),
497            });
498            return;
499        }
500        ctx.label_spans
501            .insert(label_name.clone(), label_span.clone());
502
503        if ctx.rodata_phase {
504            // Record label offset for expression evaluation
505            ctx.label_offset_map.insert(
506                label_name.clone(),
507                (Number::Int(ctx.rodata_offset as i64), Section::Rodata),
508            );
509
510            // Handle rodata label with directive
511            if let Some(dir_pair) = directive_opt {
512                match process_rodata_directive(label_name.clone(), label_span.clone(), dir_pair) {
513                    Ok(rodata) => {
514                        let size = rodata.get_size();
515                        ctx.ast.rodata_nodes.push(ASTNode::ROData {
516                            rodata,
517                            offset: ctx.rodata_offset,
518                        });
519                        ctx.rodata_offset += size;
520                    }
521                    Err(e) => ctx.errors.push(e),
522                }
523            } else if let Some(inst_pair) = instruction_opt {
524                if let Err(e) =
525                    process_instruction(inst_pair, ctx.const_map, ctx.label_offset_map, is_llvm)
526                {
527                    ctx.errors.push(e);
528                }
529                if !ctx.missing_text_directive {
530                    ctx.missing_text_directive = true;
531                    ctx.errors.push(CompileError::MissingTextDirective {
532                        span: label_span,
533                        custom_label: None,
534                    });
535                }
536            } else {
537                // Bare rodata label (no directive on same line) — store it
538                // so the next data directive can pick it up.
539                ctx.pending_rodata_label = Some((label_name, label_span));
540            }
541        } else {
542            // Record label offset for expression evaluation
543            ctx.label_offset_map.insert(
544                label_name.clone(),
545                (Number::Int(ctx.text_offset as i64), Section::Text),
546            );
547
548            ctx.ast.nodes.push(ASTNode::Label {
549                label: Label {
550                    name: label_name,
551                    span: label_span,
552                },
553                offset: ctx.text_offset,
554            });
555
556            if let Some(inst_pair) = instruction_opt {
557                match process_instruction(inst_pair, ctx.const_map, ctx.label_offset_map, is_llvm) {
558                    Ok(instruction) => {
559                        let size = instruction.get_size();
560                        ctx.ast.nodes.push(ASTNode::Instruction {
561                            instruction,
562                            offset: ctx.text_offset,
563                        });
564                        ctx.text_offset += size;
565                    }
566                    Err(e) => ctx.errors.push(e),
567                }
568            }
569        }
570    }
571}
572
573fn process_instruction(
574    pair: Pair<Rule>,
575    const_map: &HashMap<String, Number>,
576    label_offset_map: &HashMap<String, (Number, Section)>,
577    is_llvm: bool,
578) -> Result<Instruction, CompileError> {
579    if is_llvm {
580        llvm::process_instruction(pair, const_map, label_offset_map)
581    } else {
582        default::process_instruction(pair, const_map, label_offset_map)
583    }
584}
585
586fn extract_label_from_pair(
587    pair: Pair<Rule>,
588) -> Result<(String, std::ops::Range<usize>), CompileError> {
589    let span = pair.as_span();
590    Ok((pair.as_str().to_string(), span.start()..span.end()))
591}