| 1 | //! Two-pass Intel 8051 assembler. |
| 2 | //! |
| 3 | //! Pass one parses every line, picks the opcode for each instruction (which |
| 4 | //! fixes its size) and assigns addresses to labels. Pass two evaluates operand |
| 5 | //! expressions with the complete symbol table and emits bytes. |
| 6 | |
| 7 | mod expr; |
| 8 | pub mod hex; |
| 9 | mod lexer; |
| 10 | mod listing; |
| 11 | |
| 12 | use std::collections::{BTreeMap, HashMap}; |
| 13 | |
| 14 | use crate::isa::{self, OpInfo, Operand}; |
| 15 | use crate::sfr; |
| 16 | use expr::{Env, EvalError, Expr, Parser}; |
| 17 | use lexer::{Tok, Token, lex}; |
| 18 | |
| 19 | #[derive(Debug, Clone)] |
| 20 | pub struct Diagnostic { |
| 21 | /// 0-based source line. |
| 22 | pub line: usize, |
| 23 | /// 0-based column. |
| 24 | pub col: usize, |
| 25 | pub msg: String, |
| 26 | } |
| 27 | |
| 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 29 | pub enum SymKind { |
| 30 | Code, |
| 31 | Number, |
| 32 | Bit, |
| 33 | Data, |
| 34 | Idata, |
| 35 | Xdata, |
| 36 | } |
| 37 | |
| 38 | impl SymKind { |
| 39 | pub fn name(self) -> &'static str { |
| 40 | match self { |
| 41 | SymKind::Code => "CODE", |
| 42 | SymKind::Number => "NUMBER", |
| 43 | SymKind::Bit => "BIT", |
| 44 | SymKind::Data => "DATA", |
| 45 | SymKind::Idata => "IDATA", |
| 46 | SymKind::Xdata => "XDATA", |
| 47 | } |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | #[derive(Debug, Clone)] |
| 52 | pub struct Symbol { |
| 53 | pub value: i64, |
| 54 | pub kind: SymKind, |
| 55 | pub line: usize, |
| 56 | } |
| 57 | |
| 58 | /// What one source line produced. |
| 59 | #[derive(Debug, Clone, Default)] |
| 60 | pub struct LineOut { |
| 61 | /// Location counter at the start of the line, if the line occupies or |
| 62 | /// labels an address. |
| 63 | pub addr: Option<u16>, |
| 64 | pub bytes: Vec<u8>, |
| 65 | } |
| 66 | |
| 67 | #[derive(Debug, Clone, Default)] |
| 68 | pub struct AsmOutput { |
| 69 | pub lines: Vec<LineOut>, |
| 70 | pub symbols: BTreeMap<String, Symbol>, |
| 71 | pub diagnostics: Vec<Diagnostic>, |
| 72 | } |
| 73 | |
| 74 | impl AsmOutput { |
| 75 | pub fn ok(&self) -> bool { |
| 76 | self.diagnostics.is_empty() |
| 77 | } |
| 78 | |
| 79 | /// All emitted code bytes as (address, byte), in source order. |
| 80 | pub fn bytes(&self) -> impl Iterator<Item = (u16, u8)> + '_ { |
| 81 | self.lines.iter().flat_map(|l| { |
| 82 | let base = l.addr.unwrap_or(0); |
| 83 | l.bytes |
| 84 | .iter() |
| 85 | .enumerate() |
| 86 | .map(move |(i, &b)| (base.wrapping_add(i as u16), b)) |
| 87 | }) |
| 88 | } |
| 89 | |
| 90 | /// Source line holding the instruction that starts at each address. |
| 91 | pub fn addr_to_line(&self) -> HashMap<u16, usize> { |
| 92 | let mut m = HashMap::new(); |
| 93 | for (i, l) in self.lines.iter().enumerate() { |
| 94 | if let (Some(a), false) = (l.addr, l.bytes.is_empty()) { |
| 95 | m.entry(a).or_insert(i); |
| 96 | } |
| 97 | } |
| 98 | m |
| 99 | } |
| 100 | |
| 101 | pub fn code_size(&self) -> usize { |
| 102 | self.lines.iter().map(|l| l.bytes.len()).sum() |
| 103 | } |
| 104 | |
| 105 | pub fn listing(&self, source: &str) -> String { |
| 106 | listing::render(self, source) |
| 107 | } |
| 108 | |
| 109 | pub fn intel_hex(&self) -> String { |
| 110 | hex::write(self.bytes()) |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | /// Operand as written in the source, before matching it against the ISA. |
| 115 | #[derive(Debug, Clone)] |
| 116 | enum Syn { |
| 117 | A, |
| 118 | AB, |
| 119 | C, |
| 120 | Dptr, |
| 121 | Reg(u8), |
| 122 | AtReg(u8), |
| 123 | AtDptr, |
| 124 | AtAPlusDptr, |
| 125 | AtAPlusPc, |
| 126 | Imm(Expr), |
| 127 | NotBit(Expr), |
| 128 | Expr(Expr), |
| 129 | } |
| 130 | |
| 131 | #[derive(Debug, Clone)] |
| 132 | enum DbItem { |
| 133 | Bytes(Vec<u8>), |
| 134 | Expr(Expr, usize), |
| 135 | } |
| 136 | |
| 137 | #[derive(Debug, Clone)] |
| 138 | enum Stmt { |
| 139 | Empty, |
| 140 | Instr { |
| 141 | info: &'static OpInfo, |
| 142 | args: Vec<(Expr, usize)>, |
| 143 | }, |
| 144 | Org(Expr, usize), |
| 145 | Db(Vec<DbItem>), |
| 146 | Dw(Vec<(Expr, usize)>), |
| 147 | Ds(Expr, usize), |
| 148 | Equ { |
| 149 | name: String, |
| 150 | kind: SymKind, |
| 151 | expr: Expr, |
| 152 | col: usize, |
| 153 | redefinable: bool, |
| 154 | }, |
| 155 | End, |
| 156 | } |
| 157 | |
| 158 | impl Stmt { |
| 159 | fn size(&self) -> u16 { |
| 160 | match self { |
| 161 | Stmt::Instr { info, .. } => info.size as u16, |
| 162 | Stmt::Db(items) => items |
| 163 | .iter() |
| 164 | .map(|i| match i { |
| 165 | DbItem::Bytes(b) => b.len() as u16, |
| 166 | DbItem::Expr(..) => 1, |
| 167 | }) |
| 168 | .sum(), |
| 169 | Stmt::Dw(items) => 2 * items.len() as u16, |
| 170 | _ => 0, |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | struct ParsedLine { |
| 176 | label: Option<(String, usize)>, |
| 177 | stmt: Stmt, |
| 178 | } |
| 179 | |
| 180 | type PResult<T> = Result<T, (usize, String)>; |
| 181 | |
| 182 | fn split_commas(toks: &[Token]) -> Vec<&[Token]> { |
| 183 | if toks.is_empty() { |
| 184 | return Vec::new(); |
| 185 | } |
| 186 | let mut parts = Vec::new(); |
| 187 | let mut depth = 0; |
| 188 | let mut start = 0; |
| 189 | for (i, t) in toks.iter().enumerate() { |
| 190 | match t.tok { |
| 191 | Tok::Punct('(') => depth += 1, |
| 192 | Tok::Punct(')') => depth -= 1, |
| 193 | Tok::Punct(',') if depth == 0 => { |
| 194 | parts.push(&toks[start..i]); |
| 195 | start = i + 1; |
| 196 | } |
| 197 | _ => {} |
| 198 | } |
| 199 | } |
| 200 | parts.push(&toks[start..]); |
| 201 | parts |
| 202 | } |
| 203 | |
| 204 | fn parse_expr(toks: &[Token], fallback_col: usize) -> PResult<Expr> { |
| 205 | if toks.is_empty() { |
| 206 | return Err((fallback_col, "missing operand".into())); |
| 207 | } |
| 208 | Parser::parse_all(toks) |
| 209 | } |
| 210 | |
| 211 | fn reg_num(s: &str) -> Option<u8> { |
| 212 | let n = s.strip_prefix('R')?; |
| 213 | if n.len() == 1 { |
| 214 | n.parse().ok().filter(|&n: &u8| n < 8) |
| 215 | } else { |
| 216 | None |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | fn parse_operand(toks: &[Token], col: usize) -> PResult<Syn> { |
| 221 | let Some(first) = toks.first() else { |
| 222 | return Err((col, "missing operand".into())); |
| 223 | }; |
| 224 | let words: Vec<Option<&str>> = toks.iter().map(|t| t.ident()).collect(); |
| 225 | if toks.len() == 1 |
| 226 | && let Some(w) = first.ident() |
| 227 | { |
| 228 | match w { |
| 229 | "A" => return Ok(Syn::A), |
| 230 | "AB" => return Ok(Syn::AB), |
| 231 | "C" => return Ok(Syn::C), |
| 232 | "DPTR" => return Ok(Syn::Dptr), |
| 233 | _ => {} |
| 234 | } |
| 235 | if let Some(n) = reg_num(w) { |
| 236 | return Ok(Syn::Reg(n)); |
| 237 | } |
| 238 | } |
| 239 | match &first.tok { |
| 240 | Tok::Punct('@') => match (&words[1..], toks.get(2)) { |
| 241 | ([Some("R0")], _) => Ok(Syn::AtReg(0)), |
| 242 | ([Some("R1")], _) => Ok(Syn::AtReg(1)), |
| 243 | ([Some("DPTR")], _) => Ok(Syn::AtDptr), |
| 244 | ([Some("A"), None, Some("DPTR")], Some(p)) if p.is_punct('+') => Ok(Syn::AtAPlusDptr), |
| 245 | ([Some("A"), None, Some("PC")], Some(p)) if p.is_punct('+') => Ok(Syn::AtAPlusPc), |
| 246 | _ => Err(( |
| 247 | first.col, |
| 248 | "invalid indirect operand (expected @R0, @R1, @DPTR, @A+DPTR or @A+PC)".into(), |
| 249 | )), |
| 250 | }, |
| 251 | Tok::Punct('#') => Ok(Syn::Imm(parse_expr(&toks[1..], first.col + 1)?)), |
| 252 | Tok::Punct('/') => Ok(Syn::NotBit(parse_expr(&toks[1..], first.col + 1)?)), |
| 253 | _ => Ok(Syn::Expr(parse_expr(toks, col)?)), |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | fn operand_matches(op: Operand, syn: &Syn) -> bool { |
| 258 | match (op, syn) { |
| 259 | (Operand::A, Syn::A) |
| 260 | | (Operand::AB, Syn::AB) |
| 261 | | (Operand::C, Syn::C) |
| 262 | | (Operand::Dptr, Syn::Dptr) |
| 263 | | (Operand::AtDptr, Syn::AtDptr) |
| 264 | | (Operand::AtAPlusDptr, Syn::AtAPlusDptr) |
| 265 | | (Operand::AtAPlusPc, Syn::AtAPlusPc) => true, |
| 266 | (Operand::Reg(a), Syn::Reg(b)) | (Operand::AtReg(a), Syn::AtReg(b)) => a == *b, |
| 267 | (Operand::Imm8 | Operand::Imm16, Syn::Imm(_)) => true, |
| 268 | (Operand::NotBit, Syn::NotBit(_)) => true, |
| 269 | ( |
| 270 | Operand::Direct | Operand::Bit | Operand::Addr11 | Operand::Addr16 | Operand::Rel, |
| 271 | Syn::Expr(_), |
| 272 | ) => true, |
| 273 | _ => false, |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | fn parse_instruction(mnemonic: &str, col: usize, rest: &[Token]) -> PResult<Stmt> { |
| 278 | let mut syns = Vec::new(); |
| 279 | let mut cols = Vec::new(); |
| 280 | for part in split_commas(rest) { |
| 281 | let c = part.first().map_or(col, |t| t.col); |
| 282 | syns.push(parse_operand(part, c)?); |
| 283 | cols.push(c); |
| 284 | } |
| 285 | // Generic JMP/CALL always assemble to the long forms, so sizes never |
| 286 | // depend on label values. |
| 287 | let lookup = match (mnemonic, syns.as_slice()) { |
| 288 | ("JMP", [Syn::Expr(_)]) => "LJMP", |
| 289 | ("CALL", _) => "LCALL", |
| 290 | (m, _) => m, |
| 291 | }; |
| 292 | let info = isa::by_mnemonic(lookup) |
| 293 | .find(|i| { |
| 294 | i.operands.len() == syns.len() |
| 295 | && i.operands |
| 296 | .iter() |
| 297 | .zip(&syns) |
| 298 | .all(|(&o, s)| operand_matches(o, s)) |
| 299 | }) |
| 300 | .ok_or_else(|| (col, format!("invalid operands for {mnemonic}")))?; |
| 301 | let args = syns |
| 302 | .into_iter() |
| 303 | .zip(cols) |
| 304 | .filter_map(|(s, c)| match s { |
| 305 | Syn::Imm(e) | Syn::NotBit(e) | Syn::Expr(e) => Some((e, c)), |
| 306 | _ => None, |
| 307 | }) |
| 308 | .collect(); |
| 309 | Ok(Stmt::Instr { info, args }) |
| 310 | } |
| 311 | |
| 312 | fn parse_line(toks: &[Token]) -> PResult<ParsedLine> { |
| 313 | let mut toks = toks; |
| 314 | let mut label = None; |
| 315 | |
| 316 | // `name EQU expr` and friends. |
| 317 | if let (Some(name), Some(kw)) = ( |
| 318 | toks.first().and_then(Token::ident), |
| 319 | toks.get(1).and_then(Token::ident), |
| 320 | ) { |
| 321 | let kind = match kw { |
| 322 | "EQU" | "SET" => Some(SymKind::Number), |
| 323 | "BIT" => Some(SymKind::Bit), |
| 324 | "DATA" => Some(SymKind::Data), |
| 325 | "IDATA" => Some(SymKind::Idata), |
| 326 | "XDATA" => Some(SymKind::Xdata), |
| 327 | "CODE" => Some(SymKind::Code), |
| 328 | _ => None, |
| 329 | }; |
| 330 | if let Some(kind) = kind { |
| 331 | let expr = parse_expr(&toks[2..], toks[1].col + kw.len())?; |
| 332 | return Ok(ParsedLine { |
| 333 | label: None, |
| 334 | stmt: Stmt::Equ { |
| 335 | name: name.to_string(), |
| 336 | kind, |
| 337 | expr, |
| 338 | col: toks[0].col, |
| 339 | redefinable: kw == "SET", |
| 340 | }, |
| 341 | }); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | if let (Some(name), Some(colon)) = (toks.first().and_then(Token::ident), toks.get(1)) |
| 346 | && colon.is_punct(':') |
| 347 | { |
| 348 | label = Some((name.to_string(), toks[0].col)); |
| 349 | toks = &toks[2..]; |
| 350 | } |
| 351 | |
| 352 | // Allow `.org`, `.db`, ... spellings. |
| 353 | if toks.first().is_some_and(|t| t.is_punct('.')) { |
| 354 | toks = &toks[1..]; |
| 355 | } |
| 356 | |
| 357 | let Some(first) = toks.first() else { |
| 358 | return Ok(ParsedLine { |
| 359 | label, |
| 360 | stmt: Stmt::Empty, |
| 361 | }); |
| 362 | }; |
| 363 | let Some(word) = first.ident() else { |
| 364 | return Err((first.col, "expected instruction or directive".into())); |
| 365 | }; |
| 366 | let rest = &toks[1..]; |
| 367 | let end_col = first.col + word.len(); |
| 368 | |
| 369 | let stmt = match word { |
| 370 | "ORG" => Stmt::Org(parse_expr(rest, end_col)?, first.col), |
| 371 | "CSEG" | "XSEG" | "DSEG" if rest.first().and_then(Token::ident) == Some("AT") => { |
| 372 | Stmt::Org(parse_expr(&rest[1..], end_col)?, first.col) |
| 373 | } |
| 374 | "DB" | "BYTE" => { |
| 375 | let mut items = Vec::new(); |
| 376 | for part in split_commas(rest) { |
| 377 | match part { |
| 378 | [ |
| 379 | Token { |
| 380 | tok: Tok::Str(b), .. |
| 381 | }, |
| 382 | ] if b.len() != 1 => items.push(DbItem::Bytes(b.clone())), |
| 383 | _ => { |
| 384 | let c = part.first().map_or(end_col, |t| t.col); |
| 385 | items.push(DbItem::Expr(parse_expr(part, c)?, c)); |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | if items.is_empty() { |
| 390 | return Err((end_col, "DB needs at least one value".into())); |
| 391 | } |
| 392 | Stmt::Db(items) |
| 393 | } |
| 394 | "DW" | "WORD" => { |
| 395 | let mut items = Vec::new(); |
| 396 | for part in split_commas(rest) { |
| 397 | let c = part.first().map_or(end_col, |t| t.col); |
| 398 | items.push((parse_expr(part, c)?, c)); |
| 399 | } |
| 400 | if items.is_empty() { |
| 401 | return Err((end_col, "DW needs at least one value".into())); |
| 402 | } |
| 403 | Stmt::Dw(items) |
| 404 | } |
| 405 | "DS" => Stmt::Ds(parse_expr(rest, end_col)?, first.col), |
| 406 | "END" => Stmt::End, |
| 407 | // Accepted for compatibility, no effect in a single-module assembler. |
| 408 | "CSEG" | "USING" | "NAME" => Stmt::Empty, |
| 409 | m if isa::is_mnemonic(m) => parse_instruction(m, first.col, rest)?, |
| 410 | _ if rest.first().is_some_and(|t| t.is_punct(':')) => { |
| 411 | return Err(( |
| 412 | first.col, |
| 413 | "a label must be the first thing on a line".into(), |
| 414 | )); |
| 415 | } |
| 416 | _ => { |
| 417 | return Err(( |
| 418 | first.col, |
| 419 | format!("unknown instruction or directive '{word}'"), |
| 420 | )); |
| 421 | } |
| 422 | }; |
| 423 | Ok(ParsedLine { label, stmt }) |
| 424 | } |
| 425 | |
| 426 | struct Symbols { |
| 427 | user: HashMap<String, Symbol>, |
| 428 | here: i64, |
| 429 | } |
| 430 | |
| 431 | impl Env for Symbols { |
| 432 | fn lookup(&self, name: &str) -> Option<i64> { |
| 433 | if let Some(s) = self.user.get(name) { |
| 434 | return Some(s.value); |
| 435 | } |
| 436 | sfr::SFRS |
| 437 | .iter() |
| 438 | .chain(sfr::BITS) |
| 439 | .find(|(n, _)| *n == name) |
| 440 | .map(|&(_, v)| v as i64) |
| 441 | } |
| 442 | |
| 443 | fn here(&self) -> i64 { |
| 444 | self.here |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | fn is_predefined(name: &str) -> bool { |
| 449 | sfr::SFRS.iter().chain(sfr::BITS).any(|(n, _)| *n == name) |
| 450 | } |
| 451 | |
| 452 | fn is_reserved(name: &str) -> bool { |
| 453 | matches!(name, "A" | "AB" | "C" | "DPTR" | "PC") |
| 454 | || reg_num(name).is_some() |
| 455 | || isa::is_mnemonic(name) |
| 456 | } |
| 457 | |
| 458 | pub fn assemble(source: &str) -> AsmOutput { |
| 459 | let mut diags: Vec<Diagnostic> = Vec::new(); |
| 460 | let mut syms = Symbols { |
| 461 | user: HashMap::new(), |
| 462 | here: 0, |
| 463 | }; |
| 464 | let mut parsed: Vec<Option<ParsedLine>> = Vec::new(); |
| 465 | let mut addrs: Vec<i64> = Vec::new(); |
| 466 | let mut pending_equ: Vec<usize> = Vec::new(); |
| 467 | |
| 468 | let err = |diags: &mut Vec<Diagnostic>, line: usize, col: usize, msg: String| { |
| 469 | diags.push(Diagnostic { line, col, msg }); |
| 470 | }; |
| 471 | |
| 472 | // ---- Pass 1: parse, size, assign label addresses. ---- |
| 473 | let mut loc: i64 = 0; |
| 474 | let mut ended = false; |
| 475 | for (ln, text) in source.lines().enumerate() { |
| 476 | addrs.push(loc); |
| 477 | if ended { |
| 478 | parsed.push(None); |
| 479 | continue; |
| 480 | } |
| 481 | let line = match lex(text).and_then(|t| parse_line(&t)) { |
| 482 | Ok(l) => l, |
| 483 | Err((col, msg)) => { |
| 484 | err(&mut diags, ln, col, msg); |
| 485 | parsed.push(None); |
| 486 | continue; |
| 487 | } |
| 488 | }; |
| 489 | |
| 490 | if let Some((name, col)) = &line.label { |
| 491 | define( |
| 492 | &mut syms, |
| 493 | &mut diags, |
| 494 | name, |
| 495 | *col, |
| 496 | ln, |
| 497 | loc, |
| 498 | SymKind::Code, |
| 499 | false, |
| 500 | ); |
| 501 | } |
| 502 | |
| 503 | syms.here = loc; |
| 504 | match &line.stmt { |
| 505 | Stmt::Org(e, col) | Stmt::Ds(e, col) => match e.eval(&syms) { |
| 506 | Ok(v) if matches!(line.stmt, Stmt::Org(..)) => { |
| 507 | if !(0..=0xFFFF).contains(&v) { |
| 508 | err( |
| 509 | &mut diags, |
| 510 | ln, |
| 511 | *col, |
| 512 | format!("ORG address {v} out of range"), |
| 513 | ); |
| 514 | } else { |
| 515 | loc = v; |
| 516 | addrs[ln] = loc; |
| 517 | } |
| 518 | } |
| 519 | Ok(v) => { |
| 520 | if v < 0 { |
| 521 | err(&mut diags, ln, *col, "DS size must not be negative".into()); |
| 522 | } else { |
| 523 | loc += v; |
| 524 | } |
| 525 | } |
| 526 | Err(EvalError::Undefined(s, c)) => err( |
| 527 | &mut diags, |
| 528 | ln, |
| 529 | c, |
| 530 | format!("'{s}' must be defined before it is used here"), |
| 531 | ), |
| 532 | Err(EvalError::Other(m)) => err(&mut diags, ln, *col, m), |
| 533 | }, |
| 534 | Stmt::Equ { |
| 535 | name, |
| 536 | kind, |
| 537 | expr, |
| 538 | col, |
| 539 | redefinable, |
| 540 | } => match expr.eval(&syms) { |
| 541 | Ok(v) => define( |
| 542 | &mut syms, |
| 543 | &mut diags, |
| 544 | name, |
| 545 | *col, |
| 546 | ln, |
| 547 | v, |
| 548 | *kind, |
| 549 | *redefinable, |
| 550 | ), |
| 551 | Err(EvalError::Undefined(..)) => pending_equ.push(ln), |
| 552 | Err(EvalError::Other(m)) => err(&mut diags, ln, *col, m), |
| 553 | }, |
| 554 | Stmt::End => ended = true, |
| 555 | s => loc += s.size() as i64, |
| 556 | } |
| 557 | if loc > 0x10000 { |
| 558 | err(&mut diags, ln, 0, "code exceeds 64K address space".into()); |
| 559 | loc &= 0xFFFF; |
| 560 | } |
| 561 | parsed.push(Some(line)); |
| 562 | } |
| 563 | |
| 564 | // Resolve EQUs that referred to symbols defined later. |
| 565 | loop { |
| 566 | let before = pending_equ.len(); |
| 567 | pending_equ.retain(|&ln| { |
| 568 | let Some(ParsedLine { |
| 569 | stmt: |
| 570 | Stmt::Equ { |
| 571 | name, |
| 572 | kind, |
| 573 | expr, |
| 574 | col, |
| 575 | redefinable, |
| 576 | }, |
| 577 | .. |
| 578 | }) = &parsed[ln] |
| 579 | else { |
| 580 | return false; |
| 581 | }; |
| 582 | syms.here = addrs[ln]; |
| 583 | match expr.eval(&syms) { |
| 584 | Ok(v) => { |
| 585 | define( |
| 586 | &mut syms, |
| 587 | &mut diags, |
| 588 | name, |
| 589 | *col, |
| 590 | ln, |
| 591 | v, |
| 592 | *kind, |
| 593 | *redefinable, |
| 594 | ); |
| 595 | false |
| 596 | } |
| 597 | Err(_) => true, |
| 598 | } |
| 599 | }); |
| 600 | if pending_equ.len() == before { |
| 601 | break; |
| 602 | } |
| 603 | } |
| 604 | for ln in pending_equ { |
| 605 | if let Some(ParsedLine { |
| 606 | stmt: Stmt::Equ { expr, col, .. }, |
| 607 | .. |
| 608 | }) = &parsed[ln] |
| 609 | { |
| 610 | syms.here = addrs[ln]; |
| 611 | match expr.eval(&syms) { |
| 612 | Err(EvalError::Undefined(s, c)) => { |
| 613 | err(&mut diags, ln, c, format!("undefined symbol '{s}'")) |
| 614 | } |
| 615 | Err(EvalError::Other(m)) => err(&mut diags, ln, *col, m), |
| 616 | Ok(_) => {} |
| 617 | } |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | // ---- Pass 2: emit bytes. ---- |
| 622 | let mut lines = Vec::with_capacity(parsed.len()); |
| 623 | let mut used = vec![false; 0x10000]; |
| 624 | for (ln, p) in parsed.iter().enumerate() { |
| 625 | let mut out = LineOut::default(); |
| 626 | let Some(p) = p else { |
| 627 | lines.push(out); |
| 628 | continue; |
| 629 | }; |
| 630 | let pc = addrs[ln]; |
| 631 | syms.here = pc; |
| 632 | let has_addr = p.label.is_some() |
| 633 | || matches!( |
| 634 | p.stmt, |
| 635 | Stmt::Instr { .. } | Stmt::Db(_) | Stmt::Dw(_) | Stmt::Ds(..) | Stmt::Org(..) |
| 636 | ); |
| 637 | if has_addr { |
| 638 | out.addr = Some(pc as u16); |
| 639 | } |
| 640 | |
| 641 | let eval = |e: &Expr, col: usize, diags: &mut Vec<Diagnostic>| -> Option<i64> { |
| 642 | match e.eval(&syms) { |
| 643 | Ok(v) => Some(v), |
| 644 | Err(EvalError::Undefined(s, c)) => { |
| 645 | err(diags, ln, c, format!("undefined symbol '{s}'")); |
| 646 | None |
| 647 | } |
| 648 | Err(EvalError::Other(m)) => { |
| 649 | err(diags, ln, col, m); |
| 650 | None |
| 651 | } |
| 652 | } |
| 653 | }; |
| 654 | |
| 655 | match &p.stmt { |
| 656 | Stmt::Instr { info, args } => { |
| 657 | let mut bytes = vec![info.opcode]; |
| 658 | let kinds = info.operands.iter().filter(|o| o.len() > 0); |
| 659 | let mut failed = false; |
| 660 | for (&kind, (e, col)) in kinds.zip(args) { |
| 661 | let Some(v) = eval(e, *col, &mut diags) else { |
| 662 | failed = true; |
| 663 | continue; |
| 664 | }; |
| 665 | if let Err(m) = encode_operand(kind, v, pc, info.size, &mut bytes) { |
| 666 | err(&mut diags, ln, *col, m); |
| 667 | failed = true; |
| 668 | } |
| 669 | } |
| 670 | if !failed { |
| 671 | if info.opcode == 0x85 { |
| 672 | // MOV dest,src is encoded with the source byte first. |
| 673 | bytes.swap(1, 2); |
| 674 | } |
| 675 | out.bytes = bytes; |
| 676 | } |
| 677 | } |
| 678 | Stmt::Db(items) => { |
| 679 | for item in items { |
| 680 | match item { |
| 681 | DbItem::Bytes(b) => out.bytes.extend_from_slice(b), |
| 682 | DbItem::Expr(e, col) => { |
| 683 | let v = eval(e, *col, &mut diags).unwrap_or(0); |
| 684 | if !(-128..=255).contains(&v) { |
| 685 | err( |
| 686 | &mut diags, |
| 687 | ln, |
| 688 | *col, |
| 689 | format!("value {v} does not fit in a byte"), |
| 690 | ); |
| 691 | } |
| 692 | out.bytes.push(v as u8); |
| 693 | } |
| 694 | } |
| 695 | } |
| 696 | } |
| 697 | Stmt::Dw(items) => { |
| 698 | for (e, col) in items { |
| 699 | let v = eval(e, *col, &mut diags).unwrap_or(0); |
| 700 | if !(-32768..=0xFFFF).contains(&v) { |
| 701 | err( |
| 702 | &mut diags, |
| 703 | ln, |
| 704 | *col, |
| 705 | format!("value {v} does not fit in a word"), |
| 706 | ); |
| 707 | } |
| 708 | out.bytes.push((v >> 8) as u8); |
| 709 | out.bytes.push(v as u8); |
| 710 | } |
| 711 | } |
| 712 | _ => {} |
| 713 | } |
| 714 | |
| 715 | for i in 0..out.bytes.len() { |
| 716 | let a = (pc as usize + i) & 0xFFFF; |
| 717 | if used[a] { |
| 718 | err( |
| 719 | &mut diags, |
| 720 | ln, |
| 721 | 0, |
| 722 | format!("code overlaps previously emitted code at {a:04X}h"), |
| 723 | ); |
| 724 | break; |
| 725 | } |
| 726 | used[a] = true; |
| 727 | } |
| 728 | lines.push(out); |
| 729 | } |
| 730 | |
| 731 | diags.sort_by_key(|d| (d.line, d.col)); |
| 732 | let symbols = syms.user.into_iter().collect(); |
| 733 | AsmOutput { |
| 734 | lines, |
| 735 | symbols, |
| 736 | diagnostics: diags, |
| 737 | } |
| 738 | } |
| 739 | |
| 740 | #[allow(clippy::too_many_arguments)] |
| 741 | fn define( |
| 742 | syms: &mut Symbols, |
| 743 | diags: &mut Vec<Diagnostic>, |
| 744 | name: &str, |
| 745 | col: usize, |
| 746 | line: usize, |
| 747 | value: i64, |
| 748 | kind: SymKind, |
| 749 | redefinable: bool, |
| 750 | ) { |
| 751 | let msg = if is_reserved(name) { |
| 752 | Some(format!("'{name}' is a reserved word")) |
| 753 | } else if is_predefined(name) { |
| 754 | Some(format!("'{name}' is a predefined SFR or bit name")) |
| 755 | } else { |
| 756 | syms.user |
| 757 | .get(name) |
| 758 | .filter(|_| !redefinable) |
| 759 | .map(|prev| format!("'{name}' already defined on line {}", prev.line + 1)) |
| 760 | }; |
| 761 | match msg { |
| 762 | Some(msg) => diags.push(Diagnostic { line, col, msg }), |
| 763 | None => { |
| 764 | syms.user |
| 765 | .insert(name.to_string(), Symbol { value, kind, line }); |
| 766 | } |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | fn encode_operand( |
| 771 | kind: Operand, |
| 772 | v: i64, |
| 773 | pc: i64, |
| 774 | size: u8, |
| 775 | bytes: &mut Vec<u8>, |
| 776 | ) -> Result<(), String> { |
| 777 | let range = |lo: i64, hi: i64, what: &str| { |
| 778 | if (lo..=hi).contains(&v) { |
| 779 | Ok(()) |
| 780 | } else { |
| 781 | Err(format!("{what} {v} out of range")) |
| 782 | } |
| 783 | }; |
| 784 | match kind { |
| 785 | Operand::Imm8 => { |
| 786 | range(-128, 255, "immediate value")?; |
| 787 | bytes.push(v as u8); |
| 788 | } |
| 789 | Operand::Imm16 => { |
| 790 | range(-32768, 0xFFFF, "immediate value")?; |
| 791 | bytes.extend_from_slice(&(v as u16).to_be_bytes()); |
| 792 | } |
| 793 | Operand::Direct => { |
| 794 | range(0, 255, "direct address")?; |
| 795 | bytes.push(v as u8); |
| 796 | } |
| 797 | Operand::Bit | Operand::NotBit => { |
| 798 | range(0, 255, "bit address")?; |
| 799 | bytes.push(v as u8); |
| 800 | } |
| 801 | Operand::Addr16 => { |
| 802 | range(0, 0xFFFF, "address")?; |
| 803 | bytes.extend_from_slice(&(v as u16).to_be_bytes()); |
| 804 | } |
| 805 | Operand::Addr11 => { |
| 806 | range(0, 0xFFFF, "address")?; |
| 807 | let next = pc + size as i64; |
| 808 | if (next & 0xF800) != (v & 0xF800) { |
| 809 | return Err(format!("target {v:04X}h is outside the current 2K page")); |
| 810 | } |
| 811 | bytes[0] |= (((v >> 8) & 7) as u8) << 5; |
| 812 | bytes.push(v as u8); |
| 813 | } |
| 814 | Operand::Rel => { |
| 815 | let off = v - (pc + size as i64); |
| 816 | if !(-128..=127).contains(&off) { |
| 817 | return Err(format!( |
| 818 | "jump target out of range (offset {off}, must be -128..127)" |
| 819 | )); |
| 820 | } |
| 821 | bytes.push(off as u8); |
| 822 | } |
| 823 | _ => unreachable!("operand {kind:?} carries no value"), |
| 824 | } |
| 825 | Ok(()) |
| 826 | } |
| 827 | |
| 828 | #[cfg(test)] |
| 829 | mod tests { |
| 830 | use super::*; |
| 831 | |
| 832 | fn bytes(src: &str) -> Vec<u8> { |
| 833 | let out = assemble(src); |
| 834 | assert!(out.ok(), "{:?}", out.diagnostics); |
| 835 | out.bytes().map(|(_, b)| b).collect() |
| 836 | } |
| 837 | |
| 838 | fn one(line: &str) -> Vec<u8> { |
| 839 | bytes(line) |
| 840 | } |
| 841 | |
| 842 | #[test] |
| 843 | fn encodes_operand_forms() { |
| 844 | assert_eq!(one("MOV A,#55h"), [0x74, 0x55]); |
| 845 | assert_eq!(one("mov a, r3"), [0xEB]); |
| 846 | assert_eq!(one("MOV A,@R1"), [0xE7]); |
| 847 | assert_eq!(one("MOV A,30h"), [0xE5, 0x30]); |
| 848 | assert_eq!(one("MOV A,B"), [0xE5, 0xF0]); |
| 849 | assert_eq!(one("MOV 30h,#1"), [0x75, 0x30, 0x01]); |
| 850 | assert_eq!(one("MOV 30h,40h"), [0x85, 0x40, 0x30]); |
| 851 | assert_eq!(one("MOV R7,#0"), [0x7F, 0x00]); |
| 852 | assert_eq!(one("MOV @R0,A"), [0xF6]); |
| 853 | assert_eq!(one("MOV DPTR,#1234h"), [0x90, 0x12, 0x34]); |
| 854 | assert_eq!(one("MOV C,P1.3"), [0xA2, 0x93]); |
| 855 | assert_eq!(one("MOV P1.3,C"), [0x92, 0x93]); |
| 856 | assert_eq!(one("SETB TR0"), [0xD2, 0x8C]); |
| 857 | assert_eq!(one("CLR C"), [0xC3]); |
| 858 | assert_eq!(one("ANL C,/ACC.7"), [0xB0, 0xE7]); |
| 859 | assert_eq!(one("CPL 20h.0"), [0xB2, 0x00]); |
| 860 | assert_eq!(one("MOVC A,@A+DPTR"), [0x93]); |
| 861 | assert_eq!(one("MOVX @DPTR,A"), [0xF0]); |
| 862 | assert_eq!(one("JMP @A+DPTR"), [0x73]); |
| 863 | assert_eq!(one("MUL AB"), [0xA4]); |
| 864 | assert_eq!(one("PUSH ACC"), [0xC0, 0xE0]); |
| 865 | assert_eq!(one("ORL 20h,#0Fh"), [0x43, 0x20, 0x0F]); |
| 866 | assert_eq!(one("XCHD A,@R0"), [0xD6]); |
| 867 | assert_eq!(one("MOV A,#-1"), [0x74, 0xFF]); |
| 868 | assert_eq!(one("MOV A,#HIGH(1234h)"), [0x74, 0x12]); |
| 869 | } |
| 870 | |
| 871 | #[test] |
| 872 | fn jumps_and_labels() { |
| 873 | let src = " |
| 874 | start: SJMP start |
| 875 | DJNZ R2, start |
| 876 | CJNE A,#3,fwd |
| 877 | LJMP fwd |
| 878 | CALL fwd |
| 879 | fwd: AJMP start |
| 880 | SJMP $ |
| 881 | "; |
| 882 | assert_eq!( |
| 883 | bytes(src), |
| 884 | [ |
| 885 | 0x80, 0xFE, // SJMP start |
| 886 | 0xDA, 0xFC, // DJNZ R2,start |
| 887 | 0xB4, 0x03, 0x06, // CJNE -> fwd at 0x0D |
| 888 | 0x02, 0x00, 0x0D, // LJMP |
| 889 | 0x12, 0x00, 0x0D, // LCALL |
| 890 | 0x01, 0x00, // AJMP 0 |
| 891 | 0x80, 0xFE, |
| 892 | ] |
| 893 | ); |
| 894 | } |
| 895 | |
| 896 | #[test] |
| 897 | fn directives() { |
| 898 | let src = " |
| 899 | COUNT EQU 3 |
| 900 | LED BIT P1.0 |
| 901 | ORG 100h |
| 902 | MOV R0,#COUNT |
| 903 | SETB LED |
| 904 | tbl: DB 1, 'AB', COUNT*2 |
| 905 | DW tbl |
| 906 | DS 2 |
| 907 | after: NOP |
| 908 | END |
| 909 | garbage that is ignored |
| 910 | "; |
| 911 | let out = assemble(src); |
| 912 | assert!(out.ok(), "{:?}", out.diagnostics); |
| 913 | let v: Vec<_> = out.bytes().collect(); |
| 914 | assert_eq!(v[0], (0x100, 0x78)); |
| 915 | let b: Vec<u8> = v.iter().map(|x| x.1).collect(); |
| 916 | assert_eq!(b, [0x78, 3, 0xD2, 0x90, 1, b'A', b'B', 6, 0x01, 0x04, 0x00]); |
| 917 | assert_eq!(out.symbols["AFTER"].value, 0x10C); |
| 918 | } |
| 919 | |
| 920 | #[test] |
| 921 | fn forward_equ() { |
| 922 | let out = assemble("X EQU Y+1\nY EQU 2\nMOV A,#X"); |
| 923 | assert!(out.ok(), "{:?}", out.diagnostics); |
| 924 | assert_eq!(out.bytes().map(|b| b.1).collect::<Vec<_>>(), [0x74, 3]); |
| 925 | } |
| 926 | |
| 927 | #[test] |
| 928 | fn reports_errors_with_lines() { |
| 929 | let out = assemble("NOP\nMOV A,@R2\nSJMP nowhere\nFOO\nMOV A,#300\n"); |
| 930 | let lines: Vec<_> = out.diagnostics.iter().map(|d| d.line).collect(); |
| 931 | assert_eq!(lines, [1, 2, 3, 4]); |
| 932 | } |
| 933 | |
| 934 | #[test] |
| 935 | fn relative_range_checked() { |
| 936 | let out = assemble("SJMP far\nORG 200h\nfar: NOP"); |
| 937 | assert_eq!(out.diagnostics.len(), 1); |
| 938 | assert!(out.diagnostics[0].msg.contains("out of range")); |
| 939 | } |
| 940 | } |