diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2025-02-15 08:20:18 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2025-02-15 08:20:18 +1300 |
commit | 4e8fae09f0f7d6f3a4ddbe285aeb01ef0622b761 (patch) | |
tree | 79eab5ef0f5d3eee7949a317db50489dac2fce99 | |
parent | e39505931b05be321ee2b04c41a9739f00c19208 (diff) | |
download | torque-asm-4e8fae09f0f7d6f3a4ddbe285aeb01ef0622b761.zip |
Implement semantic error reporting
-rw-r--r-- | src/main.rs | 85 | ||||
-rw-r--r-- | src/parsers/semantic.rs | 79 | ||||
-rw-r--r-- | src/parsers/syntactic.rs | 2 | ||||
-rw-r--r-- | src/tokens/semantic.rs | 89 |
4 files changed, 216 insertions, 39 deletions
diff --git a/src/main.rs b/src/main.rs index 342057d..e7e52d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,6 @@ pub use environment::*; pub use parsers::*; pub use tokens::*; - pub use assembler::*; use log::{info, fatal}; use switchboard::{Switchboard, SwitchQuery}; @@ -87,8 +86,6 @@ fn main() { std::process::exit(1); } - compiler.resolver.unused().report(); - let merged_source = compiler.get_compiled_source().unwrap_or_else( |error| { error.report(); std::process::exit(1); } ); @@ -105,7 +102,7 @@ fn main() { report_syntactic_errors(&syntactic_tokens, &merged_source); let program = ProgramParser::new(syntactic_tokens).parse(); - program.print_definitions(); + report_semantic_errors(&program, &merged_source); } @@ -173,3 +170,83 @@ fn report_syntactic_errors(syntactic_tokens: &[syntactic::Token], source_code: & } } } + + +fn report_semantic_errors(program: &semantic::Program, source_code: &str) { + for error in &program.errors { + report_parse_error(error, source_code); + } + for definition in &program.definitions { + report_definition_errors(&definition.variant, source_code); + } + for invocation in &program.invocations { + report_invocation_errors(invocation, source_code); + } +} + +fn report_parse_error(error: &semantic::ParseError, source_code: &str) { + use semantic::*; + let message = match &error.variant { + ParseErrorVariant::UnterminatedMacroDefinition(name) => + format!("The macro definition '{name}' is missing a terminating ';' character"), + ParseErrorVariant::UnterminatedBlockDefinition => + format!("Block literal is missing a terminating '}}' character"), + ParseErrorVariant::InvalidArgumentDefinition(name) => + format!("The macro definition '{name}' has an invalid argument definition"), + ParseErrorVariant::InvalidToken => + format!("Invalid token"), + }; + let context = Context { source: &error.source, source_code}; + report_source_issue(LogLevel::Error, &context, &message); +} + +fn report_definition_errors(definition: &semantic::DefinitionVariant, source_code: &str) { + use semantic::*; + match definition { + DefinitionVariant::Integer(integer) => match &integer.variant { + IntegerDefinitionVariant::Constant(expr) => for token in &expr.tokens { + if let ConstantExpressionTokenVariant::Error(error) = &token.variant { + let message = match error { + ConstantExpressionParseError::InvalidHexadecimalLiteral(hex) => + format!("Invalid hexadecimal literal '{hex}' in constant expression"), + }; + let context = Context { source: &token.source, source_code}; + report_source_issue(LogLevel::Error, &context, &message); + } + } + _ => (), + } + DefinitionVariant::Block(block) => { + for error in &block.errors { + report_parse_error(&error, source_code); + } + for token in &block.tokens { + match &token.variant { + BlockTokenVariant::Word(pbl) => for error in &pbl.errors { + let message = match error.variant { + PackedBinaryLiteralParseErrorVariant::DuplicateFieldName(name) => + format!("Duplicate field name '{name}' in packed binary literal"), + PackedBinaryLiteralParseErrorVariant::InvalidCharacter(c) => + format!("Invalid character '{c}' in packed binary literal"), + }; + let context = Context { source: &error.source, source_code }; + report_source_issue(LogLevel::Error, &context, &message); + } + BlockTokenVariant::Invocation(invocation) => + report_invocation_errors(invocation, source_code), + BlockTokenVariant::Comment(_) => (), + } + } + } + DefinitionVariant::Reference(_) => (), + } +} + +fn report_invocation_errors(invocation: &semantic::Invocation, source_code: &str) { + for error in &invocation.errors { + report_parse_error(&error, source_code); + } + for argument in &invocation.arguments { + report_definition_errors(argument, source_code); + } +} diff --git a/src/parsers/semantic.rs b/src/parsers/semantic.rs index 44861e1..7ef4a4a 100644 --- a/src/parsers/semantic.rs +++ b/src/parsers/semantic.rs @@ -43,7 +43,7 @@ impl Tokens { } /// Pull tokens until the predicate returns true, otherwise return Err. - pub fn pull_until(&mut self, predicate: fn(&syn::Token) -> bool) -> Result<Self, ()> { + pub fn pull_until(&mut self, mut predicate: impl FnMut(&syn::Token) -> bool) -> Result<Self, ()> { let mut output = VecDeque::new(); while let Some(token) = self.tokens.pop_front() { match predicate(&token) { @@ -83,18 +83,30 @@ impl ProgramParser { pub fn parse(mut self) -> Program { while let Some(syn) = self.tokens.pop() { - if let SynVar::MacroDefinition(name) = syn.variant { - // Collect all tokens up to the next definition terminator. - let Ok(definition_tokens) = self.tokens.pull_until(is_terminator) else { - let variant = ParseErrorVariant::UnterminatedMacroDefinition; + match syn.variant { + SynVar::MacroDefinition(name) => { + // Collect all tokens up to the next definition terminator. + let Ok(definition_tokens) = self.tokens.pull_until(is_terminator) else { + let variant = ParseErrorVariant::UnterminatedMacroDefinition(name); + self.errors.push(ParseError { source: syn.source, variant}); + break; + }; + // Parse macro definition arguments. + match DefinitionParser::new(name, syn.source, definition_tokens).parse() { + Ok(definition) => self.definitions.push(definition), + Err(errors) => self.errors.extend(errors), + }; + } + SynVar::Comment(_) => (), + SynVar::Symbol(name) => { + let parser = InvocationParser::new(name, &mut self.tokens); + self.invocations.push(parser.parse()); + } + _ => { + let variant = ParseErrorVariant::InvalidToken; self.errors.push(ParseError { source: syn.source, variant}); break; - }; - // Parse macro definition arguments. - match DefinitionParser::new(name, syn.source, definition_tokens).parse() { - Ok(definition) => self.definitions.push(definition), - Err(errors) => self.errors.extend(errors), - }; + } } } @@ -222,11 +234,8 @@ impl BlockParser { let source = token.source; match token.variant { SynVar::Symbol(name) => { - let mut arguments = Vec::new(); - while let Some(argument) = self.parse_invocation_argument() { - arguments.push(argument); - } - let invocation = Invocation { name, arguments }; + let parser = InvocationParser::new(name, &mut self.tokens); + let invocation = parser.parse(); let variant = BlockTokenVariant::Invocation(invocation); let block_token = BlockToken { source, variant }; self.block_tokens.push(block_token); @@ -244,6 +253,31 @@ impl BlockParser { } BlockDefinition { tokens: self.block_tokens, errors: self.errors } } +} + + +struct InvocationParser<'a> { + name: String, + tokens: &'a mut Tokens, + arguments: Vec<DefinitionVariant>, + errors: Vec<ParseError>, +} + +impl<'a> InvocationParser<'a> { + pub fn new(name: String, tokens: &'a mut Tokens) -> Self { + Self { name, tokens, arguments: Vec::new(), errors: Vec::new() } + } + + pub fn parse(mut self) -> Invocation { + while let Some(argument) = self.parse_invocation_argument() { + self.arguments.push(argument); + } + Invocation { + name: self.name, + arguments: self.arguments, + errors: self.errors, + } + } fn parse_invocation_argument(&mut self) -> Option<DefinitionVariant> { // Only continue if the first token is a separator. @@ -251,7 +285,17 @@ impl BlockParser { if let Some(block_open) = self.tokens.pop_if(is_block_open) { let source = block_open.source; - if let Ok(block_tokens) = self.tokens.pull_until(is_block_close) { + let mut depth = 1; + let is_matching_block_close = |token: &syntactic::Token| { + match token.variant { + syntactic::TokenVariant::BlockOpen => { + depth += 1; false } + syntactic::TokenVariant::BlockClose => { + depth -= 1; depth == 0 } + _ => false, + } + }; + if let Ok(block_tokens) = self.tokens.pull_until(is_matching_block_close) { let block = BlockParser::new(block_tokens).parse(); Some(DefinitionVariant::Block(block)) } else { @@ -283,7 +327,6 @@ impl BlockParser { None } } - } } } diff --git a/src/parsers/syntactic.rs b/src/parsers/syntactic.rs index c98a592..909dbaa 100644 --- a/src/parsers/syntactic.rs +++ b/src/parsers/syntactic.rs @@ -94,7 +94,7 @@ impl Iterator for SyntacticParser { // Check if the comment fills the entire line. if t.start_position.column == 0 && t.end_of_line() { if let Some(path) = comment.strip_prefix(": ") { - t.source_path = Some(PathBuf::from(path.trim())); + t.embedded_path = Some(PathBuf::from(path.trim())); t.embedded_first_line = t.start_position.line + 1; } } diff --git a/src/tokens/semantic.rs b/src/tokens/semantic.rs index 66db7b2..7d5d327 100644 --- a/src/tokens/semantic.rs +++ b/src/tokens/semantic.rs @@ -68,6 +68,7 @@ pub struct ReferenceDefinition { pub struct Invocation { pub name: String, pub arguments: Vec<DefinitionVariant>, + pub errors: Vec<ParseError>, } pub struct ParseError { @@ -76,7 +77,7 @@ pub struct ParseError { } pub enum ParseErrorVariant { - UnterminatedMacroDefinition, + UnterminatedMacroDefinition(String), UnterminatedBlockDefinition, /// Name of the macro. InvalidArgumentDefinition(String), @@ -86,29 +87,85 @@ pub enum ParseErrorVariant { // ------------------------------------------------------------------------ // +macro_rules! indent { + ($indent:expr => $($tokens:tt)*) => {{ + for _ in 0..$indent { print!(" "); } + println!($($tokens)*); + }}; +} + impl Program { pub fn print_definitions(&self) { for definition in &self.definitions { - let variant = match definition.variant { - DefinitionVariant::Integer(_) => "integer", - DefinitionVariant::Block(_) => "block", - DefinitionVariant::Reference(_) => "reference", + let variant = match &definition.variant { + DefinitionVariant::Integer(_) => "INTEGER", + DefinitionVariant::Block(_) => "BLOCK", + DefinitionVariant::Reference(_) => "REFERENCE", }; - println!("DEFINE {} ({variant})", definition.name); + println!("DEFINE {variant} '{}'", definition.name); for argument in &definition.arguments { - let variant = match argument.variant { - ArgumentDefinitionVariant::Integer => "integer", - ArgumentDefinitionVariant::Block => "block", - }; - println!(" ARGUMENT {} ({variant})", argument.name); + self.print_argument_definition(argument); } - let variant = match &definition.variant { - DefinitionVariant::Integer(integer) => todo!(), - DefinitionVariant::Block(block) => todo!(), - DefinitionVariant::Reference(reference) => todo!() + match &definition.variant { + DefinitionVariant::Integer(integer) => + self.print_integer_definition(1, integer), + DefinitionVariant::Block(block) => + self.print_block_definition(1, block), + DefinitionVariant::Reference(reference) => + indent!(1 => "REFERENCE '{}'", reference.name), }; - println!(); } + + for invocation in &self.invocations { + self.print_invocation(0, invocation); + } + } + + fn print_argument_definition(&self, argument: &ArgumentDefinition) { + let variant = match argument.variant { + ArgumentDefinitionVariant::Integer => "INTEGER", + ArgumentDefinitionVariant::Block => "BLOCK", + }; + println!(" ARGUMENT {variant} '{}'", argument.name); + } + + fn print_integer_definition(&self, indent: usize, definition: &IntegerDefinition) { + match &definition.variant { + IntegerDefinitionVariant::Literal(value) => + indent!(indent => "LITERAL {value}"), + IntegerDefinitionVariant::Constant(expr) => + indent!(indent => "CONSTANT [{expr:?}]"), + } + } + + fn print_block_definition(&self, indent: usize, definition: &BlockDefinition) { + indent!(indent => "BLOCK"); + let indent = indent + 1; + for token in &definition.tokens { + match &token.variant { + BlockTokenVariant::Invocation(invocation) => + self.print_invocation(indent, invocation), + BlockTokenVariant::Comment(_) => + indent!(indent => "COMMENT"), + BlockTokenVariant::Word(word) => + indent!(indent => "WORD #{word}"), + } + } + } + + fn print_invocation(&self, indent: usize, invocation: &Invocation) { + indent!(indent => "INVOCATION '{}'", invocation.name); + let indent = indent + 1; + for argument in &invocation.arguments { + match &argument { + DefinitionVariant::Integer(integer) => + self.print_integer_definition(indent, integer), + DefinitionVariant::Block(block) => + self.print_block_definition(indent, block), + DefinitionVariant::Reference(reference) => + indent!(indent => "REFERENCE '{}'", reference.name), + }; + } } } |