c64_assembler/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
//! # C64 Assembler
//!
//! The goal of this crate is to being able to compile C64 assembly directly from Rust.
//!
//! The reasoning behind it is that in a typical C64 development workflow the programs
//! are generated in a separate step and then being saved to a disk image. For generating
//! disk images there are already crates out there like cmb.
//!
//! However some projects require more control over the compilation stage and disk
//! construction stage. Having a C64 assembler written in rust can build a bridge and
//! allows custom disk formats and faster iterations during development.
//!
//! ## Modules and functions
//!
//! An [crate::Application] is organized in [crate::Module] and
//! [crate::Function]. Modules can be shared between applications. A module public API is
//! organized in functions. Multiple variations of functions can exists. By swapping out
//! functions in a module a module can be size-optimized or CPU cycles optimized based on
//! the actual needs of the program.
//!
//! ## Usage
//!
//! ### Building pattern
//!
//! An application can be build using builder patterns.
//!
//! ```
//! use c64_assembler::builder::ApplicationBuilder;
//! use c64_assembler::builder::ModuleBuilder;
//! use c64_assembler::builder::InstructionBuilder;
//!
//! let application = ApplicationBuilder::default()
//! .name("Set black border")
//! .include_vic20_defines()
//! .module(
//! ModuleBuilder::default()
//! .name("main")
//! .instructions(
//! InstructionBuilder::default()
//! .add_basic_header()
//! .label("main_entry_point")
//! .lda_imm(0x00)
//! .comment("Load black color")
//! .sta_addr("VIC20_BORDER_COLOR")
//! .rts()
//! .build(),
//! )
//! .build(),
//! )
//! .build().unwrap();
//! ```
//!
//! ### Validating
//!
//! Using the [crate::validator::Validator] to check for consistency.
//!
//! ```
//! use c64_assembler::validator::Validator;
//! # use c64_assembler::builder::ApplicationBuilder;
//! # let application = ApplicationBuilder::default().build().unwrap();
//!
//! let validation_result = application.validate();
//! assert!(validation_result.is_ok());
//! ```
//!
//! ### Generating dasm source
//!
//! Using the [crate::generator::DasmGenerator] a dasm compatible assembly source
//! can be generated.
//!
//! ```
//! use c64_assembler::generator::Generator;
//! use c64_assembler::generator::DasmGenerator;
//! # use c64_assembler::builder::ApplicationBuilder;
//! # let application = ApplicationBuilder::default().build().unwrap();
//!
//! let source = DasmGenerator::default().generate(application).unwrap();
//! println!("{}", source);
//! ```
//!
//! Would output
//!
//! ```asm
//! ; --- Application: SET BLACK BORDER ---
//! ; NOTE: This file is generated, do not modify
//!
//! processor 6502
//!
//! VIC20_BORDER_COLOR = $D020
//!
//! org $0800
//!
//! ; --- Module begin: MAIN ---
//! byte $00, $0C, $08 ; New basic line
//! ; 10 SYS 2062
//! byte $0A, $00, $9E, $20, $32, $30, $36, $32
//! byte $00, $00, $00 ; End basic program
//!
//! main_entry_point:
//! lda #$00
//! sta VIC20_BORDER_COLOR
//! rts
//! ; --- Module end: MAIN ---
//! ```
//!
//! ### Generating .PRG byte stream
//!
//! Using the [crate::generator::ProgramGenerator] to generate the byte stream.
//! The byte stream includes the loading address.
//!
//! ```
//! use c64_assembler::generator::{Generator, ProgramGenerator, print_hexdump};
//! # use c64_assembler::builder::ApplicationBuilder;
//! # let application = ApplicationBuilder::default().build().unwrap();
//!
//! let bytes = ProgramGenerator::default().generate(application).unwrap();
//! print_hexdump(&bytes);
//! ```
//!
//! ```txt
//! 0000: 00 08 00 0C 08 0A 00 9E 20 32 30 36 32 00 00 00
//! 0010: A9 00 8D 20 D0 60
//! ```
//!
//! ### Using macros (work in progress)
//!
//! To reduce the boilerplating macros can be used. This is still under development.
//! Expect less stability, error messages and some instructions not supported.
//!
//! ```
//! use c64_assembler_macro::application;
//!
//! let application = application!(
//! name="Set black border"
//! include_vic20_defines
//! module!(
//! name="main"
//! instructions!(
//! include_basic_header
//! main_entry_point:
//! "Load black color into accumulator"
//! lda #$00
//! sta VIC20_BORDER_COLOR
//! rts
//! )
//! )
//! ).unwrap();
//! ```
use std::collections::HashMap;
use instruction::Instruction;
use memory::{define::Define, user_count::UserCount, Address};
use validator::{AssemblerResult, Error};
pub mod builder;
pub mod generator;
pub mod instruction;
pub mod memory;
pub mod validator;
#[cfg(test)]
mod test;
/// Application is the root container for the assembler
#[derive(Clone)]
pub struct Application {
/// Name of the application; only used in comments.
pub name: String,
/// Entry point of the application, default = 0x0800
pub entry_point: Address,
/// Modules of the application
pub modules: Vec<Module>,
/// Defines of the application
pub defines: Vec<Define>,
/// Lookup for addresses.
pub address_lookup: HashMap<String, Address>,
}
impl Application {
pub fn lookup_address(&self, address_name: &String) -> AssemblerResult<Address> {
if let Some(address) = self.address_lookup.get(address_name) {
Ok(*address)
} else {
Err(Error::AddressNameUnknown(address_name.to_string()))
}
}
}
/// Module
///
/// A module is reusable part between applications. If you have some frequent used
/// code, you can group it in a module so you can reused it between applications.
///
/// Can be compared with an include statement.
#[derive(Default, Clone)]
pub struct Module {
/// Name of the module; only used in comments.
pub name: String,
/// Module specific utility instructions.
///
/// For sharing code between functions.
pub instructions: Instructions,
/// Functions of this module.
pub functions: Vec<Function>,
}
/// Function is a replaceble public part of a module.
///
/// You can have multiple variations of a function. These functions can be swapped out
/// when building the module.
///
/// # Variations
///
/// Some examples why functions can have variations.
///
/// - Size optimized
/// - Performance optimized
/// - Last known working version
/// - Currently in development
#[derive(Default, Clone)]
pub struct Function {
/// Name of the function
pub name: String,
/// Documentation of the function.
pub documentation: Vec<String>,
/// Instructions belonging to this function.
pub instructions: Instructions,
user_count: usize,
}
impl UserCount for Function {
fn user_increase(&mut self) {
self.user_count += 1;
}
fn user_count(&self) -> usize {
self.user_count
}
}
/// Stream of instructions.
#[derive(Debug, Default, Clone)]
pub struct Instructions {
pub instructions: Vec<Instruction>,
}