use crate::cursor::Cursor;
use crate::diag::DiagBag;
use crate::intern::{Interner, Name};
use crate::lexer::{Dialect, LexConfig, Lexer, LitPool, Punct, TokKind, Token};
use crate::source::{FileId, SourceMap, Span};
#[derive(Clone, Debug)]
pub enum LabelDef {
Named(Name, Span),
Numeric(u32, Span),
}
#[derive(Clone, Debug)]
pub enum Body {
Directive { name: Name, span: Span },
Insn { mnemonic: Name, span: Span },
Assign { name: Name, span: Span },
SetLocation { span: Span },
}
#[derive(Clone, Debug)]
pub struct Statement {
pub labels: Vec<LabelDef>,
pub body: Option<Body>,
pub toks: Vec<Token>,
pub args: usize,
pub span: Span,
}
impl Statement {
pub fn arg_cursor(&self) -> Cursor<'_> {
Cursor::new(&self.toks[self.args.min(self.toks.len())..])
}
pub fn is_empty(&self) -> bool {
self.labels.is_empty() && self.body.is_none()
}
}
pub struct Parser<'a> {
lexer: Lexer<'a>,
peeked: Option<Token>,
}
impl<'a> Parser<'a> {
pub fn new(sm: &'a SourceMap, file: FileId, config: LexConfig) -> Parser<'a> {
Parser {
lexer: Lexer::new(sm, file, config),
peeked: None,
}
}
pub fn config_mut(&mut self) -> &mut LexConfig {
&mut self.lexer.config
}
pub fn dialect(&self) -> Dialect {
self.lexer.config.dialect
}
fn bump(&mut self, interner: &mut Interner, pool: &mut LitPool, diags: &mut DiagBag) -> Token {
match self.peeked.take() {
Some(t) => t,
None => self.lexer.next_token(interner, pool, diags),
}
}
pub fn next_statement(
&mut self,
interner: &mut Interner,
pool: &mut LitPool,
diags: &mut DiagBag,
) -> Option<Statement> {
loop {
let mut toks = Vec::new();
loop {
let t = self.bump(interner, pool, diags);
match t.kind {
TokKind::Eof => {
if toks.is_empty() {
return None;
}
self.peeked = Some(t);
break;
}
TokKind::Eol => break,
_ => toks.push(t),
}
}
if toks.is_empty() {
continue;
}
return Some(self.build(toks, interner, diags));
}
}
fn build(&self, toks: Vec<Token>, interner: &mut Interner, diags: &mut DiagBag) -> Statement {
let span = toks
.first()
.zip(toks.last())
.map(|(a, b)| a.span.to(b.span))
.unwrap_or(Span::DUMMY);
let mut i = 0usize;
let mut labels = Vec::new();
loop {
match (toks.get(i).map(|t| t.kind), toks.get(i + 1).map(|t| t.kind)) {
(Some(TokKind::Ident(n)), Some(TokKind::Punct(Punct::Colon))) => {
labels.push(LabelDef::Named(n, toks[i].span));
i += 2;
if toks.get(i).map(|t| t.kind) == Some(TokKind::Punct(Punct::Colon)) {
i += 1;
}
}
(Some(TokKind::Int(v)), Some(TokKind::Punct(Punct::Colon))) => {
labels.push(LabelDef::Numeric(v as u32, toks[i].span));
i += 2;
}
_ => break,
}
}
let body = self.classify(&toks, &mut i, interner, diags);
Statement {
labels,
body,
toks,
args: i,
span,
}
}
fn classify(
&self,
toks: &[Token],
i: &mut usize,
interner: &mut Interner,
diags: &mut DiagBag,
) -> Option<Body> {
let first = *toks.get(*i)?;
if first.is_punct(Punct::Dot) && toks.get(*i + 1).is_some_and(|t| t.is_punct(Punct::Eq)) {
*i += 2;
return Some(Body::SetLocation { span: first.span });
}
let TokKind::Ident(name) = first.kind else {
diags.error(first.span, "expected a label, directive or instruction");
*i = toks.len();
return None;
};
if toks.get(*i + 1).is_some_and(|t| t.is_punct(Punct::Eq)) {
*i += 2;
return Some(Body::Assign {
name,
span: first.span,
});
}
*i += 1;
let (is_directive, folded) = {
let text = interner.get(name);
let is_directive = match self.lexer.config.dialect {
Dialect::Gas => text.starts_with('.') && text.len() > 1,
Dialect::Nasm => false,
};
let folded = text
.bytes()
.any(|b| b.is_ascii_uppercase())
.then(|| text.to_ascii_lowercase());
(is_directive, folded)
};
let lowered = match folded {
Some(t) => interner.intern(&t),
None => name,
};
if is_directive {
Some(Body::Directive {
name: lowered,
span: first.span,
})
} else {
Some(Body::Insn {
mnemonic: lowered,
span: first.span,
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct H {
sm: SourceMap,
interner: Interner,
pool: LitPool,
diags: DiagBag,
}
fn parse(src: &str) -> (Vec<Statement>, H) {
let mut h = H {
sm: SourceMap::new(),
interner: Interner::new(),
pool: LitPool::new(),
diags: DiagBag::new(),
};
let f = h.sm.add("t.s", src);
let mut out = Vec::new();
{
let mut p = Parser::new(&h.sm, f, LexConfig::for_dialect(Dialect::Gas));
while let Some(s) = p.next_statement(&mut h.interner, &mut h.pool, &mut h.diags) {
out.push(s);
}
}
(out, h)
}
#[test]
fn splits_labels_from_instructions() {
let (st, h) = parse("foo: bar: movq %rax, %rbx\n");
assert_eq!(st.len(), 1);
assert_eq!(st[0].labels.len(), 2);
let Some(Body::Insn { mnemonic, .. }) = st[0].body else {
panic!("{:?}", st[0].body)
};
assert_eq!(h.interner.get(mnemonic), "movq");
assert_eq!(st[0].arg_cursor().rest().len(), 5);
}
#[test]
fn recognises_directives_and_numeric_labels() {
let (st, h) = parse("1: .byte 1, 2\n");
assert!(matches!(st[0].labels[0], LabelDef::Numeric(1, _)));
let Some(Body::Directive { name, .. }) = st[0].body else {
panic!()
};
assert_eq!(h.interner.get(name), ".byte");
}
#[test]
fn assignment_beats_instruction() {
let (st, h) = parse("count = 4 * 2\n");
let Some(Body::Assign { name, .. }) = st[0].body else {
panic!("{:?}", st[0].body)
};
assert_eq!(h.interner.get(name), "count");
assert_eq!(st[0].arg_cursor().rest().len(), 3);
}
#[test]
fn location_counter_assignment() {
let (st, _) = parse(". = . + 16\n");
assert!(matches!(st[0].body, Some(Body::SetLocation { .. })));
}
#[test]
fn semicolons_separate_statements_in_gas() {
let (st, _) = parse("nop; nop; nop\n");
assert_eq!(st.len(), 3);
}
#[test]
fn label_only_lines_and_blank_lines() {
let (st, _) = parse("\n\nfoo:\n\n nop\n");
assert_eq!(st.len(), 2);
assert!(st[0].body.is_none());
assert_eq!(st[0].labels.len(), 1);
}
#[test]
fn mnemonics_fold_case_but_labels_do_not() {
let (st, h) = parse("Foo: NOP\n");
let Some(Body::Insn { mnemonic, .. }) = st[0].body else {
panic!()
};
assert_eq!(h.interner.get(mnemonic), "nop");
let LabelDef::Named(n, _) = st[0].labels[0] else {
panic!()
};
assert_eq!(h.interner.get(n), "Foo");
}
#[test]
fn last_line_without_newline_still_parses() {
let (st, _) = parse("nop");
assert_eq!(st.len(), 1);
}
}