diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 177 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -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 |
10 files changed, 245 insertions, 72 deletions
@@ -1,2 +1 @@ /target -/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ea491c1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,177 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "assembler" +version = "2.3.0" +source = "git+git://benbridle.com/assembler?tag=v2.3.0#a9640fce1aaa5e80170ce4d2ac700f66cfffbb4b" +dependencies = [ + "inked", + "log 2.0.0", + "vagabond", +] + +[[package]] +name = "bedrock-asm" +version = "1.0.2" +dependencies = [ + "assembler", + "indexmap", + "log 2.0.0", + "switchboard", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inked" +version = "1.0.0" +source = "git+git://benbridle.com/inked?tag=v1.0.0#2954d37b638fa2c1dd3d51ff53f08f475aea6ea3" +dependencies = [ + "termcolor", +] + +[[package]] +name = "log" +version = "1.1.1" +source = "git+git://benbridle.com/log?tag=v1.1.1#930f3d0e2b82df1243f423c092a38546ea7533c3" + +[[package]] +name = "log" +version = "2.0.0" +source = "git+git://benbridle.com/log?tag=v2.0.0#a38d3dd487594f41151db57625410d1b786bebe4" +dependencies = [ + "inked", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "switchboard" +version = "2.1.0" +source = "git+git://benbridle.com/switchboard?tag=v2.1.0#e6435712ba5b3ca36e99fc8cbe7755940f8b1f3f" +dependencies = [ + "log 1.1.1", + "paste", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "vagabond" +version = "1.1.1" +source = "git+git://benbridle.com/vagabond?tag=v1.1.1#b190582517e6008ad1deff1859f15988e4efaa26" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" @@ -1,6 +1,6 @@ [package] name = "bedrock-asm" -version = "0.1.0" +version = "1.0.2" authors = ["Ben Bridle"] edition = "2024" description = "Assembler for the Bedrock assembly language" @@ -10,6 +10,8 @@ assembler = { git = "git://benbridle.com/assembler", tag = "v2.3.0" } log = { git = "git://benbridle.com/log", tag = "v2.0.0" } switchboard = { git = "git://benbridle.com/switchboard", tag = "v2.1.0" } +indexmap = "2.7.1" + [profile.release] lto=true opt-level="s" 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})"), |
