diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/tq.rs | 13 | ||||
-rw-r--r-- | src/formats/cmd.rs | 54 | ||||
-rw-r--r-- | src/formats/mod.rs | 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 |
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") + } } @@ -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}") } |