use crate::*;
use std::collections::{HashMap, HashSet};
pub fn parse_semantic(syntactic: Vec<Tracked<SyntacticToken>>) -> Result<Program, Vec<Tracked<SemanticError>>> {
// Record all label definitions and macro names up front.
let mut definitions = HashMap::new();
let mut macro_names = HashSet::new();
for token in &syntactic {
match &token.value {
SyntacticToken::LabelDefinition(name) => {
let name = name.clone();
let definition = Definition::new(0, DefinitionKind::LabelDefinition);
let tracked = Tracked::from(definition, token.source.clone());
if let Some(_) = definitions.insert(name.clone(), tracked) {
unreachable!("Uncaught duplicate label definition '{name}'");
}
}
SyntacticToken::MacroDefinition(definition) => {
let name = definition.name.clone();
if !macro_names.insert(name.clone()) {
unreachable!("Uncaught duplicate macro definition '{name}'")
}
}
_ => (),
}
}
// Convert syntactic tokens to semantic tokens.
let mut tokens: Vec<Tracked<SemanticToken>> = Vec::new();
let mut errors = Vec::new();
let mut stack = Vec::new();
for syn_token in syntactic {
let i = tokens.len();
let sem_token = match syn_token.value {
SyntacticToken::Comment(string) => SemanticToken::Comment(string),
SyntacticToken::LabelDefinition(name) => {
let definition = definitions.get_mut(&name).unwrap();
definition.value.definition = i;
SemanticToken::LabelDefinition(name)
}
SyntacticToken::MacroDefinition(definition) => {
let source = definition.name.source.clone();
let name = definition.name.clone();
let mut body: Vec<Tracked<SemanticToken>> = Vec::new();
let mut body_stack = Vec::new();
for syn_token in definition.body {
let j = body.len();
let sem_token = match syn_token.value {
SyntacticToken::Comment(string) =>
SemanticToken::Comment(string),
SyntacticToken::LabelDefinition(label) =>
unreachable!("Uncaught label definition '{label}' in macro '{name}'"),
SyntacticToken::MacroDefinition(definition) =>
unreachable!("Uncaught macro definition '{}' in macro '{name}'", definition.name),
SyntacticToken::RawValue(value) => SemanticToken::RawValue(value),
SyntacticToken::Instruction(instruction) => SemanticToken::Instruction(instruction),
SyntacticToken::Invocation(symbol) => {
if let Some(definition) = definitions.get_mut(&symbol) {
definition.value.deep_references.push((i, j));
} else if let Some(definition) = macro_names.get(&symbol) {
let error = SemanticError::InvocationBeforeDefinition;
let source = syn_token.source.wrap(definition.source.clone());
errors.push(Tracked::from(error, source));
} else {
unreachable!("Uncaught undefined symbol '{symbol}'");
};
SemanticToken::Invocation(symbol)
}
SyntacticToken::Padding(value) => SemanticToken::Padding(value),
SyntacticToken::String(bytes) => SemanticToken::String(bytes),
SyntacticToken::BlockOpen => {
body_stack.push(j);
SemanticToken::BlockOpen(0)
}
SyntacticToken::BlockClose => {
let Some(k) = body_stack.pop() else {
unreachable!("Uncaught unmatched block terminator in macro {name}");
};
body[k].value = SemanticToken::BlockOpen(j);
SemanticToken::BlockClose(k)
}
};
body.push(Tracked::from(sem_token, syn_token.source));
}
let kind = DefinitionKind::MacroDefinition(body);
let tracked = Tracked::from(Definition::new(i, kind), source);
if let Some(_) = definitions.insert(name.value.clone(), tracked) {
unreachable!("Uncaught duplicate definition '{name}'")
};
if !body_stack.is_empty() {
unreachable!("Uncaught unterminated block in macro {name}");
}
SemanticToken::MacroDefinition(name)
}
SyntacticToken::RawValue(value) => SemanticToken::RawValue(value),
SyntacticToken::Instruction(instruction) => SemanticToken::Instruction(instruction),
SyntacticToken::Invocation(symbol) => {
if let Some(definition) = definitions.get_mut(&symbol) {
definition.value.references.push(i);
} else if let Some(definition) = macro_names.get(&symbol) {
let error = SemanticError::InvocationBeforeDefinition;
let source = syn_token.source.wrap(definition.source.clone());
errors.push(Tracked::from(error, source));
} else {
unreachable!("Uncaught undefined symbol '{symbol}'");
};
SemanticToken::Invocation(symbol)
}
SyntacticToken::Padding(value) => SemanticToken::Padding(value),
SyntacticToken::String(bytes) => SemanticToken::String(bytes),
SyntacticToken::BlockOpen => {
stack.push(i);
SemanticToken::BlockOpen(0)
}
SyntacticToken::BlockClose => {
let Some(k) = stack.pop() else {
unreachable!("Uncaught unmatched block terminator");
};
tokens[k].value = SemanticToken::BlockOpen(i);
SemanticToken::BlockClose(k)
}
};
tokens.push(Tracked::from(sem_token, syn_token.source));
}
if !stack.is_empty() {
unreachable!("Uncaught unterminated block");
}
match errors.is_empty() {
true => Ok(Program { definitions, tokens }),
false => Err(errors),
}
}