summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/tq.rs13
-rw-r--r--src/formats/cmd.rs54
-rw-r--r--src/formats/mod.rs13
-rw-r--r--src/stages/bytecode.rs2
-rw-r--r--src/stages/syntactic.rs24
5 files changed, 92 insertions, 14 deletions
diff --git a/src/bin/tq.rs b/src/bin/tq.rs
index b27f702..a41aba9 100644
--- a/src/bin/tq.rs
+++ b/src/bin/tq.rs
@@ -78,16 +78,18 @@ Environment variables:
into the program.
Output formats:
- <debug>
+ cmd
+ CMD module load format used by the CP/M operating system.
+ debug
Print assembled words as human-readable binary literals.
- <inhx>
+ inhx
Original 8-bit Intel hex format.
- <inhx32>
+ inhx32
Modified 16-bit Intel hex format used by Microchip.
- <raw>
+ raw
Assembled words are converted to big-endian bytestrings and concatenated.
Each word is padded to the nearest byte. Words must all be the same width.
- <source>
+ source
Print the source file before assembly, with symbols resolved.
Created by Ben Bridle.
@@ -172,6 +174,7 @@ Created by Ben Bridle.
if !dry_run {
let result = match format {
+ Format::Cmd => format_cmd(&segments),
Format::Debug => format_debug(&segments),
Format::Inhx => format_inhx(&segments),
Format::Inhx32 => format_inhx32(&segments),
diff --git a/src/formats/cmd.rs b/src/formats/cmd.rs
new file mode 100644
index 0000000..2ff2097
--- /dev/null
+++ b/src/formats/cmd.rs
@@ -0,0 +1,54 @@
+// CP/M CMD format, also the TRS-80 Load Module Format (LMF)
+// https://en.wikipedia.org/wiki/CMD_file_(CP/M)
+// https://www.tim-mann.org/trs80/doc/ldosq1-4.pdf (page 43)
+use crate::*;
+
+
+pub fn format_cmd(segments: &[Segment]) -> Result<Vec<u8>, FormatError> {
+ let mut records = Vec::new();
+ let mut address;
+ for segment in segments {
+ address = segment.address;
+ for chunk in segment.words.chunks(16) {
+ records.push(data_record(chunk, address)?);
+ address += 16;
+ }
+ }
+ let start_address = segments.first().map(|s| s.address).unwrap_or(0);
+ records.push(terminating_record(start_address)?);
+
+ let mut output = String::new();
+ for record in records {
+ output.push_str(&record.to_string_plain());
+ }
+ return Ok(output.into_bytes());
+}
+
+fn data_record(words: &[Tracked<Word>], address: usize) -> Result<InhxRecord, FormatError> {
+ let Ok(address) = u16::try_from(address) else {
+ return Err(FormatError::AddressTooLarge(u16::MAX as usize, address));
+ };
+ let mut record = InhxRecord::new();
+ record.byte(0x01);
+ let data_bytes = words.len() as u8;
+ record.byte(data_bytes.wrapping_add(2));
+ record.le_double(address);
+ for word in words {
+ if word.value.width > 8 {
+ return Err(FormatError::WordTooWide(8, word.width, word.source.clone()));
+ }
+ record.byte(word.value.value as u8);
+ }
+ return Ok(record);
+}
+
+fn terminating_record(address: usize) -> Result<InhxRecord, FormatError> {
+ let Ok(address) = u16::try_from(address) else {
+ return Err(FormatError::AddressTooLarge(u16::MAX as usize, address));
+ };
+ let mut record = InhxRecord::new();
+ record.byte(0x02);
+ record.byte(0x02);
+ record.le_double(address);
+ return Ok(record);
+}
diff --git a/src/formats/mod.rs b/src/formats/mod.rs
index a77bd72..105207c 100644
--- a/src/formats/mod.rs
+++ b/src/formats/mod.rs
@@ -1,8 +1,10 @@
+mod cmd;
mod inhx;
mod inhx32;
mod raw;
mod debug;
+pub use cmd::*;
pub use inhx::*;
pub use inhx32::*;
pub use raw::*;
@@ -15,6 +17,7 @@ use log::*;
#[derive(Clone, Copy, PartialEq)]
pub enum Format {
+ Cmd,
Debug,
Inhx,
Inhx32,
@@ -25,6 +28,7 @@ pub enum Format {
impl Format {
pub fn from_str(string: &str) -> Self {
match string {
+ "cmd" => Self::Cmd,
"debug" => Self::Debug,
"inhx" => Self::Inhx,
"inhx32" => Self::Inhx32,
@@ -38,6 +42,7 @@ impl Format {
impl std::fmt::Display for Format {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let string = match self {
+ Self::Cmd => "cmd",
Self::Debug => "debug",
Self::Inhx => "inhx",
Self::Inhx32 => "inhx32",
@@ -112,6 +117,14 @@ impl InhxRecord {
}
format!(":{output}{checksum:0>2X}\n")
}
+
+ pub fn to_string_plain(self) -> String {
+ let mut output = String::new();
+ for byte in &self.bytes {
+ output.push_str(&format!("{byte:0>2X}"));
+ }
+ format!("{output}\n")
+ }
}
diff --git a/src/stages/bytecode.rs b/src/stages/bytecode.rs
index 3618b26..e4464a1 100644
--- a/src/stages/bytecode.rs
+++ b/src/stages/bytecode.rs
@@ -163,7 +163,7 @@ impl BytecodeParser {
}
};
let value_width = match field_value.cmp(&0) {
- std::cmp::Ordering::Less => (-field_value).ilog2() + 1,
+ std::cmp::Ordering::Less => (-field_value).ilog2() + 2,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => field_value.ilog2() + 1,
};
diff --git a/src/stages/syntactic.rs b/src/stages/syntactic.rs
index 14f8815..45d5e60 100644
--- a/src/stages/syntactic.rs
+++ b/src/stages/syntactic.rs
@@ -168,22 +168,27 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser) -> Result<Vec<Tracked<Syntac
c => {
let token = format!("{c}{}", t.eat_token());
- if let Some(hex_string) = token.strip_prefix("0x") {
+ let (stripped, neg) = match token.strip_prefix('-') {
+ Some(stripped) => (stripped, true),
+ None => (token.as_str(), false),
+ };
+ if let Some(hex_string) = stripped.strip_prefix("0x") {
let hex_string = hex_string.to_string();
- match parse_integer_literal(&hex_string, 16) {
+ match parse_integer_literal(&hex_string, 16, neg) {
Ok(value) => SyntacticToken::IntegerLiteral(value),
Err(_) => err!(SyntacticError::InvalidHexadecimalLiteral(hex_string)),
}
- } else if let Some(binary_string) = token.strip_prefix("0b") {
+ } else if let Some(binary_string) = stripped.strip_prefix("0b") {
let binary_string = binary_string.to_string();
- match parse_integer_literal(&binary_string, 2) {
+ match parse_integer_literal(&binary_string, 2, neg) {
Ok(value) => SyntacticToken::IntegerLiteral(value),
Err(_) => err!(SyntacticError::InvalidBinaryLiteral(binary_string)),
}
} else {
- match parse_integer_literal(&token, 10) {
+ let decimal_string = stripped.to_string();
+ match parse_integer_literal(&decimal_string, 10, neg) {
Ok(value) => SyntacticToken::IntegerLiteral(value),
- Err(true) => err!(SyntacticError::InvalidDecimalLiteral(token)),
+ Err(true) => err!(SyntacticError::InvalidDecimalLiteral(decimal_string)),
Err(false) => SyntacticToken::Symbol(ScopedSymbol::Global(token)),
}
}
@@ -200,10 +205,13 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser) -> Result<Vec<Tracked<Syntac
}
-fn parse_integer_literal(token: &str, radix: u32) -> Result<isize, bool> {
+fn parse_integer_literal(token: &str, radix: u32, neg: bool) -> Result<isize, bool> {
match usize::from_str_radix(&token.replace('_', ""), radix) {
Ok(value) => match isize::try_from(value) {
- Ok(value) => Ok(value),
+ Ok(value) => match neg {
+ true => Ok(-value),
+ false => Ok(value),
+ }
Err(_) => Err(true),
}
Err(_) => Err(false),