summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock177
-rw-r--r--Cargo.toml4
-rw-r--r--src/bin/br-asm.rs21
-rw-r--r--src/lib.rs37
-rw-r--r--src/stages/bytecode.rs24
-rw-r--r--src/stages/semantic.rs21
-rw-r--r--src/stages/semantic_tokens.rs9
-rw-r--r--src/stages/syntactic.rs21
-rw-r--r--src/stages/syntactic_tokens.rs2
10 files changed, 245 insertions, 72 deletions
diff --git a/.gitignore b/.gitignore
index 4fffb2f..ea8c4bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 9673558..747594f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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);
-}
diff --git a/src/lib.rs b/src/lib.rs
index d45d449..76ec544 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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})"),