summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock126
-rw-r--r--Cargo.toml7
-rw-r--r--LICENSE.md9
-rw-r--r--rust-toolchain.toml3
-rw-r--r--src/formats/mod.rs2
-rw-r--r--src/lib.rs2
-rw-r--r--src/stages/bytecode.rs156
-rw-r--r--src/stages/bytecode_tokens.rs6
-rw-r--r--src/stages/compiler.rs16
-rw-r--r--src/stages/intermediate.rs207
-rw-r--r--src/stages/intermediate_tokens.rs12
-rw-r--r--src/stages/semantic.rs34
-rw-r--r--src/stages/semantic_tokens.rs38
-rw-r--r--src/stages/syntactic.rs6
-rw-r--r--src/stages/syntactic_tokens.rs4
-rw-r--r--src/types/expression_stack.rs18
-rw-r--r--src/types/operator.rs9
17 files changed, 506 insertions, 149 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 541cac1..bc53a0a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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.2"
+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"
diff --git a/Cargo.toml b/Cargo.toml
index ca18d3a..41e6a51 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,11 +1,12 @@
[package]
name = "torque-asm"
-version = "2.0.2"
+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/formats/mod.rs b/src/formats/mod.rs
index 105207c..e15bfbd 100644
--- a/src/formats/mod.rs
+++ b/src/formats/mod.rs
@@ -12,8 +12,6 @@ pub use debug::*;
use crate::*;
-use log::*;
-
#[derive(Clone, Copy, PartialEq)]
pub enum Format {
diff --git a/src/lib.rs b/src/lib.rs
index 420bb61..f0de207 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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 e4464a1..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() + 2,
- 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 45d5e60..a1ba833 100644
--- a/src/stages/syntactic.rs
+++ b/src/stages/syntactic.rs
@@ -184,6 +184,12 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser) -> Result<Vec<Tracked<Syntac
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 {
let decimal_string = stripped.to_string();
match parse_integer_literal(&decimal_string, 10, neg) {
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}")
}