739 lines
27 KiB
Rust
739 lines
27 KiB
Rust
use std::collections::HashMap;
|
||
use std::sync::atomic::AtomicU32;
|
||
use std::time::SystemTime;
|
||
|
||
use chrono::{DateTime, Local};
|
||
|
||
use super::registers::RegisterAllocator;
|
||
use crate::{block, comment, dsa};
|
||
|
||
use crate::model::{
|
||
BinaryOperator, CompilerError, ConstExpr, Declaration, Dependency, Expression,
|
||
Program, Statement, UnaryOperator, Variable,
|
||
};
|
||
|
||
pub struct CodeGenerator {
|
||
ast: Program,
|
||
imports: HashMap<String, String>,
|
||
globals: Vec<String>,
|
||
functions: Vec<String>,
|
||
symbols: Vec<String>,
|
||
allocator: RegisterAllocator,
|
||
}
|
||
|
||
fn import(name: &str, path: &str) -> String {
|
||
format!("include {name}: \"{}\"", path)
|
||
}
|
||
|
||
impl CodeGenerator {
|
||
const RET: &'static str = "\tjmp _ret";
|
||
|
||
pub fn new(ast: Program) -> Self {
|
||
CodeGenerator {
|
||
ast,
|
||
imports: HashMap::new(),
|
||
globals: Vec::new(),
|
||
functions: Vec::new(),
|
||
symbols: Vec::new(),
|
||
allocator: RegisterAllocator::new(),
|
||
}
|
||
}
|
||
|
||
pub fn include(&mut self, name: &str, path: &str) {
|
||
self.imports.insert(name.to_string(), path.to_string());
|
||
}
|
||
|
||
fn is_global(&self, name: &str) -> bool {
|
||
// Check if this variable is in the globals list
|
||
self.globals
|
||
.iter()
|
||
.any(|g| g.contains(&format!("dw {}:", name)))
|
||
}
|
||
|
||
pub fn generate(&mut self) -> Result<String, CompilerError> {
|
||
// always include the print library for debugging!
|
||
self.include("print", "./lib/io/print.dsa");
|
||
|
||
for block in self.ast.clone().declarations {
|
||
match block {
|
||
Declaration::Variable {
|
||
var: Variable { name, .. },
|
||
..
|
||
} => self.symbols.push(name),
|
||
Declaration::Function { name, .. } => self.symbols.push(name),
|
||
Declaration::Dependency(Dependency { name, .. }) => {
|
||
self.symbols.push(name)
|
||
}
|
||
}
|
||
}
|
||
|
||
for block in self.ast.clone().declarations {
|
||
self.generate_block(block.clone())?;
|
||
}
|
||
|
||
self.generate_layout()
|
||
}
|
||
|
||
fn generate_layout(&mut self) -> Result<String, CompilerError> {
|
||
let datetime: DateTime<Local> = SystemTime::now().into();
|
||
Ok(dsa![
|
||
"",
|
||
comment!("GENERATED BY DSC COMPILER"),
|
||
comment!(format!(
|
||
"Generated at {}",
|
||
datetime.format("%Y-%m-%d %H:%M:%S")
|
||
)),
|
||
"",
|
||
// imports
|
||
comment!("Imports"),
|
||
self.imports
|
||
.iter()
|
||
.map(|(k, v)| import(k, v))
|
||
.collect::<Vec<String>>()
|
||
.join("\n"),
|
||
"",
|
||
// reserved memory
|
||
comment!("Globals & Reserved Memory"),
|
||
self.globals.join("\n"),
|
||
"",
|
||
// entry point
|
||
comment!("Entry Point"),
|
||
"dw stack: 0x10000",
|
||
"db message: \"Process Exited with code:\"",
|
||
block! [ "_init"
|
||
dsa![ldw stack, bpr],
|
||
dsa![mov bpr, spr],
|
||
dsa![push zero],
|
||
dsa![call main],
|
||
dsa![call print::print_newline],
|
||
dsa![lwi message, rg0],
|
||
dsa![push rg0],
|
||
dsa![call print::print],
|
||
dsa![pop zero],
|
||
dsa![call print::print_hex_word],
|
||
dsa![pop zero],
|
||
dsa![hlt]
|
||
],
|
||
"",
|
||
comment!("Return"),
|
||
block! [ "_ret"
|
||
dsa![mov bpr, spr],
|
||
dsa![pop bpr],
|
||
dsa![return]
|
||
],
|
||
comment!("Compiled Code Starts..."),
|
||
// block! [ "main"
|
||
// dsa![push bpr],
|
||
// dsa![mov spr, bpr],
|
||
// dsa![lwi 67, rg1],
|
||
// dsa![stw rg1, spr, 8],
|
||
// dsa![mov bpr, spr],
|
||
// dsa![pop bpr],
|
||
// dsa![return]
|
||
// ],
|
||
self.functions.join("\n"),
|
||
])
|
||
}
|
||
|
||
fn generate_global(&mut self, name: &str, init: Option<ConstExpr>) {
|
||
self.globals.push(format!(
|
||
"dw {}: {}",
|
||
name,
|
||
init.unwrap_or(ConstExpr::Number(0))
|
||
))
|
||
}
|
||
|
||
fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> {
|
||
match block {
|
||
Declaration::Variable { var, init, .. } => {
|
||
self.generate_global(&var.name, init)
|
||
}
|
||
Declaration::Function {
|
||
name, params, body, ..
|
||
} => {
|
||
let func = self.generate_function(&name, ¶ms, &body).join("\n");
|
||
|
||
self.functions.push(format!("{func}\n"));
|
||
}
|
||
Declaration::Dependency(Dependency { name, path }) => {
|
||
self.imports.insert(name, path);
|
||
}
|
||
};
|
||
|
||
Ok(())
|
||
}
|
||
|
||
// Example: Generate code for a function
|
||
fn generate_function(
|
||
&mut self,
|
||
name: &str,
|
||
params: &[Variable],
|
||
body: &[Statement],
|
||
) -> Vec<String> {
|
||
let mut code = Vec::new();
|
||
|
||
// Reset allocator for new function
|
||
self.allocator.reset();
|
||
|
||
// Function prologue
|
||
code.push(format!("{}:", name));
|
||
code.push("\tpush bpr".to_string());
|
||
code.push("\tmov spr, bpr".to_string());
|
||
code.push(String::new());
|
||
|
||
// Allocate parameters to registers or stack locations
|
||
for (i, param) in params.iter().enumerate() {
|
||
let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8
|
||
// Track that this parameter is at a stack location
|
||
let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap();
|
||
code.extend(load_code);
|
||
code.push(format!("\tldw bpr, {}, {}", reg, offset));
|
||
}
|
||
|
||
// Generate code for function body
|
||
for stmt in body {
|
||
let stmt_code = self.generate_statement(stmt).unwrap();
|
||
code.extend(stmt_code);
|
||
}
|
||
|
||
// automatically return at function end
|
||
if let Some(x) = code.last()
|
||
&& x == Self::RET
|
||
{
|
||
} else {
|
||
code.push(Self::RET.to_string());
|
||
}
|
||
|
||
code
|
||
}
|
||
|
||
// Example: Generate code for a statement
|
||
fn generate_statement(
|
||
&mut self,
|
||
stmt: &Statement,
|
||
) -> Result<Vec<String>, CompilerError> {
|
||
let mut code = Vec::new();
|
||
|
||
match stmt {
|
||
Statement::Declaration { var, value } => {
|
||
if let Some(expr) = value {
|
||
// Evaluate expression
|
||
let (result_reg, expr_code) = self.generate_expression(expr, true)?;
|
||
code.extend(expr_code);
|
||
|
||
// Store result in variable
|
||
let store_code = self.allocator.store_var(&var.name, &result_reg);
|
||
code.extend(store_code);
|
||
|
||
// Free temporary register
|
||
self.allocator.free_temp(&result_reg);
|
||
} else {
|
||
// Just declaring variable without initialization
|
||
self.allocator.alloc_var(&var.name)?;
|
||
}
|
||
}
|
||
|
||
Statement::Break => unimplemented!(),
|
||
Statement::Continue => unimplemented!(),
|
||
|
||
Statement::PtrWrite { ptr, value } => {
|
||
let (result_reg, expr_code) = self.generate_expression(value, true)?;
|
||
code.extend(expr_code);
|
||
|
||
let (ptr_reg, ptr_code) = self.generate_expression(ptr, true)?;
|
||
code.extend(ptr_code);
|
||
|
||
code.push(format!("\tstw {}, {}", result_reg, ptr_reg));
|
||
|
||
self.allocator.free_temp(&result_reg);
|
||
self.allocator.free_temp(&ptr_reg);
|
||
}
|
||
|
||
Statement::Assign { varname, value } => {
|
||
// Evaluate expression
|
||
let (result_reg, expr_code) = self.generate_expression(value, true)?;
|
||
code.extend(expr_code);
|
||
|
||
// Check if this is a global variable
|
||
if self.is_global(varname) {
|
||
// Store to global label
|
||
code.push(format!("\tstw {}, {}", result_reg, varname));
|
||
} else {
|
||
// Store result in local variable
|
||
let store_code = self.allocator.store_var(varname, &result_reg);
|
||
code.extend(store_code);
|
||
}
|
||
|
||
// Free temporary register
|
||
self.allocator.free_temp(&result_reg);
|
||
}
|
||
|
||
Statement::Return(expr) => {
|
||
if let Some(e) = expr {
|
||
let (result_reg, expr_code) = self.generate_expression(e, true)?;
|
||
code.extend(expr_code);
|
||
code.push(format!("\tstw {}, bpr, 8", result_reg));
|
||
code.push(format!("\tjmp _ret"));
|
||
self.allocator.free_temp(&result_reg);
|
||
}
|
||
}
|
||
|
||
Statement::If {
|
||
condition,
|
||
then_stmt,
|
||
else_stmt,
|
||
} => {
|
||
// Generate condition
|
||
let (cond_reg, cond_code) = self.generate_expression(condition, true)?;
|
||
code.extend(cond_code);
|
||
|
||
// Compare with zero
|
||
code.push(format!("\tcmp {}, zero", cond_reg));
|
||
self.allocator.free_temp(&cond_reg);
|
||
|
||
// Generate unique labels
|
||
let then_label = format!("_then_{}", self.get_unique_label());
|
||
let else_label = format!("_else_{}", self.get_unique_label());
|
||
let end_label = format!("_end_{}", self.get_unique_label());
|
||
|
||
// Jump to else if condition is false (equal to zero)
|
||
code.push(format!("\tjeq {}", else_label));
|
||
|
||
// Then block
|
||
code.push(format!("{}:", then_label));
|
||
for s in then_stmt {
|
||
code.extend(self.generate_statement(s)?);
|
||
}
|
||
|
||
if then_stmt.len() == 0 {
|
||
code.push("\tnop".to_string());
|
||
}
|
||
|
||
code.push(format!("\tjmp {}", end_label));
|
||
|
||
// Else block
|
||
code.push(format!("{}:", else_label));
|
||
for s in else_stmt {
|
||
code.extend(self.generate_statement(s)?);
|
||
}
|
||
|
||
if else_stmt.len() == 0 {
|
||
code.push("\tnop".to_string());
|
||
}
|
||
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
|
||
Statement::While { condition, body } => {
|
||
let loop_start = format!("_while_start_{}", self.get_unique_label());
|
||
let loop_end = format!("_while_end_{}", self.get_unique_label());
|
||
|
||
code.push(format!("{}:", loop_start));
|
||
|
||
// Generate condition
|
||
let (cond_reg, cond_code) = self.generate_expression(condition, true)?;
|
||
code.extend(cond_code);
|
||
|
||
code.push(format!("\tcmp {}, zero", cond_reg));
|
||
self.allocator.free_temp(&cond_reg);
|
||
|
||
code.push(format!("\tjeq {}", loop_end));
|
||
|
||
// Loop body
|
||
for s in body {
|
||
code.extend(self.generate_statement(s)?);
|
||
}
|
||
|
||
code.push(format!("\tjmp {}", loop_start));
|
||
code.push(format!("{}:", loop_end));
|
||
}
|
||
|
||
Statement::Loop(body) => {
|
||
let loop_start = format!("_loop_start_{}", self.get_unique_label());
|
||
|
||
code.push(format!("{}:", loop_start));
|
||
|
||
for s in body {
|
||
code.extend(self.generate_statement(s)?);
|
||
}
|
||
|
||
code.push(format!("\tjmp {}", loop_start));
|
||
}
|
||
|
||
Statement::Expression { expr } => {
|
||
let (result_reg, expr_code) = self.generate_expression(expr, false)?;
|
||
code.extend(expr_code);
|
||
self.allocator.free_temp(&result_reg);
|
||
}
|
||
|
||
Statement::Block(statements) => {
|
||
for s in statements {
|
||
code.extend(self.generate_statement(s)?);
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(code)
|
||
}
|
||
|
||
// Example: Generate code for an expression
|
||
// Returns (register containing result, assembly code)
|
||
fn generate_expression(
|
||
&mut self,
|
||
expr: &Expression,
|
||
use_result: bool,
|
||
) -> Result<(String, Vec<String>), CompilerError> {
|
||
let mut code = Vec::new();
|
||
|
||
// optimisation to prevent generating dead code!
|
||
if expr.is_pure() && !use_result {
|
||
return Ok((String::new(), code));
|
||
}
|
||
|
||
match expr {
|
||
Expression::StringLiteral(value) => {
|
||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||
code.extend(alloc_code);
|
||
|
||
// write string into memory
|
||
let uuid = self.get_unique_label();
|
||
code.push(format!("\tdb str_{uuid}: \"{value}\""));
|
||
|
||
// Load pointer to string
|
||
code.push(format!("\tlwi str_{uuid}, {reg}"));
|
||
|
||
Ok((reg, code))
|
||
}
|
||
|
||
Expression::CharLiteral(value) => {
|
||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||
code.extend(alloc_code);
|
||
|
||
// Load immediate value
|
||
code.push(format!("\tlli {}, {} // '{value}'", *value as u8, reg));
|
||
|
||
Ok((reg, code))
|
||
}
|
||
|
||
Expression::Number(value) => {
|
||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||
code.extend(alloc_code);
|
||
|
||
// Load immediate value
|
||
code.push(format!("\tlli {}, {}", value & 0xFFFF, reg));
|
||
if *value > 0xFFFF || *value < 0 {
|
||
code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg));
|
||
}
|
||
|
||
Ok((reg, code))
|
||
}
|
||
|
||
Expression::Variable { name, .. } => {
|
||
if self.is_global(&name.name) {
|
||
// Allocate a temporary register for the global
|
||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||
code.extend(alloc_code);
|
||
|
||
// Load from global label
|
||
code.push(format!("\tldw {}, {}", name.name, reg));
|
||
|
||
Ok((reg, code))
|
||
} else {
|
||
// Local variable - use existing allocator logic
|
||
let (reg, load_code) = self.allocator.load_var(&name.name)?;
|
||
code.extend(load_code);
|
||
Ok((reg, code))
|
||
}
|
||
}
|
||
|
||
Expression::Binary { op, left, right } => {
|
||
// Evaluate left operand
|
||
let (left_reg, left_code) = self.generate_expression(left, true)?;
|
||
code.extend(left_code);
|
||
|
||
// Evaluate right operand
|
||
let (right_reg, right_code) = self.generate_expression(right, true)?;
|
||
code.extend(right_code);
|
||
|
||
// Allocate result register
|
||
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||
code.extend(result_alloc);
|
||
|
||
// Generate operation
|
||
match op {
|
||
BinaryOperator::Add => {
|
||
code.push(format!(
|
||
"\tadd {}, {}, {}",
|
||
left_reg, right_reg, result_reg
|
||
));
|
||
}
|
||
BinaryOperator::Sub => {
|
||
code.push(format!(
|
||
"\tsub {}, {}, {}",
|
||
left_reg, right_reg, result_reg
|
||
));
|
||
}
|
||
BinaryOperator::Mul => {
|
||
self.include("maths", "./lib/maths/core.dsa");
|
||
// Call multiply function
|
||
code.push(format!("\tpush {}", right_reg));
|
||
code.push(format!("\tpush {}", left_reg));
|
||
code.push("\tcall maths::multiply".to_string());
|
||
code.push(format!("\tpop {}", result_reg));
|
||
code.push("\tpop zero".to_string());
|
||
}
|
||
// Comparison operators - return 1 (true) or 0 (false)
|
||
BinaryOperator::Eq => {
|
||
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
|
||
code.push(format!("\tlli 0, {}", result_reg));
|
||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||
code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1
|
||
code.push(format!("\tlli 1, {}", result_reg));
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
BinaryOperator::Ne => {
|
||
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
|
||
code.push(format!("\tlli 0, {}", result_reg));
|
||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||
code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1
|
||
code.push(format!("\tlli 1, {}", result_reg));
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
BinaryOperator::Lt => {
|
||
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
|
||
code.push(format!("\tlli 0, {}", result_reg));
|
||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||
code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1
|
||
code.push(format!("\tlli 1, {}", result_reg));
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
BinaryOperator::Le => {
|
||
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
|
||
code.push(format!("\tlli 0, {}", result_reg));
|
||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||
code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1
|
||
code.push(format!("\tlli 1, {}", result_reg));
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
BinaryOperator::Gt => {
|
||
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
|
||
code.push(format!("\tlli 0, {}", result_reg));
|
||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||
code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1
|
||
code.push(format!("\tlli 1, {}", result_reg));
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
BinaryOperator::Ge => {
|
||
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
|
||
code.push(format!("\tlli 0, {}", result_reg));
|
||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||
code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1
|
||
code.push(format!("\tlli 1, {}", result_reg));
|
||
code.push(format!("{}:", end_label));
|
||
}
|
||
_ => unimplemented!(),
|
||
}
|
||
|
||
// Free operand registers (allocator will protect variables)
|
||
self.allocator.free_temp(&left_reg);
|
||
self.allocator.free_temp(&right_reg);
|
||
|
||
Ok((result_reg, code))
|
||
}
|
||
|
||
Expression::Call { name, args } => {
|
||
// first evaluate all the args we're going to need
|
||
let mut arg_regs = Vec::new();
|
||
for arg in args.iter().rev() {
|
||
let (arg_reg, arg_code) = self.generate_expression(arg, true)?;
|
||
code.extend(arg_code);
|
||
arg_regs.push(arg_reg);
|
||
}
|
||
|
||
// Save caller-saved registers and track which ones we saved
|
||
// old method, inefficient.
|
||
// let saved_regs = self.allocator.get_caller_saved_registers();
|
||
// for reg in &saved_regs {
|
||
// code.push(format!("\tpush {}", reg));
|
||
// }
|
||
|
||
// Save caller-saved registers and track which ones we saved
|
||
let saved_regs = self.allocator.get_caller_saved_registers();
|
||
for reg in &saved_regs {
|
||
// spill variables to stack
|
||
code.extend(self.allocator.spill_register(reg).unwrap());
|
||
}
|
||
|
||
// Evaluate and push arguments in reverse order
|
||
for (i, arg_reg) in arg_regs.iter().enumerate() {
|
||
code.push(format!(
|
||
"\tpush {} // push arg {}",
|
||
arg_reg,
|
||
args.len() - 1 - i
|
||
));
|
||
}
|
||
|
||
// if GLOBAL_METHODS.contains_key(name.name.as_str()) {
|
||
// code.push(format!("\tcall {}",
|
||
// GLOBAL_METHODS[name.name.as_str()])); } else
|
||
if self.symbols.contains(&name.name) {
|
||
// Call local function
|
||
code.push(format!("\tcall {}", name));
|
||
} else if let Some(ns) = name.namespace.clone()
|
||
&& self.imports.contains_key(&ns)
|
||
{
|
||
code.push(format!("\tcall {}", name));
|
||
} else {
|
||
return Err(CompilerError::Undefined(name.clone()));
|
||
}
|
||
|
||
let result_reg: String;
|
||
|
||
if use_result {
|
||
let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||
result_reg = temp_result_reg;
|
||
|
||
code.extend(result_alloc);
|
||
code.push(format!("\tpop {}", result_reg));
|
||
|
||
// Clean up arguments
|
||
if args.len() > 1 {
|
||
for _ in 0..(args.len() - 1) {
|
||
code.push("\tpop zero".to_string());
|
||
}
|
||
}
|
||
} else {
|
||
result_reg = "zero".to_string();
|
||
|
||
// Clean up arguments
|
||
if args.len() > 0 {
|
||
for _ in 0..(args.len()) {
|
||
code.push("\tpop zero".to_string());
|
||
}
|
||
}
|
||
}
|
||
|
||
// Restore caller-saved registers in reverse order (LIFO)
|
||
// for reg in saved_regs.iter().rev() {
|
||
// code.push(format!("\tpop {}", reg));
|
||
// }
|
||
|
||
// Free argument registers
|
||
for reg in arg_regs {
|
||
self.allocator.free_temp(®);
|
||
}
|
||
|
||
Ok((result_reg, code))
|
||
}
|
||
|
||
Expression::Unary { op, operand } => {
|
||
let (operand_reg, operand_code) =
|
||
self.generate_expression(operand, true)?;
|
||
code.extend(operand_code);
|
||
|
||
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||
code.extend(result_alloc);
|
||
|
||
match op {
|
||
UnaryOperator::Minus => {
|
||
// Negate: result = 0 - operand
|
||
code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg));
|
||
}
|
||
UnaryOperator::Plus => {
|
||
// Just move
|
||
code.push(format!("\tmov {}, {}", operand_reg, result_reg));
|
||
}
|
||
UnaryOperator::Dereference => {
|
||
code.push(format!("\tldw {}, {}", operand_reg, result_reg));
|
||
}
|
||
UnaryOperator::Reference => {
|
||
code.extend(self.allocator.spill_register(&operand_reg)?);
|
||
code.push(format!(
|
||
"\tsubi bpr {} {}",
|
||
-(4 + self.allocator.get_stack_offset()),
|
||
result_reg
|
||
))
|
||
}
|
||
}
|
||
|
||
self.allocator.free_temp(&operand_reg);
|
||
Ok((result_reg, code))
|
||
}
|
||
|
||
Expression::Empty => Ok(("zero".to_string(), code)),
|
||
}
|
||
}
|
||
|
||
// Helper for generating unique labels
|
||
fn get_unique_label(&mut self) -> String {
|
||
// You'd implement a counter here
|
||
static COUNTER: AtomicU32 = AtomicU32::new(0);
|
||
|
||
let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||
(val + 1).to_string()
|
||
}
|
||
}
|
||
|
||
/// Build a single string from any number of arguments.
|
||
/// Each argument must implement `Display` or be convertible to a string.
|
||
#[macro_export]
|
||
macro_rules! dsa {
|
||
($($arg:expr),* $(,)?) => {{
|
||
// Start with an empty String – we’ll grow it as we go.
|
||
use std::fmt::Write;
|
||
let mut s = ::std::string::String::new();
|
||
$(
|
||
// `write!` is cheaper than `format!` for each element
|
||
// because it re‑uses the same buffer.
|
||
|
||
write!(s, "{}\n", $arg).expect("write to String failed");
|
||
)*
|
||
s
|
||
}};
|
||
}
|
||
|
||
// ──────────────────────── dsa! ────────────────────────
|
||
// A tiny helper that just turns its token‑stream into a string.
|
||
// The trailing comma is kept – it’s part of the syntax you want.
|
||
#[macro_export]
|
||
macro_rules! cmd {
|
||
($($tokens:tt)*) => {{
|
||
// We’ll just stringify the tokens and return a String.
|
||
format!("{}", concat!(stringify!($tokens), "\n"))
|
||
}};
|
||
}
|
||
|
||
// ──────────────────────── block! ────────────────────────
|
||
// Usage:
|
||
//
|
||
// let asm = block![ "name"
|
||
// dsa![mov rg0, rg1],
|
||
// dsa![add rg1, rg1]
|
||
// ];
|
||
//
|
||
// `asm` is a `&'static str` containing:
|
||
//
|
||
// name:
|
||
// mov rg0, rg1
|
||
// add rg1, rg1
|
||
//
|
||
#[macro_export]
|
||
macro_rules! block {
|
||
// The first token must be a string literal – that’s the label.
|
||
($label:literal $(dsa![$($ins:tt)*]),* ) => {{
|
||
// Build a single string at compile time.
|
||
const CODE: &str = concat!(
|
||
$label, ":\n",
|
||
// Each `dsa!` call yields a string like `"mov rg0, rg1"`.
|
||
// We add a newline after each one to get the desired layout.
|
||
$(concat!("\t", stringify!($($ins)*), "\n")),*
|
||
);
|
||
CODE
|
||
}};
|
||
}
|
||
|
||
#[macro_export]
|
||
macro_rules! comment {
|
||
($text:expr) => {{ format!("// {}", $text) }};
|
||
}
|