diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/br-asm.rs | 21 | ||||
| -rw-r--r-- | src/lib.rs | 37 | ||||
| -rw-r--r-- | src/stages/bytecode.rs | 24 | ||||
| -rw-r--r-- | src/stages/semantic.rs | 21 | ||||
| -rw-r--r-- | src/stages/semantic_tokens.rs | 9 | ||||
| -rw-r--r-- | src/stages/syntactic.rs | 21 | ||||
| -rw-r--r-- | src/stages/syntactic_tokens.rs | 2 |
7 files changed, 65 insertions, 70 deletions
diff --git a/src/bin/br-asm.rs b/src/bin/br-asm.rs index ced4245..e7a9230 100644 --- a/src/bin/br-asm.rs +++ b/src/bin/br-asm.rs @@ -3,25 +3,6 @@ use switchboard::*; fn main() { - let mut args = Switchboard::from_env(); - args.named("version"); - args.named("verbose").short('v'); - - if args.get("version").as_bool() { - print_version(); - } - if args.get("verbose").as_bool() { - log::set_log_level(log::LogLevel::Info); - } - + let args = Switchboard::from_env(); assemble(args, "br-asm"); } - - -fn print_version() -> ! { - let name = env!("CARGO_PKG_NAME"); - let version = env!("CARGO_PKG_VERSION"); - eprintln!("{name} v{version}"); - eprintln!("Written by Ben Bridle."); - std::process::exit(0); -} @@ -20,7 +20,26 @@ pub const WIDE_MODE: u8 = 0x40; pub const IMMEDIATE_MODE: u8 = 0x20; -pub fn assemble(mut args: Switchboard, invocation: &str) { +pub fn assemble(mut args: Switchboard, invocation: &str) -> ! { + args.named("help").short('h'); + args.named("version"); + args.named("verbose").short('v'); + + if args.get("help").as_bool() { + print_help(invocation); + std::process::exit(0); + } + if args.get("version").as_bool() { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + eprintln!("{name} v{version}"); + eprintln!("Written by Ben Bridle."); + std::process::exit(0); + } + if args.get("verbose").as_bool() { + log::set_log_level(log::LogLevel::Info); + } + args.positional("source"); args.positional("destination"); args.named("extension").default("brc"); @@ -34,14 +53,8 @@ pub fn assemble(mut args: Switchboard, invocation: &str) { args.named("dry-run").short('n'); args.named("tree"); args.named("with-symbols"); - args.named("help").short('h'); args.raise_errors(); - if args.get("help").as_bool() { - print_help(invocation); - std::process::exit(0); - } - let source_path = args.get("source").as_path_opt().map( |p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}"))); let destination_path = args.get("destination").as_path_opt(); @@ -135,9 +148,10 @@ pub fn assemble(mut args: Switchboard, invocation: &str) { while let Some(0) = bytecode.last() { bytecode.pop(); } - let difference = length - bytecode.len(); + let new_length = bytecode.len(); + let difference = length - new_length; if difference > 0 { - info!("Truncated program to {length} bytes (saved {difference} bytes)"); + info!("Truncated program to {new_length} bytes (saved {difference} bytes)"); } } @@ -169,6 +183,7 @@ pub fn assemble(mut args: Switchboard, invocation: &str) { }; write_bytes_and_exit(&bytes, destination_path.as_ref()); } + std::process::exit(0); } @@ -191,7 +206,7 @@ fn print_help(invocation: &str) { eprintln!("\ Usage: {invocation} [source] [destination] -Convert Bedrock source code into an assembled Bedrock program. +Assembler for the Bedrock computer system. Usage: To assemble a Bedrock program from a source file and write to an output @@ -204,7 +219,7 @@ Usage: Environment variables: BEDROCK_LIBS - A list of colon-separated paths which will be searched to find Bedrock + A list of colon-separated paths that will be searched to find Bedrock source code files to use as libraries when assembling a Bedrock program. If a library file resolves an unresolved symbol in the program being assembled, the library file will be merged into the program. diff --git a/src/stages/bytecode.rs b/src/stages/bytecode.rs index 6878c42..02cc739 100644 --- a/src/stages/bytecode.rs +++ b/src/stages/bytecode.rs @@ -1,6 +1,6 @@ use crate::*; -use std::collections::HashMap; +use indexmap::IndexMap; /// Doesn't truncate trailing null bytes. @@ -14,22 +14,16 @@ pub fn generate_bytecode(semantic: &Program) -> Result<AssembledProgram, Vec<Tra let address = information.address; symbols.push(AssembledSymbol { name, address, source }); } - match generator.errors.is_empty() { - true => Ok( - AssembledProgram { - bytecode: generator.bytecode, - symbols, - } - ), + true => Ok(AssembledProgram { bytecode: generator.bytecode, symbols }), false => Err(generator.errors), } } pub struct BytecodeGenerator<'a> { - definitions: &'a HashMap<String, Tracked<Definition>>, - labels: HashMap<String, LabelInformation>, + definitions: &'a IndexMap<String, Tracked<Definition>>, + labels: IndexMap<String, LabelInformation>, stack: Vec<usize>, bytecode: Vec<u8>, errors: Vec<Tracked<BytecodeError>>, @@ -41,8 +35,8 @@ struct LabelInformation { } impl<'a> BytecodeGenerator<'a> { - pub fn new(definitions: &'a HashMap<String, Tracked<Definition>>) -> Self { - let mut labels = HashMap::new(); + pub fn new(definitions: &'a IndexMap<String, Tracked<Definition>>) -> Self { + let mut labels = IndexMap::new(); for (name, definition) in definitions { if let DefinitionVariant::LabelDefinition = definition.variant { // Use fake address for now. @@ -61,9 +55,7 @@ impl<'a> BytecodeGenerator<'a> { pub fn parse(&mut self, tokens: &[Tracked<SemanticToken>], in_macro: bool) { macro_rules! byte { - ($byte:expr) => { - self.bytecode.push($byte) - }; + ($byte:expr) => { self.bytecode.push($byte) }; } macro_rules! double { ($double:expr) => {{ @@ -140,7 +132,7 @@ impl<'a> BytecodeGenerator<'a> { } } - if !self.stack.is_empty() { + if !in_macro && !self.stack.is_empty() { unreachable!("Uncaught unterminated block"); } } diff --git a/src/stages/semantic.rs b/src/stages/semantic.rs index f2774a4..dc9709e 100644 --- a/src/stages/semantic.rs +++ b/src/stages/semantic.rs @@ -1,15 +1,24 @@ use crate::*; -use std::collections::{HashMap, HashSet}; +use std::str::FromStr; + +use indexmap::{IndexMap, IndexSet}; pub fn parse_semantic(syntactic: Vec<Tracked<SyntacticToken>>) -> Result<Program, Vec<Tracked<SemanticError>>> { + let mut errors = Vec::new(); + // Record all label definitions and macro names up front. - let mut definitions = HashMap::new(); - let mut macro_names = HashSet::new(); + let mut definitions = IndexMap::new(); + let mut macro_names = IndexSet::new(); for token in &syntactic { match &token.value { SyntacticToken::LabelDefinition(name) => { + // Check if identifier is reserved. + if Instruction::from_str(&name).is_ok() { + let error = SemanticError::ReservedIdentifier(name.to_string()); + errors.push(Tracked::from(error, token.source.clone())); + } // Use a fake index for now. let definition = Definition::new(0, DefinitionVariant::LabelDefinition); let tracked = Tracked::from(definition, token.source.clone()); @@ -19,6 +28,11 @@ pub fn parse_semantic(syntactic: Vec<Tracked<SyntacticToken>>) -> Result<Program } SyntacticToken::MacroDefinition(definition) => { let name = &definition.name; + // Check if identifier is reserved. + if Instruction::from_str(&name).is_ok() { + let error = SemanticError::ReservedIdentifier(name.to_string()); + errors.push(Tracked::from(error, name.source.clone())); + } if !macro_names.insert(name.clone()) { unreachable!("Uncaught duplicate macro definition '{name}'") } @@ -29,7 +43,6 @@ pub fn parse_semantic(syntactic: Vec<Tracked<SyntacticToken>>) -> Result<Program // 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 { diff --git a/src/stages/semantic_tokens.rs b/src/stages/semantic_tokens.rs index fe49c26..c735828 100644 --- a/src/stages/semantic_tokens.rs +++ b/src/stages/semantic_tokens.rs @@ -1,10 +1,10 @@ use crate::*; -use std::collections::HashMap; +use indexmap::IndexMap; pub struct Program { - pub definitions: HashMap<String, Tracked<Definition>>, + pub definitions: IndexMap<String, Tracked<Definition>>, pub tokens: Vec<Tracked<SemanticToken>>, } @@ -49,6 +49,7 @@ pub enum SemanticToken { pub enum SemanticError { InvocationBeforeDefinition, + ReservedIdentifier(String), } @@ -64,12 +65,14 @@ fn report_semantic_error(error: &Tracked<SemanticError>, source_code: &str) { let message = match &error.value { SemanticError::InvocationBeforeDefinition => "Macro cannot be invoked before it has been defined", + SemanticError::ReservedIdentifier(name) => + &format!("Identifier '{name}' is reserved for a built-in instruction"), }; report_source_issue(LogLevel::Error, &context, message); } -pub fn print_semantic_token(i: usize, token: &SemanticToken, definitions: &HashMap<String, Tracked<Definition>>) { +pub fn print_semantic_token(i: usize, token: &SemanticToken, definitions: &IndexMap<String, Tracked<Definition>>) { match token { SemanticToken::Literal(value) => indent!(i, "Literal({value})"), SemanticToken::Pad(value) => indent!(i, "Pad({value})"), diff --git a/src/stages/syntactic.rs b/src/stages/syntactic.rs index 6453ae0..59b8b95 100644 --- a/src/stages/syntactic.rs +++ b/src/stages/syntactic.rs @@ -37,7 +37,7 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser, label_name: &str) -> Result< } // Eat characters until the end character is found. - macro_rules! is_any_end { + macro_rules! is_end { ($end:expr) => { |t: &mut Tokeniser| { t.eat_char() == Some($end) @@ -45,15 +45,6 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser, label_name: &str) -> Result< }; } - // Eat characters until the end character is found without a preceding back-slash. - macro_rules! is_plain_end { - ($end:expr) => { - |t: &mut Tokeniser| { - t.eat_if(concat!('\\', $end)).is_some() || t.eat_char() == Some($end) - } - }; - } - loop { // Eat leading whitespace. while let Some(c) = t.peek_char() { @@ -67,7 +58,7 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser, label_name: &str) -> Result< let token = match c { '"' => { let source = t.get_source(); - match t.track_until(is_plain_end!('"')) { + match t.track_until(is_end!('"')) { Some(string) => { let mut bytes = string.into_bytes(); bytes.push(0x00); @@ -78,14 +69,14 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser, label_name: &str) -> Result< } '\'' => { let source = t.get_source(); - match t.track_until(is_plain_end!('\'')) { + match t.track_until(is_end!('\'')) { Some(string) => SyntacticToken::String(string.into_bytes()), None => err!(SyntacticError::UnterminatedRawString, source), } } '(' => { let source = t.get_source(); - if let Some(string) = t.track_until(is_any_end!(')')) { + if let Some(string) = t.track_until(is_end!(')')) { // Check if the comment fills the entire line. if t.start.position.column == 0 && t.end_of_line() { if let Some(path) = string.strip_prefix(": ") { @@ -105,7 +96,7 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser, label_name: &str) -> Result< let source = t.get_source(); check_name!(name, source); t.mark_child(); - if let Some(_) = t.track_until(is_any_end!(';')) { + if let Some(_) = t.track_until(is_end!(';')) { let child = t.tokenise_child_span(); match parse_body_from_tokeniser(child, &label_name) { Ok(body) => { @@ -149,7 +140,7 @@ fn parse_syntactic_from_tokeniser(mut t: Tokeniser, label_name: &str) -> Result< } }, ':' => { - SyntacticToken::Symbol(String::from(':')) + SyntacticToken::Instruction(Instruction { value: 0x21 }) } c => { let token = format!("{c}{}", t.eat_token()); diff --git a/src/stages/syntactic_tokens.rs b/src/stages/syntactic_tokens.rs index 2a95967..35afa80 100644 --- a/src/stages/syntactic_tokens.rs +++ b/src/stages/syntactic_tokens.rs @@ -80,7 +80,7 @@ pub fn print_syntactic_token(i: usize, token: &SyntacticToken) { SyntacticToken::String(bytes) => indent!(i, "String({})", String::from_utf8_lossy(bytes)), SyntacticToken::Comment(_) => indent!(i, "Comment"), SyntacticToken::BlockOpen => indent!(i, "BlockOpen"), - SyntacticToken::BlockClose => indent!(i, "BlockOpen"), + SyntacticToken::BlockClose => indent!(i, "BlockClose"), SyntacticToken::Symbol(name) => indent!(i, "Symbol({name})"), SyntacticToken::Instruction(instruction) => indent!(i, "Instruction({instruction})"), SyntacticToken::LabelDefinition(name) => indent!(i, "LabelDefinition({name})"), |
