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}