summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2025-02-15 08:20:18 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2025-02-15 08:20:18 +1300
commit4e8fae09f0f7d6f3a4ddbe285aeb01ef0622b761 (patch)
tree79eab5ef0f5d3eee7949a317db50489dac2fce99
parente39505931b05be321ee2b04c41a9739f00c19208 (diff)
downloadtorque-asm-4e8fae09f0f7d6f3a4ddbe285aeb01ef0622b761.zip
Implement semantic error reporting
-rw-r--r--src/main.rs85
-rw-r--r--src/parsers/semantic.rs79
-rw-r--r--src/parsers/syntactic.rs2
-rw-r--r--src/tokens/semantic.rs89
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),
+ };
+ }
}
}