diff options
-rw-r--r-- | Cargo.lock | 126 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | LICENSE.md | 9 | ||||
-rw-r--r-- | rust-toolchain.toml | 3 | ||||
-rw-r--r-- | src/bin/tq.rs | 13 | ||||
-rw-r--r-- | src/formats/cmd.rs | 54 | ||||
-rw-r--r-- | src/formats/mod.rs | 15 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/stages/bytecode.rs | 156 | ||||
-rw-r--r-- | src/stages/bytecode_tokens.rs | 6 | ||||
-rw-r--r-- | src/stages/compiler.rs | 16 | ||||
-rw-r--r-- | src/stages/intermediate.rs | 207 | ||||
-rw-r--r-- | src/stages/intermediate_tokens.rs | 12 | ||||
-rw-r--r-- | src/stages/semantic.rs | 34 | ||||
-rw-r--r-- | src/stages/semantic_tokens.rs | 38 | ||||
-rw-r--r-- | src/stages/syntactic.rs | 30 | ||||
-rw-r--r-- | src/stages/syntactic_tokens.rs | 4 | ||||
-rw-r--r-- | src/types/expression_stack.rs | 18 | ||||
-rw-r--r-- | src/types/operator.rs | 9 |
19 files changed, 597 insertions, 162 deletions
@@ -3,17 +3,12 @@ version = 4 [[package]] -name = "ansi" -version = "1.0.0" -source = "git+git://benbridle.com/ansi?tag=v1.0.0#81d47867c2c97a9ae1d1c8fdfcd42c582410ad2a" - -[[package]] name = "assembler" -version = "2.2.2" -source = "git+git://benbridle.com/assembler?tag=v2.2.2#85c1fd299e04e27307a1c1633282df6f1d579a98" +version = "2.3.0" +source = "git+git://benbridle.com/assembler?tag=v2.3.0#a9640fce1aaa5e80170ce4d2ac700f66cfffbb4b" dependencies = [ - "ansi", - "log 1.1.2", + "inked", + "log 2.0.0", "vagabond", ] @@ -40,16 +35,24 @@ dependencies = [ ] [[package]] +name = "inked" +version = "1.0.0" +source = "git+git://benbridle.com/inked?tag=v1.0.0#2954d37b638fa2c1dd3d51ff53f08f475aea6ea3" +dependencies = [ + "termcolor", +] + +[[package]] name = "log" version = "1.1.1" source = "git+git://benbridle.com/log?tag=v1.1.1#930f3d0e2b82df1243f423c092a38546ea7533c3" [[package]] name = "log" -version = "1.1.2" -source = "git+git://benbridle.com/log?tag=v1.1.2#3d5d1f7a19436151ba1dd52a2b50664969d90db6" +version = "2.0.0" +source = "git+git://benbridle.com/log?tag=v2.0.0#a38d3dd487594f41151db57625410d1b786bebe4" dependencies = [ - "ansi", + "inked", ] [[package]] @@ -68,16 +71,107 @@ dependencies = [ ] [[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] name = "torque-asm" -version = "2.0.1" +version = "2.5.0" dependencies = [ "assembler", "indexmap", - "log 1.1.2", + "log 2.0.0", "switchboard", ] [[package]] name = "vagabond" -version = "1.0.1" -source = "git+git://benbridle.com/vagabond?tag=v1.0.1#08f3153fea62ea81a42438347eeee058f5bec199" +version = "1.1.1" +source = "git+git://benbridle.com/vagabond?tag=v1.1.1#b190582517e6008ad1deff1859f15988e4efaa26" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" @@ -1,11 +1,12 @@ [package] name = "torque-asm" -version = "2.0.1" +version = "2.5.0" edition = "2021" +license = "MIT" [dependencies] -assembler = { git = "git://benbridle.com/assembler", tag = "v2.2.2" } -log = { git = "git://benbridle.com/log", tag = "v1.1.2" } +assembler = { git = "git://benbridle.com/assembler", tag = "v2.3.0" } +log = { git = "git://benbridle.com/log", tag = "v2.0.0" } switchboard = { git = "git://benbridle.com/switchboard", tag = "v2.1.0" } indexmap = "2.7.1" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..21ed643 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Ben Bridle + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a25e7e0 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" + 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..e15bfbd 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::*; @@ -10,11 +12,10 @@ pub use debug::*; use crate::*; -use log::*; - #[derive(Clone, Copy, PartialEq)] pub enum Format { + Cmd, Debug, Inhx, Inhx32, @@ -25,6 +26,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 +40,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 +115,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") + } } @@ -7,3 +7,5 @@ pub use types::*; pub use formats::*; pub use assembler::*; + +use log::*; diff --git a/src/stages/bytecode.rs b/src/stages/bytecode.rs index 3618b26..71f1ff0 100644 --- a/src/stages/bytecode.rs +++ b/src/stages/bytecode.rs @@ -34,26 +34,82 @@ impl BytecodeParser { } pub fn parse(mut self, tokens: Vec<Tracked<IntermediateToken>>) -> Result<Vec<Segment>, Vec<Tracked<BytecodeError>>> { - // Calculate all label addresses ahead of time. + // Register all labels with address 0. + for token in &tokens { + if let IntermediateToken::LabelDefinition(name) = &token.value { + let tracked = Tracked::from(0, token.source.clone()); + if let Some(_) = self.addresses.insert(name.clone(), tracked) { + unreachable!("Uncaught duplicate label definition '{name}'"); + } + } + } + // Attempt to calculate all label addresses naively ahead of time. + // This will give false results if we pin an address calculated from a label address. let mut address = 0; for token in &tokens { let source = &token.source; match &token.value { IntermediateToken::LabelDefinition(name) => { let tracked = Tracked::from(address, source.clone()); - if let Some(_) = self.addresses.insert(name.clone(), tracked) { - unreachable!("Uncaught duplicate label definition '{name}'"); - } + self.addresses.insert(name.clone(), tracked); } IntermediateToken::Word(_) => { address += 1; } IntermediateToken::PinnedAddress(pinned) => { - address = pinned.value; + // Attempt to calculate a sane initial value for a pinned address. + match &pinned.value { + IntermediateInteger::Integer(value) => { + address = (*value).try_into().unwrap_or(0); + } + IntermediateInteger::Expression(expression) => { + let result = self.evaluate_expression(&expression, &pinned.source); + address = result.try_into().unwrap_or(0); + } + IntermediateInteger::LabelReference(_) => { + let error = BytecodeError::PinnedLabel; + self.errors.push(Tracked::from(error, source.clone())); + } + } } } } - for token in &tokens { + // Return unrecoverable errors. + if !self.errors.is_empty() { + return Err(self.errors); + } + + for i in 0..4 { + info!("Attempting iteration {} of bytecode assembly stage", i+1); + // Erase the previous parse attempt. + self.segments.clear(); + self.errors.clear(); + // Attempt to parse the program. + let previous_addresses = self.addresses.clone(); + self.parse_iteration(&tokens); + // Return unrecoverable errors. + if !self.errors.is_empty() { + return Err(self.errors); + } + // Check label stability + if self.check_for_instability(&previous_addresses) { + continue; + } + // Check for backtrack + if self.check_for_backtrack() { + continue; + }; + // Program is stable, return. + info!("Stabilised in {} iteration of bytecode assembly stage", i+1); + return Ok(self.segments); + } + + return Err(self.errors); + } + + /// Attempt to parse the full program using the current label values. + fn parse_iteration(&mut self, tokens: &[Tracked<IntermediateToken>]) { + for token in tokens { let source = &token.source; match &token.value { IntermediateToken::Word(word) => { @@ -68,26 +124,37 @@ impl BytecodeParser { self.words.push(word); self.address += 1; } - IntermediateToken::PinnedAddress(address) => { - let current = self.address; - let pinned = address.value; - if current > pinned { - let error = BytecodeError::PinnedAddressBacktrack(pinned, current); - self.errors.push(Tracked::from(error, address.source.clone())); - } else { - let words = std::mem::take(&mut self.words); - if !words.is_empty() { - let address = self.segment_address; - let source = std::mem::take(&mut self.segment_source); - let segment = Segment { address, source, words }; - self.segments.push(segment); + IntermediateToken::PinnedAddress(integer) => { + // Calculate the address of the new segment. + let pinned = match &integer.value { + IntermediateInteger::Integer(value) => { + (*value).try_into().unwrap_or(0) + } + IntermediateInteger::Expression(expression) => { + let result = self.evaluate_expression(&expression, &integer.source); + result.try_into().unwrap_or(0) } - self.segment_source = Some(address.source.clone()); - self.address = pinned; - self.segment_address = pinned; + IntermediateInteger::LabelReference(_) => + // Already handled when registering initial label values. + unreachable!(), + }; + // Start a new segment. + let words = std::mem::take(&mut self.words); + if !words.is_empty() { + let address = self.segment_address; + let source = std::mem::take(&mut self.segment_source); + let segment = Segment { address, source, words }; + self.segments.push(segment); } + self.segment_source = Some(integer.source.clone()); + self.address = pinned; + self.segment_address = pinned; + } + IntermediateToken::LabelDefinition(name) => { + // Record the latest known address of this label. + let address = self.addresses.get_mut(name).unwrap(); + address.value = self.address; } - IntermediateToken::LabelDefinition(_) => (), } } // Finish final segment. @@ -98,11 +165,6 @@ impl BytecodeParser { let segment = Segment { address, source, words }; self.segments.push(segment); } - - match self.errors.is_empty() { - true => Ok(self.segments), - false => Err(self.errors), - } } fn evaluate_expression(&mut self, expression: &IntermediateExpression, source: &SourceSpan) -> isize { @@ -162,11 +224,7 @@ impl BytecodeParser { *value } }; - let value_width = match field_value.cmp(&0) { - std::cmp::Ordering::Less => (-field_value).ilog2() + 1, - std::cmp::Ordering::Equal => 0, - std::cmp::Ordering::Greater => field_value.ilog2() + 1, - }; + let value_width = width(field_value); if field.width < value_width { let error = BytecodeError::ValueTooWide(field.width, value_width); self.errors.push(Tracked::from(error, field_source.clone())); @@ -179,4 +237,36 @@ impl BytecodeParser { let word = Word { width: word.width, value: word_value }; return Tracked::from(word, source.clone()); } + + fn check_for_instability(&mut self, previous_addresses: &HashMap<String, Tracked<usize>>) -> bool { + let mut instability_occurred = false; + for (name, previous_address) in previous_addresses.iter() { + let current_address = &self.addresses[name]; + if current_address != previous_address { + info!("Label '{name}' was unstable, moving from address 0x{:04x} to 0x{:04x}", + previous_address.value, current_address.value); + let error = BytecodeError::UnstableLabel(name.to_string()); + self.errors.push(Tracked::from(error, previous_address.source.clone())); + instability_occurred = true; + } + } + return instability_occurred; + } + + fn check_for_backtrack(&mut self) -> bool { + let mut backtrack_occurred = false; + let mut current_address = 0; + for segment in &self.segments { + if segment.address < current_address { + let error = BytecodeError::PinnedAddressBacktrack(segment.address, current_address); + if let Some(source) = &segment.source { + self.errors.push(Tracked::from(error, source.clone())); + } + info!("Backtrack occurred with segment at address 0x{:04x}", segment.address); + backtrack_occurred = true; + } + current_address = segment.address + segment.words.len(); + } + return backtrack_occurred; + } } diff --git a/src/stages/bytecode_tokens.rs b/src/stages/bytecode_tokens.rs index b54cb0e..17b13b8 100644 --- a/src/stages/bytecode_tokens.rs +++ b/src/stages/bytecode_tokens.rs @@ -40,6 +40,8 @@ pub enum BytecodeError { PinnedAddressBacktrack(usize, usize), /// expected, received ValueTooWide(u32, u32), + PinnedLabel, + UnstableLabel(String), StackError(Tracked<StackError>), } @@ -57,6 +59,10 @@ fn report_bytecode_error(error: &Tracked<BytecodeError>, source_code: &str) { &format!("Word is {received} bits wide, but was expected to have a fixed width of {expected} bits"), BytecodeError::PinnedAddressBacktrack(pinned, real) => &format!("Cannot pin to address {pinned} when address is already {real}"), + BytecodeError::PinnedLabel => + &format!("Cannot pin directly to a label"), + BytecodeError::UnstableLabel(name) => + &format!("Label '{name}' never stabilised"), BytecodeError::StackError(stack_error) => { report_stack_error(stack_error, source_code); return; }, BytecodeError::ValueTooWide(expected, received) => diff --git a/src/stages/compiler.rs b/src/stages/compiler.rs index 44b7660..9d16bf0 100644 --- a/src/stages/compiler.rs +++ b/src/stages/compiler.rs @@ -92,6 +92,9 @@ impl SymbolParser { MacroDefinitionBody::Integer(integer) => { self.parse_integer_token(&integer, &integer.source) } + MacroDefinitionBody::String(string) => { + self.parse_string_token(&string, &string.source) + } MacroDefinitionBody::Invocation(invocation) => { self.parse_invocation(&invocation, &invocation.source) } @@ -142,10 +145,12 @@ impl SymbolParser { InvocationArgument::BlockToken(block) => { self.parse_block_token(block, &source); } + InvocationArgument::StringToken(string) => { + self.parse_string_token(string, &source); + }, InvocationArgument::Invocation(invocation) => { self.parse_invocation(invocation, &source); } - InvocationArgument::String(_) => (), } } } @@ -197,4 +202,13 @@ impl SymbolParser { IntegerToken::IntegerLiteral(_) => (), } } + + fn parse_string_token(&mut self, token: &StringToken, source: &SourceSpan) { + match &token { + StringToken::Invocation(invocation) => { + self.parse_invocation(&invocation, source) + } + StringToken::StringLiteral(_) => (), + } + } } diff --git a/src/stages/intermediate.rs b/src/stages/intermediate.rs index 6853f62..cfe33b7 100644 --- a/src/stages/intermediate.rs +++ b/src/stages/intermediate.rs @@ -84,10 +84,27 @@ impl IntermediateParser { ArgumentType::Block => { IntermediateValue::Block(Vec::new()) } + ArgumentType::String => { + let string = String::new(); + let chars = Vec::new(); + let literal = StringLiteral { string, chars }; + let tracked = Tracked::from(literal, null.clone()); + IntermediateValue::String(tracked) + } }; let tracked = Tracked::from(value, null.clone()); arguments.insert(argument.name.clone(), tracked); } + // Register macro definition with empty body so that macro can invoke itself. + let name = definition.name.to_string(); + let dummy_definition = MacroDefinition { + name: definition.name, + arguments: definition.arguments, + body: MacroDefinitionBody::Block(Vec::new()), + }; + if self.macro_definitions.insert(name.clone(), dummy_definition).is_some() { + unreachable!("Uncaught duplicate macro definition '{}'", name); + } let mut env = Environment { label_names: &self.label_names, macro_names: &self.macro_names, @@ -100,10 +117,9 @@ impl IntermediateParser { if self.errors.len() != error_count { break; } - - let name = definition.name.to_string(); - if self.macro_definitions.insert(name.clone(), definition).is_some() { - unreachable!("Uncaught duplicate macro definition '{}'", name); + // Replace dummy macro body with original macro body. + if let Some(registered) = self.macro_definitions.get_mut(&name) { + registered.body = definition.body; } } SemanticToken::BlockToken(block_token) => { @@ -164,6 +180,11 @@ impl<'a> Environment<'a> { let value = IntermediateValue::Block(tokens); Some(Tracked::from(value, source.clone())) } + MacroDefinitionBody::String(string) => { + let string = self.parse_string_token(string, &source)?; + let integer = IntermediateValue::String(string); + Some(Tracked::from(integer, source.clone())) + } } } @@ -176,46 +197,31 @@ impl<'a> Environment<'a> { } BlockToken::PinnedAddress(address) => { if let Some(integer) = self.parse_integer_token(address, &address.source) { - if let Some(source) = integer_contains_label_reference(&integer) { - let error = IntermediateError::LabelReferenceInPinnedAddress; - let new_source = address.source.clone().wrap(source); - self.errors.push(Tracked::from(error, new_source)); - } else { - match evaluate_integer(&integer, source) { - Ok(value) => { - let value = usize::try_from(value).unwrap_or(0); - let tracked = Tracked::from(value, address.source.clone()); - let token = IntermediateToken::PinnedAddress(tracked); - intermediate.push(Tracked::from(token, source.clone())); - } - Err(error) => self.errors.push(error), - } - } + let token = IntermediateToken::PinnedAddress(integer); + intermediate.push(Tracked::from(token, source.clone())); } } BlockToken::ConditionalBlock(cond) => { - let predicate = self.parse_integer_token(&cond.predicate, &cond.predicate.source); - let mut body = self.parse_block_token(&cond.body, &cond.body.source); - if let Some(predicate) = predicate { - let mut found_error = false; + if let Some(predicate) = self.parse_integer_token(&cond.predicate, &cond.predicate.source) { if let Some(source) = integer_contains_label_reference(&predicate) { let error = IntermediateError::LabelReferenceInConditionPredicate; let new_source = cond.predicate.source.clone().wrap(source); self.errors.push(Tracked::from(error, new_source)); - found_error = true; - }; - if let Some(source) = block_contains_label_definition(&cond.body, &cond.body.source) { - let error = IntermediateError::LabelDefinitionInConditionBody; - let new_source = cond.body.source.clone().wrap(source); - self.errors.push(Tracked::from(error, new_source)); - found_error = true; - } - if !found_error { + } else { match evaluate_integer(&predicate, &cond.predicate.source) { - Ok(value) => if value != 0 { intermediate.append(&mut body) }, + Ok(value) => if value != 0 { + let mut body = self.parse_block_token(&cond.body, &cond.body.source); + if let Some(source) = block_contains_label_definition(&cond.body, &cond.body.source) { + let error = IntermediateError::LabelDefinitionInConditionBody; + let new_source = cond.body.source.clone().wrap(source); + self.errors.push(Tracked::from(error, new_source)); + } else { + intermediate.append(&mut body); + } + }, Err(error) => self.errors.push(error), } - } + }; } } BlockToken::WordTemplate(word_template) => { @@ -272,10 +278,21 @@ impl<'a> Environment<'a> { } } + fn parse_string_token(&mut self, string: &StringToken, source: &SourceSpan) -> Option<Tracked<StringLiteral>> { + match string { + StringToken::StringLiteral(literal) => { + Some(Tracked::from(literal.clone(), source.clone())) + } + StringToken::Invocation(invocation) => { + self.parse_string_invocation(&invocation, source) + } + } + } + fn parse_integer_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Tracked<IntermediateInteger>> { match self.parse_invocation(invocation, source)?.value { IntermediateValue::Integer(integer) => Some(integer), - IntermediateValue::Block(_) => { + IntermediateValue::Block(_) | IntermediateValue::String(_) => { let error = IntermediateError::ExpectedInteger; self.errors.push(Tracked::from(error, source.clone())); None @@ -286,7 +303,7 @@ impl<'a> Environment<'a> { fn parse_block_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Vec<Tracked<IntermediateToken>>> { match self.parse_invocation(invocation, source)?.value { IntermediateValue::Block(tokens) => Some(tokens), - IntermediateValue::Integer(_) => { + IntermediateValue::Integer(_) | IntermediateValue::String(_) => { let error = IntermediateError::ExpectedBlock; self.errors.push(Tracked::from(error, source.clone())); None @@ -294,9 +311,21 @@ impl<'a> Environment<'a> { } } + fn parse_string_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Tracked<StringLiteral>> { + match self.parse_invocation(invocation, source)?.value { + IntermediateValue::String(literal) => Some(literal), + IntermediateValue::Integer(_) | IntermediateValue::Block(_) => { + let error = IntermediateError::ExpectedString; + self.errors.push(Tracked::from(error, source.clone())); + None + } + } + } + fn parse_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Tracked<IntermediateValue>> { let received_count = invocation.arguments.len(); if let Some(argument) = self.arguments.get(&invocation.name) { + // This invocation is a macro argument if received_count != 0 { let error = IntermediateError::IncorrectArgumentCount(0, received_count); self.errors.push(Tracked::from(error, source.clone())); @@ -305,6 +334,7 @@ impl<'a> Environment<'a> { Some(argument.clone()) } } else if let Some(label_name) = self.label_names.get(&invocation.name) { + // This invocation is a label reference if received_count != 0 { let error = IntermediateError::IncorrectArgumentCount(0, received_count); self.errors.push(Tracked::from(error, source.clone())); @@ -328,48 +358,9 @@ impl<'a> Environment<'a> { // Gather and type-check the provided arguments. let mut arguments = Vec::new(); for (i, argument) in invocation.arguments.iter().enumerate() { - let received_type = match &argument.value { - InvocationArgument::String(string) => { - let mut values = Vec::new(); - for c in &string.chars { - let integer = IntermediateInteger::Integer(**c); - let tracked = Tracked::from(integer, c.source.clone()); - values.push(IntermediateValue::Integer(tracked)); - } - arguments.push(RepeatedArgument::List(values)); - ArgumentType::Integer - } - InvocationArgument::IntegerToken(integer) => { - let tracked = self.parse_integer_token(&integer, &argument.source)?; - let value = IntermediateValue::Integer(tracked); - arguments.push(RepeatedArgument::Loop(value)); - ArgumentType::Integer - } - InvocationArgument::BlockToken(block) => { - let tokens = self.parse_block_token(&block, &argument.source); - let value = IntermediateValue::Block(tokens); - arguments.push(RepeatedArgument::Loop(value)); - ArgumentType::Block - } - InvocationArgument::Invocation(invocation) => { - let value = self.parse_invocation(&invocation, &argument.source)?; - let received_type = match &value.value { - IntermediateValue::Integer(_) => ArgumentType::Integer, - IntermediateValue::Block(_) => ArgumentType::Block, - }; - arguments.push(RepeatedArgument::Loop(value.value)); - received_type - } - }; - let expected_type = match received_type { - ArgumentType::Integer => ArgumentType::Block, - ArgumentType::Block => ArgumentType::Integer, - }; - if definition.arguments[i].variant != received_type { - let error = IntermediateError::IncorrectArgumentType(expected_type, received_type); - self.errors.push(Tracked::from(error, argument.source.clone())); - return None; - } + let expected_type = definition.arguments[i].variant; + let received_value = self.parse_invocation_argument(argument, expected_type)?; + arguments.push(received_value); } // Invoke the invocation multiple times. let repetitions = arguments.iter().map(|a| a.len()).max().unwrap_or(1); @@ -400,6 +391,7 @@ impl<'a> Environment<'a> { unreachable!("Uncaught duplicate macro argument name '{name}'"); }; } + // Invoke the macro once. let mut env = Environment { label_names: &self.label_names, macro_names: &self.macro_names, @@ -408,16 +400,19 @@ impl<'a> Environment<'a> { errors: &mut self.errors, id: next_id!(), }; + // Save the result of this macro invocation. values.push(env.parse_macro_definition_body(&definition.body, source)?); } if values.len() == 1 { + // If the macro was invoked once, return the value. values.pop() } else { - // Flatten all values into a list of block tokens. + // If the macro was invoked multiple times, create a list of + // block tokens from the returned values. let mut block = Vec::new(); for value in values { match value.value { - IntermediateValue::Integer(_) => { + IntermediateValue::Integer(_) | IntermediateValue::String(_) => { let error = IntermediateError::ExpectedBlock; self.errors.push(Tracked::from(error, value.source)); return None; @@ -440,6 +435,58 @@ impl<'a> Environment<'a> { } } + fn parse_invocation_argument(&mut self, argument: &Tracked<InvocationArgument>, expected_type: ArgumentType) -> Option<RepeatedArgument> { + let source = &argument.source; + let (received_value, received_type) = match &argument.value { + InvocationArgument::StringToken(string) => { + let string = self.parse_string_token(string, source)?; + self.parse_invocation_string_argument(string, expected_type)? + } + InvocationArgument::IntegerToken(integer) => { + let tracked = self.parse_integer_token(&integer, &argument.source)?; + let value = IntermediateValue::Integer(tracked); + (RepeatedArgument::Loop(value), ArgumentType::Integer) + } + InvocationArgument::BlockToken(block) => { + let tokens = self.parse_block_token(&block, &argument.source); + let value = IntermediateValue::Block(tokens); + (RepeatedArgument::Loop(value), ArgumentType::Block) + } + InvocationArgument::Invocation(invocation) => { + let value = self.parse_invocation(&invocation, &argument.source)?; + match value.value { + IntermediateValue::Integer(_) => + (RepeatedArgument::Loop(value.value), ArgumentType::Integer), + IntermediateValue::Block(_) => + (RepeatedArgument::Loop(value.value), ArgumentType::Block), + IntermediateValue::String(string) => + self.parse_invocation_string_argument(string, expected_type)? + } + } + }; + if expected_type != received_type { + let error = IntermediateError::IncorrectArgumentType(expected_type, received_type); + self.errors.push(Tracked::from(error, argument.source.clone())); + return None; + } + return Some(received_value); + } + + fn parse_invocation_string_argument(&mut self, string: Tracked<StringLiteral>, expected_type: ArgumentType) -> Option<(RepeatedArgument, ArgumentType)> { + if let ArgumentType::Integer = expected_type { + let mut values = Vec::new(); + for c in &string.chars { + let integer = IntermediateInteger::Integer(**c); + let tracked = Tracked::from(integer, c.source.clone()); + values.push(IntermediateValue::Integer(tracked)); + } + Some((RepeatedArgument::List(values), ArgumentType::Integer)) + } else { + let value = IntermediateValue::String(string); + Some((RepeatedArgument::Loop(value), ArgumentType::String)) + } + } + fn parse_expression(&mut self, expression: &Expression, source: &SourceSpan) -> Option<Tracked<IntermediateInteger>> { let mut intermediate = Vec::new(); let mut error = false; diff --git a/src/stages/intermediate_tokens.rs b/src/stages/intermediate_tokens.rs index a09581e..8b7cfaf 100644 --- a/src/stages/intermediate_tokens.rs +++ b/src/stages/intermediate_tokens.rs @@ -4,7 +4,7 @@ use crate::*; #[derive(Clone)] pub enum IntermediateToken { Word(IntermediateWord), - PinnedAddress(Tracked<usize>), + PinnedAddress(Tracked<IntermediateInteger>), LabelDefinition(String), } @@ -47,6 +47,7 @@ pub enum IntermediateExpressionToken { pub enum IntermediateValue { Integer(Tracked<IntermediateInteger>), Block(Vec<Tracked<IntermediateToken>>), + String(Tracked<StringLiteral>), } pub enum RepeatedArgument { @@ -66,6 +67,7 @@ impl RepeatedArgument { pub enum IntermediateError { ExpectedInteger, ExpectedBlock, + ExpectedString, ListExhausted, LabelReferenceInConditionPredicate, LabelDefinitionInConditionBody, @@ -91,6 +93,8 @@ fn report_intermediate_error(error: &Tracked<IntermediateError>, source_code: &s "An integer value was expected here", IntermediateError::ExpectedBlock => "A block value was expected here", + IntermediateError::ExpectedString => + "A string value was expected here", IntermediateError::ListExhausted => "This string is shorter than another string passed to the same invocation", IntermediateError::LabelReferenceInConditionPredicate => @@ -121,8 +125,10 @@ pub fn print_intermediate_token(i: usize, token: &IntermediateToken) { print_intermediate_integer(i+1, &field.value.value); } } - IntermediateToken::PinnedAddress(address) => - indent!(i, "PinnedAddress({address})"), + IntermediateToken::PinnedAddress(address) => { + indent!(i, "PinnedAddress"); + print_intermediate_integer(i+1, address); + } IntermediateToken::LabelDefinition(name) => indent!(i, "LabelDefinition({name})"), } diff --git a/src/stages/semantic.rs b/src/stages/semantic.rs index 3c98192..6cd83f8 100644 --- a/src/stages/semantic.rs +++ b/src/stages/semantic.rs @@ -137,13 +137,18 @@ impl SemanticParser { for token in tokens { match token { MacroDefinitionBody::Integer(integer) => { - let error = SemanticError::ExpectedInteger(location); + let error = SemanticError::ExpectedBlock(location); let tracked = Tracked::from(error, integer.source); self.errors.push(tracked); } MacroDefinitionBody::Block(mut tokens) => { block_tokens.append(&mut tokens); } + MacroDefinitionBody::String(string) => { + let error = SemanticError::ExpectedBlock(location); + let tracked = Tracked::from(error, string.source); + self.errors.push(tracked); + } MacroDefinitionBody::Invocation(invocation) => { // Convert invocation to a block invocation. let token = BlockToken::Invocation(invocation.value); @@ -178,10 +183,10 @@ impl SemanticParser { let tracked = Tracked::from(token, source); Some(MacroDefinitionBody::Integer(tracked)) } - SyntacticToken::StringLiteral(_) => { - let error = SemanticError::MisplacedStringLiteral; - self.errors.push(Tracked::from(error, source)); - None + SyntacticToken::StringLiteral(value) => { + let token = StringToken::StringLiteral(value); + let tracked = Tracked::from(token, source); + Some(MacroDefinitionBody::String(tracked)) } SyntacticToken::WordTemplate(word_template) => { let token = BlockToken::WordTemplate(word_template); @@ -260,6 +265,11 @@ impl SemanticParser { self.errors.push(Tracked::from(error, token.source)); None } + MacroDefinitionBody::String(string) => { + let error = SemanticError::ExpectedInteger(location); + self.errors.push(Tracked::from(error, string.source)); + None + } } } @@ -281,6 +291,11 @@ impl SemanticParser { self.errors.push(Tracked::from(error, integer.source)); None } + MacroDefinitionBody::String(string) => { + let error = SemanticError::ExpectedBlock(location); + self.errors.push(Tracked::from(error, string.source)); + None + } } } @@ -357,7 +372,8 @@ impl SemanticParser { let source = token.source; match token.value { SyntacticToken::StringLiteral(string_literal) => { - let argument = InvocationArgument::String(string_literal); + let string = StringToken::StringLiteral(string_literal); + let argument = InvocationArgument::StringToken(string); Some(Tracked::from(argument, source)) } SyntacticToken::IntegerLiteral(value) => { @@ -430,6 +446,12 @@ impl SemanticParser { } } } + SyntacticToken::StringLiteral(string) => { + let variant = ArgumentType::String; + let name = string.string; + let definition = ArgumentDefinition { name, variant }; + return Some(Tracked::from(definition, source)); + } _ => (), }; let error = SemanticError::InvalidArgumentDefinition; diff --git a/src/stages/semantic_tokens.rs b/src/stages/semantic_tokens.rs index dfbea1a..bdbc0f9 100644 --- a/src/stages/semantic_tokens.rs +++ b/src/stages/semantic_tokens.rs @@ -17,10 +17,11 @@ pub struct ArgumentDefinition { pub variant: ArgumentType, } -#[derive(PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ArgumentType { Integer, Block, + String, } impl std::fmt::Display for ArgumentType { @@ -28,6 +29,7 @@ impl std::fmt::Display for ArgumentType { match self { ArgumentType::Integer => write!(f, "an integer"), ArgumentType::Block => write!(f, "a block"), + ArgumentType::String => write!(f, "a string"), } } } @@ -35,6 +37,7 @@ impl std::fmt::Display for ArgumentType { pub enum MacroDefinitionBody { Integer(Tracked<IntegerToken>), Block(Vec<Tracked<BlockToken>>), + String(Tracked<StringToken>), Invocation(Tracked<Invocation>), } @@ -68,15 +71,20 @@ pub enum BlockToken { Invocation(Invocation), } +pub enum StringToken { + StringLiteral(StringLiteral), + Invocation(Invocation), +} + pub struct Invocation { pub name: String, pub arguments: Vec<Tracked<InvocationArgument>>, } pub enum InvocationArgument { - String(StringLiteral), IntegerToken(IntegerToken), BlockToken(BlockToken), + StringToken(StringToken), Invocation(Invocation), } @@ -88,6 +96,7 @@ pub enum SemanticError { ExpectedInteger(SemanticLocation), ExpectedBlock(SemanticLocation), + ExpectedString(SemanticLocation), InvalidArgumentDefinition, InvalidInvocationArgument, @@ -153,9 +162,11 @@ fn report_semantic_error(error: &Tracked<SemanticError>, source_code: &str) { &format!("An integer value was expected {location}"), SemanticError::ExpectedBlock(location) => &format!("A block value was expected {location}"), + SemanticError::ExpectedString(location) => + &format!("A string value was expected {location}"), SemanticError::InvalidArgumentDefinition => - "Argument definitions must be in the form 'name' or '{{name}}'", + "Argument definitions must be in the form name, {name}, or \"name\"", SemanticError::InvalidInvocationArgument => "This token cannot be used in an invocation argument", @@ -185,6 +196,9 @@ pub fn print_semantic_token(i: usize, token: &SemanticToken) { MacroDefinitionBody::Block(tokens) => { print_block(i+1, tokens); } + MacroDefinitionBody::String(string) => { + print_string_token(i+1, string); + } MacroDefinitionBody::Invocation(invocation) => { print_invocation(i+1, invocation); } @@ -202,6 +216,9 @@ fn print_argument_definition(i: usize, argument: &ArgumentDefinition) { ArgumentType::Block => { indent!(i, "Argument({}, block)", argument.name) } + ArgumentType::String => { + indent!(i, "Argument({}, string)", argument.name) + } } } @@ -249,8 +266,8 @@ fn print_invocation(i: usize, invocation: &Invocation) { fn print_invocation_argument(i: usize, argument: &InvocationArgument) { match &argument { - InvocationArgument::String(string_literal) => { - indent!(i, "String({string_literal})") + InvocationArgument::StringToken(string) => { + print_string_token(i, string) } InvocationArgument::IntegerToken(integer) => { print_integer_token(i, integer) @@ -278,6 +295,17 @@ fn print_integer_token(i: usize, integer: &IntegerToken) { } } +fn print_string_token(i: usize, string: &StringToken) { + match string { + StringToken::StringLiteral(string_literal) => { + indent!(i, "String({string_literal})") + } + StringToken::Invocation(invocation) => { + print_invocation(i, invocation) + } + } +} + fn print_expression(i: usize, expression: &Expression) { indent!(i, "Expression"); for token in &expression.tokens { diff --git a/src/stages/syntactic.rs b/src/stages/syntactic.rs index 14f8815..a1ba833 100644 --- a/src/stages/syntactic.rs +++ b/src/stages/syntactic.rs @@ -168,22 +168,33 @@ 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 if let Some(octal_string) = stripped.strip_prefix("0o") { + let octal_string = octal_string.to_string(); + match parse_integer_literal(&octal_string, 8, neg) { + Ok(value) => SyntacticToken::IntegerLiteral(value), + Err(_) => err!(SyntacticError::InvalidOctalLiteral(octal_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 +211,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), diff --git a/src/stages/syntactic_tokens.rs b/src/stages/syntactic_tokens.rs index 041c568..5a0ac9e 100644 --- a/src/stages/syntactic_tokens.rs +++ b/src/stages/syntactic_tokens.rs @@ -23,6 +23,7 @@ pub struct SyntacticMacroDefinition { pub body: Vec<Tracked<SyntacticToken>>, } +#[derive(Clone)] pub struct StringLiteral { pub string: String, pub chars: Vec<Tracked<isize>>, @@ -70,6 +71,7 @@ pub enum SyntacticError { InvalidDecimalLiteral(String), InvalidHexadecimalLiteral(String), InvalidBinaryLiteral(String), + InvalidOctalLiteral(String), } @@ -118,6 +120,8 @@ fn report_syntactic_error(error: &Tracked<SyntacticError>, source_code: &str) { &format!("The string '{string}' is not a valid hexadecimal literal"), SyntacticError::InvalidBinaryLiteral(string) => &format!("The string '{string}' is not a valid binary literal"), + SyntacticError::InvalidOctalLiteral(string) => + &format!("The string '{string}' is not a valid octal literal"), }; report_source_issue(LogLevel::Error, &context, message); diff --git a/src/types/expression_stack.rs b/src/types/expression_stack.rs index 4d26eb2..228fa2d 100644 --- a/src/types/expression_stack.rs +++ b/src/types/expression_stack.rs @@ -61,11 +61,29 @@ impl ExpressionStack { Operator::BitOr => { pop!(b); pop!(a); push!(a | b) }, Operator::BitXor => { pop!(b); pop!(a); push!(a ^ b) }, Operator::BitNot => { pop!(a); push!(!a) }, + Operator::Length => { pop!(a); push!(width(a) as isize) }, + Operator::Tally => { pop!(a); push!(tally(a) as isize) }, + Operator::Absolute => { pop!(a); push!(a.wrapping_abs()) }, } return Ok(()); } } +/// Find the number of bits required to hold an integer. +pub fn width(value: isize) -> u32 { + match value.cmp(&0) { + std::cmp::Ordering::Less => (-value).ilog2() + 2, + std::cmp::Ordering::Equal => 0, + std::cmp::Ordering::Greater => value.ilog2() + 1, + } +} + +pub fn tally(value: isize) -> u32 { + let width = width(value); + let mask = 2i32.pow(width) -1; + let value = (value as usize) & (mask as usize); + return value.count_ones(); +} pub enum StackError { Underflow, diff --git a/src/types/operator.rs b/src/types/operator.rs index a7e7b9b..da4dfb4 100644 --- a/src/types/operator.rs +++ b/src/types/operator.rs @@ -18,6 +18,9 @@ pub enum Operator { BitOr, BitXor, BitNot, + Length, + Tally, + Absolute, } impl Operator { @@ -55,6 +58,9 @@ impl Operator { "<or>" => Some(Operator::BitOr), "<xor>" => Some(Operator::BitXor), "<not>" => Some(Operator::BitNot), + "<len>" => Some(Operator::Length), + "<tal>" => Some(Operator::Tally), + "<abs>" => Some(Operator::Absolute), _ => None, } } @@ -81,6 +87,9 @@ impl std::fmt::Display for Operator { Operator::BitOr => "<or>", Operator::BitXor => "<xor>", Operator::BitNot => "<not>", + Operator::Length => "<len>", + Operator::Tally => "<tal>", + Operator::Absolute => "<abs>", }; write!(f, "{string}") } |