diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/tq.rs | 13 | ||||
-rw-r--r-- | src/formats/cmd.rs | 54 | ||||
-rw-r--r-- | src/formats/mod.rs | 13 | ||||
-rw-r--r-- | src/stages/bytecode.rs | 2 | ||||
-rw-r--r-- | src/stages/syntactic.rs | 24 |
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), |