added a create-project system to assembler, and fixed a couple of parsing bugs

This commit is contained in:
2025-06-20 03:25:28 +01:00
parent f791b05292
commit 42c26d4184
15 changed files with 392 additions and 107 deletions
+9 -1
View File
@@ -7,6 +7,7 @@ pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![];
for node in nodes {
println!("node {node}");
instructions.push(build_instruction(node)?);
}
@@ -169,6 +170,9 @@ fn build_instruction(node: Node) -> Result<Instruction, AssembleError> {
let immediate = expect_token!(args.first().unwrap(), Immediate)?;
Ok(Instruction::Segment(immediate))
}
// These pseudo-instructions should have been expanded!
// this case being activated suggests the wrong syntax was given, and indicates a bug in the parser
Opcode::Db
| Opcode::Dh
| Opcode::Dw
@@ -178,6 +182,10 @@ fn build_instruction(node: Node) -> Result<Instruction, AssembleError> {
| Opcode::Push
| Opcode::Pop
| Opcode::Lwi
| Opcode::Include => todo!(),
| Opcode::Include
| Opcode::Call
| Opcode::Return
| Opcode::Pusha
| Opcode::Popa => Err(AssembleError::InvalidArg),
}
}
+115 -47
View File
@@ -26,6 +26,10 @@ fn try_expand(
match node.opcode() {
Opcode::Push => expand_push(node.clone(), result)?,
Opcode::Pop => expand_pop(node.clone(), result)?,
Opcode::Pusha => expand_pusha(node.clone(), result)?,
Opcode::Popa => expand_popa(node.clone(), result)?,
Opcode::Call => expand_call(node.clone(), result)?,
Opcode::Return => expand_return(node.clone(), result)?,
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
expand_ldx(node.clone(), result)?
}
@@ -42,22 +46,105 @@ fn try_expand(
fn expand_push(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let reg = expect_type!(current.arg(0).unwrap(), Register)?;
let spr = Token::Register(Register::Spr);
nodes.extend(vec![
node!(
label,
Opcode::SubI,
Token::Register(Register::Spr),
Token::Immediate(4),
Token::Register(Register::Spr)
),
node!(
None,
Opcode::Stw,
reg,
Token::Register(Register::Spr),
Token::Immediate(0)
),
node!(label, Opcode::SubI, spr, 4, spr),
node!(None, Opcode::Stw, reg, spr, 0),
]);
Ok(())
}
fn expand_pusha(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let count = expect_token!(current.arg(0).unwrap(), Immediate)?;
let spr = Token::Register(Register::Spr);
let registers: Vec<Register> = Register::general();
nodes.push(node!(
label,
Opcode::SubI,
spr,
Token::Immediate(count * 4),
spr
));
nodes.extend(
(0..count)
.rev()
.map(|i| {
node!(
None,
Opcode::Stw,
Token::Register(registers[i as usize]),
spr,
Token::Immediate(i * 4)
)
})
.collect::<Vec<Node>>(),
);
Ok(())
}
fn expand_popa(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let count = expect_token!(current.arg(0).unwrap(), Immediate)?;
let spr = Token::Register(Register::Spr);
let registers: Vec<Register> = Register::general();
nodes.extend(
(0..count)
.rev()
.map(|i| {
node!(
{ if i == 0 { label.clone() } else { None } },
Opcode::Ldw,
spr,
Token::Register(registers[i as usize]),
Token::Immediate(i * 4)
)
})
.collect::<Vec<Node>>(),
);
nodes.push(node!(
None,
Opcode::AddI,
spr,
Token::Immediate(count * 4),
spr
));
Ok(())
}
fn expand_call(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let addr = expect_type!(current.arg(0).unwrap(), Symbol)?;
let spr = Token::Register(Register::Spr);
let pcx = Token::Register(Register::Pcx);
let zero = Token::Register(Register::Zero);
nodes.extend(vec![
node!(label, Opcode::SubI, spr, 4, spr),
node!(None, Opcode::Stw, pcx, spr, 0),
node!(None, Opcode::Jmp, addr, zero),
]);
Ok(())
}
fn expand_return(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let spr = Token::Register(Register::Spr);
let ret = Token::Register(Register::Ret);
nodes.extend(vec![
node!(label, Opcode::Ldw, spr, ret, 0),
node!(None, Opcode::AddI, spr, 4, spr),
node!(None, Opcode::Jmp, 4, ret),
]);
Ok(())
@@ -66,22 +153,11 @@ fn expand_push(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError
fn expand_pop(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let reg = expect_type!(current.arg(0).unwrap(), Register)?;
let spr = Token::Register(Register::Spr);
nodes.extend(vec![
node!(
label,
Opcode::Ldw,
Token::Register(Register::Spr),
reg,
Token::Immediate(0)
),
node!(
None,
Opcode::AddI,
Token::Register(Register::Spr),
Token::Immediate(4),
Token::Register(Register::Spr)
),
node!(label, Opcode::Ldw, spr, reg, 0),
node!(None, Opcode::AddI, spr, 4, spr),
]);
Ok(())
@@ -94,9 +170,9 @@ fn expand_ldx(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError>
let offset = expect_type!(current.arg(2).unwrap(), Immediate)?;
nodes.extend(vec![
node!(current.label(), Opcode::Lli, name.clone(), reg.clone()),
node!(None, Opcode::Lui, name.clone(), reg.clone()),
node!(None, opcode, reg.clone(), reg, offset),
node!(current.label(), Opcode::Lli, name, reg),
node!(None, Opcode::Lui, name, reg),
node!(None, opcode, reg, reg, offset),
]);
Ok(())
@@ -107,20 +183,12 @@ fn expand_stx(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError>
let base = expect_type!(current.arg(0).unwrap(), Register)?;
let dest = expect_type!(current.arg(1).unwrap(), Symbol)?;
let offset = expect_type!(current.arg(2).unwrap(), Immediate)?;
let temp = Token::Register(Register::Rgf);
nodes.extend(vec![
node!(
current.label(),
Opcode::Lli,
dest.clone(),
Token::Register(Register::Rgf)
),
node!(
None,
Opcode::Lui,
dest.clone(),
Token::Register(Register::Rgf)
),
node!(None, opcode, base, Token::Register(Register::Rgf), offset),
node!(current.label(), Opcode::Lli, dest, temp),
node!(None, Opcode::Lui, dest, temp),
node!(None, opcode, base, temp, offset),
]);
Ok(())
@@ -131,8 +199,8 @@ fn expand_lwi(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError>
let reg = expect_type!(current.arg(1).unwrap(), Register)?;
nodes.extend(vec![
node!(current.label(), Opcode::Lli, val.clone(), reg.clone()),
node!(None, Opcode::Lui, val.clone(), reg.clone()),
node!(current.label(), Opcode::Lli, val, reg),
node!(None, Opcode::Lui, val, reg),
]);
Ok(())
@@ -153,7 +221,7 @@ fn expand_resx(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError
// push the inital node with the label
for _ in 0..size.div_ceil(units_per) {
// push the rest of the nodes
buffer.push(node!(None, Opcode::Data, Token::Immediate(0)));
buffer.push(node!(None, Opcode::Data, 0));
}
buffer[0].symbol = Some(region_label);
nodes.extend(buffer);
+35 -9
View File
@@ -18,7 +18,19 @@ macro_rules! node {
};
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
Node::new($symbol.clone(), $opcode.clone(), vec![$($tokens.clone()),+])
Node::new(
$symbol.clone(),
$opcode.clone(),
vec![$(node!(@convert_token $tokens)),+]
)
};
(@convert_token $token: literal) => {
Token::Immediate($token)
};
(@convert_token $token: expr) => {
$token.clone()
};
}
@@ -36,7 +48,7 @@ impl Node {
}
pub fn opcode(&self) -> Opcode {
self.opcode.clone()
self.opcode
}
pub fn args(&self) -> Vec<Token> {
@@ -134,11 +146,13 @@ impl fmt::Display for Opcode {
Opcode::Push => write!(f, "push"),
Opcode::Pop => write!(f, "pop"),
Opcode::Lwi => write!(f, "lwi"),
Opcode::Call => write!(f, "call"),
Opcode::Return => write!(f, "return"),
Opcode::Pusha => write!(f, "pusha"),
Opcode::Popa => write!(f, "popa"),
// utility - removed at compile time
// meta instructions
Opcode::Include => write!(f, "include"),
// special - generated by assembler
Opcode::Data => write!(f, "data"),
Opcode::Segment => write!(f, "[SEGMENT]"),
}
@@ -200,7 +214,7 @@ impl TokenType {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Opcode {
// Real instructions (0x00-0x26)
Nop,
@@ -242,6 +256,7 @@ pub enum Opcode {
Hlt,
AddI,
SubI,
// Pseudo-instructions
Db,
Dh,
@@ -251,10 +266,14 @@ pub enum Opcode {
Resw,
Push,
Pop,
Pusha,
Popa,
Lwi,
Include,
Call,
Return,
// fake instructions (these aren't present in the binary as instructions)
// meta instructions (these aren't present in the binary as instructions)
Include,
Data,
Segment,
}
@@ -328,6 +347,10 @@ impl FromStr for Opcode {
"pop" => Ok(Self::Pop),
"lwi" => Ok(Self::Lwi),
"include" => Ok(Self::Include),
"call" => Ok(Self::Call),
"return" => Ok(Self::Return),
"pusha" => Ok(Self::Push),
"popa" => Ok(Self::Pop),
_ => Err(OpcodeFromStrError::InvalidRegister("unknown opcode")),
}
}
@@ -340,7 +363,9 @@ impl Opcode {
"lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc",
"dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor",
"xnor", "int", "irt", "hlt", "addi", "subi", // Pseudo-instructions
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "include",
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return",
"pusha", "popa", // meta instructions
"include",
];
pub fn to_opcode_value(&self) -> Option<u8> {
@@ -385,6 +410,7 @@ impl Opcode {
Self::AddI => Some(0x25),
Self::SubI => Some(0x26),
Self::Segment => Some(0x27),
// Pseudo-instructions don't have opcode values
_ => None,
}
+24 -13
View File
@@ -40,10 +40,12 @@ impl Default for Program {
impl Parser {
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
let mut self_ = Parser {
tokens: tokens.into_iter().rev().collect(),
tokens: tokens.clone().into_iter().rev().collect(),
nodes: vec![],
};
println!("{:#?}", tokens);
while !self_.tokens.is_empty() {
let ins = self_.parse_instruction()?;
self_.nodes.push(ins);
@@ -68,8 +70,6 @@ impl Parser {
unreachable!();
}
println!("{:?}", self.peek_next()?);
// check if the Node starts with a label
let label = expect_token!(self.peek_next()?, Symbol).ok();
if label.is_some() {
@@ -158,14 +158,24 @@ impl Parser {
| Opcode::Jlt
| Opcode::Jle => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
let offset = if expect_type!(self.peek_next()?, Register).is_ok() {
self.next()?
} else {
Token::Register(Register::Zero)
let offset = match self.peek_next() {
Ok(token) => {
if expect_type!(token, Register).is_ok() {
self.next()?
} else {
Token::Register(Register::Zero)
}
}
Err(_) => Token::Register(Register::Zero),
};
args = vec![imm, offset];
}
Opcode::Call => {
let addr = expect_type!(self.next()?, Symbol)?;
args = vec![addr];
}
// I-type instructions
Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
@@ -193,18 +203,19 @@ impl Parser {
}
Opcode::Db | Opcode::Dh | Opcode::Dw => {
args = self.parse_data_definition(opcode.clone())?;
args = self.parse_data_definition(opcode)?;
}
// E-type pseudoinstructions (stack operations)
Opcode::Push => {
Opcode::Push | Opcode::Pop => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg];
}
Opcode::Pop => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg];
Opcode::Pusha | Opcode::Popa => {
let count =
expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8));
args = vec![count];
}
// Special instructions
@@ -214,7 +225,7 @@ impl Parser {
}
// Instructions with no arguments
Opcode::Hlt | Opcode::Nop | Opcode::Irt => {
Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => {
args = vec![];
}
+2 -8
View File
@@ -1,14 +1,8 @@
use assembler::codegen::codegen;
use assembler::expand::expand_pseudo_ops;
use assembler::model::{Node, Opcode, Symbol, Token, TokenType};
use assembler::parser::{Parser, Program};
use assembler::resolver::{create_sections, resolve_dependencies, resolve_symbols};
use common::prelude::*;
use core::fmt;
pub mod assembler;
use crate::assembler::lexer;
pub mod tooling;
mod util;
pub mod prelude {
pub use crate::assembler::assemble;
+6
View File
@@ -3,6 +3,12 @@ use std::{fs, io::Write, path::PathBuf};
fn main() {
// parse args:
let args: Vec<String> = std::env::args().collect();
if args.len() == 2 && args[1] == "init" {
assembler::tooling::project::tool_libcreate();
std::process::exit(0);
}
if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
eprintln!("Usage: binary_name -i input_path -o output_path");
std::process::exit(1);
+1
View File
@@ -0,0 +1 @@
pub mod project;
+93
View File
@@ -0,0 +1,93 @@
use crate::util::input;
pub fn tool_libcreate() {
let mut ptype: String;
loop {
ptype = input("Enter project type (bin|lib)");
if ptype == "bin" || ptype == "lib" {
break;
}
}
let project_name = input("Enter project name");
let project_path = input("Enter Directory to create project in");
println!("[ Creating new {ptype} project {project_name} in {project_path} ]");
let template = match ptype.as_str() {
"bin" => generate_bin_template(&project_name),
"lib" => generate_lib_template(&project_name),
_ => panic!("Invalid project type"),
};
let path = format!("{}/{}.dsa", project_path, project_name);
std::fs::write(path, template).expect("Unable to write file");
}
fn generate_lib_template(module_name: &str) -> String {
format!(
r#"// {module_name}.dsa
// usage:
//
// include {module_name} "<relative path>"
//
// usage for {module_name}_main:
// push (arg1)
// push (arg0)
// call {module_name}::{module_name}_main
// pop (arg0)
// pop (arg1)
// Example data declarations
// dw example_data: 0x0000
// Main function template
{module_name}_main:
// the correct way to start a function as defined by the calling convention
push bpr
mov spr, bpr
// explanation of how to access args
ldw bpr, rg0, 8 // arg 0
ldw bpr, rg0, 12 // arg 1
// your code goes here
// Example: load example_data into rg1
// ldw example_data, rg1
// the correct way to end a function as defined by the calling convention
mov bpr, spr
pop bpr
return
"#,
)
}
fn generate_bin_template(project_name: &str) -> String {
format!(
r#"// {project_name}.dsa
// Binary executable project
// Example Dependencies
// include math "libs/math/math.dsa"
include print "../resources/dsa/print.dsa"
// Data declarations - It is best practice to include these before any code!
dw message: "Hello from {project_name}.dsa!" // strings are automatically null terminated!
// Program entry point - execution starts at the first non-definition line
{project_name}:
// Getting started: Calling external functions
// Syntax: push (arg1), push (arg0), call namespace::function, pop (arg0), pop (arg1)
// Example: Print a string (if print library is included)
ldw message, rg0 // load address of message
push rg0 // push argument
call print::print // call the print function
pop rg0 // clean up stack
// Program must end with halt instruction
halt
"#,
)
}
+9
View File
@@ -0,0 +1,9 @@
use std::io::Write;
pub fn input(prompt: &str) -> String {
print!("{}\n > ", prompt);
std::io::stdout().flush().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}