Krolyxon
Commit 815d036

Branches

Tags

Refs →

bench51cratesbench51-coresrcasmmod.rs

940 lines · 27.4 KB · Rust

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
7mod expr;
8pub mod hex;
9mod lexer;
10mod listing;
11
12use std::collections::{BTreeMap, HashMap};
13
14use crate::isa::{self, OpInfo, Operand};
15use crate::sfr;
16use expr::{Env, EvalError, Expr, Parser};
17use lexer::{Tok, Token, lex};
18
19#[derive(Debug, Clone)]
20pub 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)]
29pub enum SymKind {
30 Code,
31 Number,
32 Bit,
33 Data,
34 Idata,
35 Xdata,
36}
37
38impl 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)]
52pub 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)]
60pub 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)]
68pub struct AsmOutput {
69 pub lines: Vec<LineOut>,
70 pub symbols: BTreeMap<String, Symbol>,
71 pub diagnostics: Vec<Diagnostic>,
72}
73
74impl 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)]
116enum 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)]
132enum DbItem {
133 Bytes(Vec<u8>),
134 Expr(Expr, usize),
135}
136
137#[derive(Debug, Clone)]
138enum 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
158impl 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
175struct ParsedLine {
176 label: Option<(String, usize)>,
177 stmt: Stmt,
178}
179
180type PResult<T> = Result<T, (usize, String)>;
181
182fn 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
204fn 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
211fn 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
220fn 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
257fn 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
277fn 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
312fn 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
426struct Symbols {
427 user: HashMap<String, Symbol>,
428 here: i64,
429}
430
431impl 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
448fn is_predefined(name: &str) -> bool {
449 sfr::SFRS.iter().chain(sfr::BITS).any(|(n, _)| *n == name)
450}
451
452fn 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
458pub 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)]
741fn 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
770fn 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)]
829mod 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 = "
874start: SJMP start
875 DJNZ R2, start
876 CJNE A,#3,fwd
877 LJMP fwd
878 CALL fwd
879fwd: 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 = "
899COUNT EQU 3
900LED BIT P1.0
901 ORG 100h
902 MOV R0,#COUNT
903 SETB LED
904tbl: DB 1, 'AB', COUNT*2
905 DW tbl
906 DS 2
907after: 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}