summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/tq.rs13
-rw-r--r--src/formats/cmd.rs54
-rw-r--r--src/formats/mod.rs15
-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.rs30
-rw-r--r--src/stages/syntactic_tokens.rs4
-rw-r--r--src/types/expression_stack.rs18
-rw-r--r--src/types/operator.rs9
15 files changed, 471 insertions, 143 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..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")
+ }
}
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 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}")
}