c64_assembler/
lib.rs

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