use crate::*;
use indexmap::IndexMap;
/// The entire semantic program, ready to generate bytecode.
pub struct SemanticProgram {
pub macro_definitions: IndexMap<String, MacroDefinition>,
pub label_definitions: IndexMap<String, LabelDefinition>,
pub body: Vec<SemanticToken>,
}
/// A symbol definition.
pub struct MacroDefinition {
pub source: SourceSpan,
pub arguments: Vec<ArgumentDefinition>,
pub value: Value,
pub errors: Vec<SemanticParseError>,
}
pub struct ArgumentDefinition {
pub name: String,
pub source: SourceSpan,
pub variant: ArgumentVariant,
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum ArgumentVariant {
Integer,
Block,
}
pub struct ArgumentInvocation {
pub source: SourceSpan,
pub value: Value,
}
pub enum Value {
Integer(Integer),
Block(Vec<SemanticToken>),
Invocation(Invocation),
}
pub enum Integer {
Literal(TrackedInteger),
String(TrackedString),
Expression(Expression),
LabelReference(Tracked<String>),
}
pub enum SemanticToken {
Word(PackedBinaryLiteral),
Invocation(Invocation),
LabelDefinition(LabelDefinition),
PinnedAddress(PinnedAddress),
Error(SemanticParseError),
}
pub struct Invocation {
pub name: String,
pub source: SourceSpan,
pub arguments: Vec<ArgumentInvocation>,
pub errors: Vec<SemanticParseError>,
}
#[derive(Clone)]
pub struct LabelDefinition {
pub source: SourceSpan,
pub name: String,
}
#[derive(Clone)]
pub struct PinnedAddress {
pub source: SourceSpan,
pub address: usize,
}
pub struct SemanticParseError {
pub source: SourceSpan,
pub variant: SemanticParseErrorVariant,
}
pub enum SemanticParseErrorVariant {
UnterminatedMacroDefinition(String),
UnterminatedBlock,
InvalidToken,
}
impl std::fmt::Display for ArgumentVariant {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
ArgumentVariant::Integer => write!(f, "integer"),
ArgumentVariant::Block => write!(f, "block"),
}
}
}
// ------------------------------------------------------------------------ //
macro_rules! indent {
($indent:expr => $($tokens:tt)*) => {{
for _ in 0..$indent { print!(" "); }
println!($($tokens)*);
}};
}
impl SemanticProgram {
pub fn print_definitions(&self) {
for (name, definition) in &self.macro_definitions {
let variant = match &definition.value {
Value::Integer(_) => "INTEGER",
Value::Block(_) => "BLOCK",
Value::Invocation(_) => "INVOCATION",
};
println!("DEFINE {variant} '{name}'");
for argument in &definition.arguments {
self.print_argument_definition(argument);
}
match &definition.value {
Value::Integer(integer) =>
self.print_integer(1, integer),
Value::Block(block) =>
self.print_block(1, block),
Value::Invocation(invocation) =>
indent!(1 => "INVOCATION '{}'", invocation.name),
};
println!();
}
println!("LABELS");
for (name, _) in &self.label_definitions {
println!(" @{name}");
}
println!();
self.print_block(0, &self.body);
}
fn print_argument_definition(&self, argument: &ArgumentDefinition) {
let variant = match argument.variant {
ArgumentVariant::Integer => "INTEGER",
ArgumentVariant::Block => "BLOCK",
};
println!(" ARGUMENT {variant} '{}'", argument.name);
}
fn print_integer(&self, indent: usize, integer: &Integer) {
match &integer {
Integer::Literal(value) =>
indent!(indent => "LITERAL {value}"),
Integer::Expression(expr) =>
indent!(indent => "EXPRESSION [{expr:?}]"),
Integer::String(string) =>
indent!(indent => "STRING '{string}'"),
Integer::LabelReference(name) =>
indent!(indent => "LABEL REFERENCE '{name}'"),
}
}
fn print_block(&self, indent: usize, block: &[SemanticToken]) {
indent!(indent => "BLOCK");
for semantic_token in block {
match &semantic_token {
SemanticToken::Word(word) =>
indent!(indent+1 => "WORD #{word}"),
SemanticToken::Invocation(invocation) =>
self.print_invocation(indent+1, invocation),
SemanticToken::LabelDefinition(definition) =>
indent!(indent+1 => "LABEL DEFINITION @{}", definition.name),
SemanticToken::PinnedAddress(addr) =>
indent!(indent+1 => "PINNED ADDRESS {}", addr.address),
SemanticToken::Error(_) =>
indent!(indent+1 => "ERROR"),
}
}
}
fn print_invocation(&self, indent: usize, invocation: &Invocation) {
indent!(indent => "INVOCATION '{}'", invocation.name);
for argument in &invocation.arguments {
match &argument.value {
Value::Integer(integer) =>
self.print_integer(indent+1, integer),
Value::Block(block) =>
self.print_block(indent+1, block),
Value::Invocation(invocation) =>
self.print_invocation(indent+1, invocation),
};
}
}
}