Chapter 10: Implementing Directives
In this chapter, we’ll implement handlers for all ByteASM directives.
Directive Overview
| Directive | Purpose | Example |
|---|---|---|
.org | Set assembly address | .org 0x8000 |
.db | Define bytes | .db 0x01, "Hi", 0 |
.dw | Define words | .dw 0x1234, label |
.equ | Define constant | .equ SCREEN 0x1000 |
.include | Include file | .include "utils.s" |
The .org Directive
.org (origin) sets the current assembly address.
Pass 1 Handling
#![allow(unused)]
fn main() {
DirectiveStmt::Org { address, .. } => {
let addr = self.evaluate(address)? as u16;
self.origin = addr;
self.current_address = addr;
}
}
Pass 2 Handling
#![allow(unused)]
fn main() {
DirectiveStmt::Org { address, .. } => {
let addr = self.evaluate(address)? as u16;
// If moving forward, pad with zeros
if addr > self.current_address {
self.pad_to(addr);
}
self.current_address = addr;
}
}
Usage Examples
.org 0x8000 ; Start code at 0x8000
reset:
; ... code ...
.org 0xFFFC ; Jump to reset vector location
.dw reset ; Write reset vector
Multiple .org Directives
.org 0x8000
lda #0x01 ; At 0x8000
.org 0x8100 ; Skip to 0x8100 (gap filled with zeros)
lda #0x02 ; At 0x8100
The .db Directive
.db (define bytes) emits raw bytes and strings.
Pass 1 Handling
Calculate size without emitting:
#![allow(unused)]
fn main() {
DirectiveStmt::Db { values, .. } => {
for value in values {
match value {
DataValue::Byte(_) => {
self.current_address = self.current_address.wrapping_add(1);
}
DataValue::String(s) => {
self.current_address = self.current_address.wrapping_add(s.len() as u16);
}
}
}
}
}
Pass 2 Handling
Emit the actual bytes:
#![allow(unused)]
fn main() {
DirectiveStmt::Db { values, location } => {
for value in values {
match value {
DataValue::Byte(expr) => {
let byte = self.evaluate_byte(expr, *location)?;
self.emit_byte(byte);
}
DataValue::String(s) => {
for byte in s.bytes() {
self.emit_byte(byte);
}
}
}
}
}
}
Usage Examples
; Single bytes
.db 0x00, 0xFF, 0x42
; Expressions
.db 10 + 5, CONSTANT - 1
; Strings
.db "Hello, World!", 0x0A, 0
; Mixed
.db "Score: ", 0x30, 0 ; "Score: 0\0"
Strings and Escape Sequences
The scanner handles escape sequences, so:
.db "Line 1\nLine 2\0"
Emits: 4C 69 6E 65 20 31 0A 4C 69 6E 65 20 32 00
The .dw Directive
.dw (define words) emits 16-bit values in little-endian format.
Pass 1 Handling
#![allow(unused)]
fn main() {
DirectiveStmt::Dw { values, .. } => {
self.current_address = self.current_address
.wrapping_add((values.len() * 2) as u16);
}
}
Pass 2 Handling
#![allow(unused)]
fn main() {
DirectiveStmt::Dw { values, location } => {
for value in values {
let word = self.evaluate_word(value, *location)?;
self.emit_word(word);
}
}
}
Usage Examples
; Numeric values
.dw 0x1234, 0x5678 ; Emits: 34 12 78 56
; Labels (addresses)
.dw start, main_loop ; Emits addresses
; Interrupt vectors
.org 0xFFFC
.dw reset ; Reset vector
.dw irq_handler ; IRQ vector
The .equ Directive
.equ defines a constant that can be used throughout the program.
Pass 1 Handling
#![allow(unused)]
fn main() {
DirectiveStmt::Equ { name, value, location } => {
let val = self.evaluate(value)?;
self.symbols.define_constant(name, val, *location)?;
}
}
Pass 2 Handling
#![allow(unused)]
fn main() {
DirectiveStmt::Equ { .. } => {
// Constants are handled in pass 1, nothing to emit
}
}
Usage Examples
; Hardware addresses
.equ VID_PTR 0xFD
.equ INPUT 0xFF
; Constants
.equ SCREEN_WIDTH 64
.equ SCREEN_HEIGHT 64
.equ PIXEL_COUNT SCREEN_WIDTH * SCREEN_HEIGHT
; Usage
lda INPUT
sta VID_PTR
ldx #SCREEN_WIDTH
Constants vs Labels
Constants differ from labels:
- Labels: Get their value from their position in code
- Constants: Have values assigned directly
.equ CONST 0x42 ; CONST = 0x42 (assigned)
label: ; label = current address (computed)
nop
The .include Directive
.include inserts another source file at the current position.
Implementation (Simplified)
#![allow(unused)]
fn main() {
DirectiveStmt::Include { path, location } => {
// Read the include file
let source = std::fs::read_to_string(path)
.map_err(|_| CodeGenError::Internal {
message: format!("cannot read file: {}", path),
})?;
// Parse it
let program = parser::parse(&source)?;
// Recursively assemble
for stmt in program.statements {
self.process_statement(&stmt)?;
}
}
}
Usage Examples
; main.s
.include "constants.s"
.include "macros.s"
.org 0x8000
reset:
jsr init_screen
; ...
; constants.s
.equ VID_PTR 0xFD
.equ INPUT 0xFF
.equ VRAM 0x1000
Circular Include Detection
We need to detect and prevent circular includes:
; a.s includes b.s
; b.s includes a.s → Error!
Track included files and error if we see a repeat.
Directive Processing Flow
Complete Pass 1 Handler
#![allow(unused)]
fn main() {
pub fn pass1_directive(&mut self, directive: &DirectiveStmt) -> Result<(), CodeGenError> {
match directive {
DirectiveStmt::Org { address, .. } => {
let addr = self.evaluate(address)? as u16;
self.origin = addr;
self.current_address = addr;
}
DirectiveStmt::Db { values, .. } => {
for value in values {
match value {
DataValue::Byte(_) => {
self.current_address = self.current_address.wrapping_add(1);
}
DataValue::String(s) => {
self.current_address =
self.current_address.wrapping_add(s.len() as u16);
}
}
}
}
DirectiveStmt::Dw { values, .. } => {
self.current_address = self
.current_address
.wrapping_add((values.len() * 2) as u16);
}
DirectiveStmt::Equ { name, value, location } => {
let val = self.evaluate(value)?;
self.symbols.define_constant(name, val, *location)?;
}
DirectiveStmt::Include { .. } => {
// Handle includes
}
}
Ok(())
}
}
Complete Pass 2 Handler
#![allow(unused)]
fn main() {
pub fn emit_directive(&mut self, directive: &DirectiveStmt) -> Result<(), CodeGenError> {
match directive {
DirectiveStmt::Org { address, .. } => {
let addr = self.evaluate(address)? as u16;
if addr > self.current_address {
self.pad_to(addr);
}
self.current_address = addr;
}
DirectiveStmt::Db { values, location } => {
for value in values {
match value {
DataValue::Byte(expr) => {
let byte = self.evaluate_byte(expr, *location)?;
self.emit_byte(byte);
}
DataValue::String(s) => {
for byte in s.bytes() {
self.emit_byte(byte);
}
}
}
}
}
DirectiveStmt::Dw { values, location } => {
for value in values {
let word = self.evaluate_word(value, *location)?;
self.emit_word(word);
}
}
DirectiveStmt::Equ { .. } => {
// Nothing to emit
}
DirectiveStmt::Include { .. } => {
// Handle includes
}
}
Ok(())
}
}
Summary
In this chapter, we implemented all ByteASM directives:
.org: Sets the assembly address, pads with zeros.db: Emits bytes and strings.dw: Emits 16-bit words (little-endian).equ: Defines named constants.include: Includes other source files
In the next chapter, we’ll implement comprehensive error handling and reporting.
Previous: Chapter 9 - Expression Evaluation | Next: Chapter 11 - Error Handling