summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
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);
-}
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})"),