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>,
}