40 Commits

Author SHA1 Message Date
zxq5 a1099249e9 updated roadmap 2026-02-04 01:59:50 +00:00
zxq5 cb65a928c8 fixed bug where stack inspector shows incorrect addresses 2026-02-04 01:59:43 +00:00
zxq5 fa8aa1cd29 integrated compiler in DSA editor 2026-02-04 01:58:55 +00:00
zxq5 7780f5804f deleted old files and modified some dsa source files 2026-02-04 01:58:37 +00:00
zxq5 889ee8ef71 wrote dsa/dsc code examples including an allocator 2026-02-04 01:58:03 +00:00
zxq5 dd20401ad6 added basic logging to common
TODO: improve logging
2026-02-04 01:57:40 +00:00
zxq5 f4933b55fb forgot to commit this 2026-02-04 01:57:18 +00:00
zxq5 14a04a524c added support for DSA libraries to compiler and made some optimisations.
provided an API for the editor to use.
2026-02-04 01:56:58 +00:00
zxq5 f25db6c8fd updated assembler logging 2026-02-04 01:56:15 +00:00
zxq5 48a74bfde2 updated dsc example to reflect current feature set. 2026-02-03 15:38:40 +00:00
zxq5 7973b2afca - refactored lexer
- updated lexer to allow hex and binary integer literals
- updated parser with support for writing to pointers
- updated code generation to support writing to pointers
- fixed a bug with codegen where args are loaded from incorrect offsets
  due to saving registers prior to calling.
2026-02-03 15:37:38 +00:00
zxq5 ce2eda72a0 updated roadmap with progress 2026-02-03 15:34:35 +00:00
zxq5 3afeafc9d4 - compiler works for basic maths expressions and functions
- basic pointers and reading values from pointers works
- writing to pointers not yet implemented (looks painful so a problem
  for tomorrow)
- updated print library. the compiler has this hardcoded in all programs
  for now
2026-02-03 02:11:30 +00:00
zxq5 5573c5a609 minor code changes for codegen 2026-02-02 11:15:45 +00:00
zxq5 8f7163c459 added some documentation and started on compiler for custom language (not C) based on previous prototypes. pretty broken state rn. 2026-02-01 22:16:09 +00:00
zxq5 52ef7872f0 compiler working for some mathematical expressions, function calls and
simple conditionals
2026-01-31 13:28:42 +00:00
zxq5 e31deb594f fixed a bug with the multiply function in core.dsa and added a print_num
function to print.dsa for decimal numbers
2026-01-31 13:28:11 +00:00
zxq5 63c9d858b4 added a to-do list and bacon.toml for developing the compiler 2026-01-31 13:27:31 +00:00
zxq5 782c842a42 updated gitignore 2026-01-29 19:33:30 +00:00
zxq5 259746558f codegen progress 2026-01-29 19:29:48 +00:00
zxq5 b9f98bff7b started on codegen, scaffolding basically done 2025-11-15 02:58:36 +00:00
zxq5 091dabfbf3 Merge remote-tracking branch 'refs/remotes/origin/compiler' into compiler 2025-11-14 23:37:57 +00:00
zxq5 fd5b305576 started work on c compiler 2025-11-14 23:36:51 +00:00
zxq5 b33fdbfeec rust macros make me want to unalive. 2025-07-01 01:08:37 +01:00
zxq5 2582ad10fa started work on compiler 2025-06-30 20:44:39 +01:00
zxq5 ae92510fb8 dsa lib bugfixes 2025-06-26 20:53:51 +01:00
zxq5 7c63340888 minor changes to assembler 2025-06-26 20:53:22 +01:00
zxq5 e9f04824ea updates to dsa libs 2025-06-26 00:51:20 +01:00
zxq5 620584488b fixed unit tests & misc changes to workspace config 2025-06-26 00:50:58 +01:00
zxq5 1101331f70 fixed a couple of emulator bugs, including fixing shift instructions. finished implementing lib/io/print/print_hex_byte and print_hex_word 2025-06-25 16:31:42 +01:00
zxq5 c171b0db89 worked on print.dsa and maths/core.dsa 2025-06-25 00:40:31 +01:00
zxq5 82b99c127c finished initial interrupts implementation 2025-06-25 00:13:55 +01:00
nullndvoid 92c4660a4d misc: get rid of some errors from Cargo lol 2025-06-24 21:55:11 +01:00
zxq5 2a6991fe4a idk, i refactored some stuff ig 2025-06-24 19:34:45 +01:00
zxq5 0fdd28aad1 finished the interpreter 2025-06-24 19:31:55 +01:00
zxq5 f639240b6c progress on debugging bf.dsa 2025-06-24 18:07:33 +01:00
zxq5 d2c1492dca added step(n) feature to emulator, allowing for stepping n instructions at a time 2025-06-24 18:07:11 +01:00
zxq5 4ef8bbdf46 updated dependencies 2025-06-24 18:06:29 +01:00
zxq5 76197fac8f finished refactor of emulator - started on loader (needs significant changes before functional in the way that I would like) 2025-06-23 23:45:47 +01:00
zxq5 bc5ddef311 added error handling to emulator 2025-06-23 21:28:38 +01:00
87 changed files with 9860 additions and 9450 deletions
+7
View File
@@ -0,0 +1,7 @@
[build]
rustc-wrapper = "sccache"
# Enable to cut unused deps.
# rustflags = ["-D", "unused-crate-dependencies"]
[future-incompat-report]
frequency = "always"
+1
View File
@@ -1,2 +1,3 @@
/target
**/*.env
Cargo.lock
+3 -1
View File
@@ -5,5 +5,7 @@
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true
"files.trimTrailingWhitespace": true,
"gitea.owner": "LowLevelDevs",
"gitea.repo": "damn_simple_architecture",
}
Generated
-4261
View File
File diff suppressed because it is too large Load Diff
+10 -1
View File
@@ -1,8 +1,17 @@
cargo-features = ["codegen-backend"]
[workspace]
members = ["emulator", "common", "assembler", "dsa_editor"]
members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "c_compiler"]
resolver = "3"
[workspace.package]
version = "0.2.0"
edition = "2024"
authors = ["zxq5", "nullndvoid"]
[profile.dev]
codegen-backend = "cranelift"
panic = "abort" # Cranelift does not support stack unwinds.
lto = false
debug = true
incremental = false # sccache does not support caching incremental crates.
+28 -33
View File
@@ -1,34 +1,29 @@
++++++++++++++++++++++++++++++++++++++++++++ c1v44 : ASCII code of comma
>++++++++++++++++++++++++++++++++ c2v32 : ASCII code of space
>++++++++++++++++ c3v11 : quantity of numbers to be calculated
> c4v0 : zeroth Fibonacci number (will not be printed)
>+ c5v1 : first Fibonacci number
<< c3 : loop counter
[ block : loop to print (i)th number and calculate next one
>> c5 : the number to be printed
block : divide c5 by 10 (preserve c5)
> c6v0 : service zero
>++++++++++ c7v10 : divisor
<< c5 : back to dividend
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<] c5v0 : divmod algo; results in 0 n d_n%d n%d n/d
>[<+>-] c5 : move dividend back to c5 and clear c6
>[-] c7v0 : clear c7
>> block : c9 can have two digits; divide it by ten again
>++++++++++ c10v10: divisor
< c9 : back to dividend
[->-[>+>>]>[+[-<+>]>+>>]<<<<<] c9v0 : another divmod algo; results in 0 d_n%d n%d n/d
>[-] c10v0 : clear c10
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]c12v0 : print nonzero n/d (first digit) and clear c12
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] c11v0 : print nonzero n%d (second digit) and clear c11
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-] c8v0 : print any n%d (last digit) and clear c8
<<<<<<<.>. c1c2 : print comma and space
block : actually calculate next Fibonacci in c6
>>[>>+<<-] c4v0 : move c4 to c6 (don't need to preserve it)
>[>+<<+>-] c5v0 : move c5 to c6 and c4 (need to preserve it)
>[<+>-] c6v0 : move c6 with sum to c5
<<<- c3 : decrement loop counter
++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++
>++++++++++++++++
>
>+
<<
[
>>
>
>++++++++++
<<
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]
>[<+>-]
>[-]
>>
>++++++++++
<
[->-[>+>>]>[+[-<+>]>+>>]<<<<<]
>[-]
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<.>.
>>[>>+<<-]
>[>+<<+>-]
>[<+>-]
<<<-
]
<<++... c1 : output three dots
<<++...
Binary file not shown.
+2 -6
View File
@@ -11,14 +11,10 @@ pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![];
for node in nodes {
instructions.push(
build_instruction(&node)
.unwrap_or_else(|_| panic!("Failed to build instruction: {node:?}")),
);
instructions.push(build_instruction(&node)?);
}
println!("------------------------");
log("Compilation Success ✅");
log("Assembly Successful ✅");
Ok(instructions)
}
+11 -3
View File
@@ -7,12 +7,11 @@ use common::prelude::Register;
pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleError> {
let mut tokens = Vec::new();
program = program.replace(',', "");
let lines = program.lines();
let mut literal = String::new();
for line in lines {
for token in line.split_whitespace() {
for (i, token) in line.split_whitespace().enumerate() {
if token.starts_with("//") {
break;
}
@@ -23,7 +22,9 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
if !literal.is_empty() {
if !token.starts_with('"') {
literal.push(' ');
if i > 0 {
literal.push(' ');
}
literal.push_str(token);
}
@@ -37,6 +38,11 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
continue;
}
let token = token.trim_end_matches(',');
if token.is_empty() {
continue;
}
if let Some(token) = parse_register(token)? {
tokens.push(token);
} else if let Some(token) = parse_opcode(token)? {
@@ -59,6 +65,8 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
}
}
// println!("{:#?}", tokens);
Ok(tokens)
}
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
-1
View File
@@ -1,7 +1,6 @@
//! Macros used throughout the assembler
use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
+9 -12
View File
@@ -9,13 +9,9 @@ use std::{
thread,
};
pub use common::logging::log;
use common::prelude::Instruction;
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
// Module declarations
#[macro_use]
pub mod macros;
@@ -138,7 +134,11 @@ fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
log("Generating assembly output...");
let instructions = codegen(nodes)?;
log("Compilation Successful");
Ok(instructions)
}
@@ -187,10 +187,7 @@ fn prepare_dependency(
let deps = Parser::get_dependencies(&nodes, path)?;
log(&format!(
"{:20} {:20}",
"Expanding PseudoInstructions", filename
));
log(&format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
// add a section instruction
nodes.insert(
@@ -198,9 +195,9 @@ fn prepare_dependency(
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
);
for n in &nodes {
println!("{n}");
}
// for n in &nodes {
// println!("{n}");
// }
program.add_module(nodes);
+22 -3
View File
@@ -51,19 +51,26 @@ impl fmt::Display for Node {
.as_ref()
.map_or_else(String::new, |symbol| format!("{symbol}:\n"));
let args = self
.args()
.into_iter()
.map(|arg| arg.to_string())
.collect::<Vec<_>>()
.join(" ");
write!(
f,
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{:?} \x1b[0m",
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m",
symbol,
self.opcode(),
self.args()
args,
)
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ( module: {})", self.name, self.module)
write!(f, "{} [ID:{}]", self.name, self.module)
}
}
@@ -174,6 +181,18 @@ pub enum Token {
Opcode(Opcode),
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Symbol(symbol) => write!(f, "{}", symbol),
Self::Register(register) => write!(f, "{}", register),
Self::Immediate(immediate) => write!(f, "{}", immediate),
Self::StringLit(string_lit) => write!(f, "{}", string_lit),
Self::Opcode(opcode) => write!(f, "{}", opcode),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TokenType {
Symbol,
+4 -6
View File
@@ -113,11 +113,10 @@ impl Parser {
let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() {
if expect_type!(next, Immediate).is_ok() {
if let Ok(next) = self.peek_next()
&& expect_type!(next, Immediate).is_ok() {
offset = self.next()?;
}
}
args = vec![base, dest, offset];
}
@@ -125,11 +124,10 @@ impl Parser {
let base = expect_type!(self.next()?, Register)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() {
if expect_type!(next, Immediate).is_ok() {
if let Ok(next) = self.peek_next()
&& expect_type!(next, Immediate).is_ok() {
offset = self.next()?;
}
}
args = vec![base, dest, offset];
}
+6 -1
View File
@@ -13,11 +13,16 @@
)]
pub mod assembler;
pub mod brainf;
pub mod image_builder;
pub mod tooling;
mod util;
pub mod prelude {
pub use crate::assembler::CompilerEngine;
pub use crate::image_builder;
pub use crate::tooling::brainf;
pub use crate::tooling::project;
}
use num_cpus as _;
use threadpool as _;
+11 -4
View File
@@ -1,4 +1,11 @@
use assembler::{brainf, prelude::*};
use common as _;
use num_cpus as _;
use threadpool as _;
use assembler::{
prelude::*,
tooling::{brainf, project},
};
use std::{fs, io::Write, path::PathBuf};
fn main() {
@@ -16,7 +23,7 @@ fn main() {
let mut file = match fs::File::create("brainf.dsb") {
Err(e) => {
eprintln!("Failed to create output file: {}", e);
eprintln!("Failed to create output file: {e}");
std::process::exit(1);
}
Ok(file) => file,
@@ -24,7 +31,7 @@ fn main() {
for instruction in result {
if let Err(e) = file.write(&instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e);
eprintln!("Failed to write to output file: {e}");
std::process::exit(1);
}
}
@@ -50,7 +57,7 @@ fn main() {
for instruction in result {
if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e);
eprintln!("Failed to write to output file: {e}");
std::process::exit(1);
}
}
+1
View File
@@ -1 +1,2 @@
pub mod brainf;
pub mod project;
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "c_compiler"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
chrono = "0.4.42"
+14
View File
@@ -0,0 +1,14 @@
int var_x = 5;
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int main() {
int result = var_x + factorial(5);
print(result);
return 0;
}
+926
View File
@@ -0,0 +1,926 @@
#!/usr/bin/env python3
"""
Simple C to DSA Assembly Compiler
Supports a subset of C including:
- int variables and functions
- Arithmetic operations (+, -, *, /)
- Comparisons (==, !=, <, >, <=, >=)
- If/else statements
- While loops
- Function calls
- Return statements
"""
import re
import sys
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
from pprint import pprint
import json
class TokenType(Enum):
# Keywords
INT = "int"
IF = "if"
ELSE = "else"
WHILE = "while"
RETURN = "return"
# Identifiers and literals
IDENTIFIER = "IDENTIFIER"
NUMBER = "NUMBER"
# Operators
PLUS = "+"
MINUS = "-"
STAR = "*"
SLASH = "/"
ASSIGN = "="
EQ = "=="
NE = "!="
LT = "<"
GT = ">"
LE = "<="
GE = ">="
# Delimiters
LPAREN = "("
RPAREN = ")"
LBRACE = "{"
RBRACE = "}"
SEMICOLON = ";"
COMMA = ","
EOF = "EOF"
@dataclass
class Token:
type: TokenType
value: str
line: int
col: int
class Lexer:
def __init__(self, source: str):
self.source = source
self.pos = 0
self.line = 1
self.col = 1
self.tokens = []
def error(self, msg: str):
raise SyntaxError(f"Lexer error at line {self.line}, col {self.col}: {msg}")
def peek(self, offset: int = 0) -> Optional[str]:
pos = self.pos + offset
return self.source[pos] if pos < len(self.source) else None
def advance(self) -> Optional[str]:
if self.pos >= len(self.source):
return None
char = self.source[self.pos]
self.pos += 1
if char == "\n":
self.line += 1
self.col = 1
else:
self.col += 1
return char
def skip_whitespace(self):
while self.peek() and self.peek() in " \t\n\r":
self.advance()
def skip_comment(self):
if self.peek() == "/" and self.peek(1) == "/":
while self.peek() and self.peek() != "\n":
self.advance()
self.advance() # skip newline
def read_number(self) -> str:
num = ""
while self.peek() and self.peek().isdigit():
num += self.advance()
return num
def read_identifier(self) -> str:
ident = ""
while self.peek() and (self.peek().isalnum() or self.peek() == "_"):
ident += self.advance()
return ident
def tokenize(self) -> List[Token]:
keywords = {
"int": TokenType.INT,
"if": TokenType.IF,
"else": TokenType.ELSE,
"while": TokenType.WHILE,
"return": TokenType.RETURN,
}
while self.pos < len(self.source):
self.skip_whitespace()
self.skip_comment()
if self.pos >= len(self.source):
break
line, col = self.line, self.col
char = self.peek()
# Numbers
if char.isdigit():
num = self.read_number()
self.tokens.append(Token(TokenType.NUMBER, num, line, col))
# Identifiers and keywords
elif char.isalpha() or char == "_":
ident = self.read_identifier()
token_type = keywords.get(ident, TokenType.IDENTIFIER)
self.tokens.append(Token(token_type, ident, line, col))
# Two-character operators
elif char == "=" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.EQ, "==", line, col))
elif char == "!" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.NE, "!=", line, col))
elif char == "<" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.LE, "<=", line, col))
elif char == ">" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.GE, ">=", line, col))
# Single-character operators
elif char == "+":
self.advance()
self.tokens.append(Token(TokenType.PLUS, "+", line, col))
elif char == "-":
self.advance()
self.tokens.append(Token(TokenType.MINUS, "-", line, col))
elif char == "*":
self.advance()
self.tokens.append(Token(TokenType.STAR, "*", line, col))
elif char == "/":
self.advance()
self.tokens.append(Token(TokenType.SLASH, "/", line, col))
elif char == "=":
self.advance()
self.tokens.append(Token(TokenType.ASSIGN, "=", line, col))
elif char == "<":
self.advance()
self.tokens.append(Token(TokenType.LT, "<", line, col))
elif char == ">":
self.advance()
self.tokens.append(Token(TokenType.GT, ">", line, col))
elif char == "(":
self.advance()
self.tokens.append(Token(TokenType.LPAREN, "(", line, col))
elif char == ")":
self.advance()
self.tokens.append(Token(TokenType.RPAREN, ")", line, col))
elif char == "{":
self.advance()
self.tokens.append(Token(TokenType.LBRACE, "{", line, col))
elif char == "}":
self.advance()
self.tokens.append(Token(TokenType.RBRACE, "}", line, col))
elif char == ";":
self.advance()
self.tokens.append(Token(TokenType.SEMICOLON, ";", line, col))
elif char == ",":
self.advance()
self.tokens.append(Token(TokenType.COMMA, ",", line, col))
else:
self.error(f"Unexpected character: {char}")
self.tokens.append(Token(TokenType.EOF, "", self.line, self.col))
return self.tokens
# AST Node classes
@dataclass
class ASTNode:
pass
@dataclass
class Program(ASTNode):
declarations: List["Declaration"]
@dataclass
class Declaration(ASTNode):
pass
@dataclass
class FunctionDecl(Declaration):
name: str
params: List[str]
body: "CompoundStmt"
@dataclass
class VarDecl(Declaration):
name: str
init: Optional["Expression"] = None
@dataclass
class Statement(ASTNode):
pass
@dataclass
class CompoundStmt(Statement):
statements: List[Statement]
@dataclass
class ExprStmt(Statement):
expr: Optional["Expression"]
@dataclass
class IfStmt(Statement):
condition: "Expression"
then_stmt: Statement
else_stmt: Optional[Statement] = None
@dataclass
class WhileStmt(Statement):
condition: "Expression"
body: Statement
@dataclass
class ReturnStmt(Statement):
expr: Optional["Expression"]
@dataclass
class Expression(ASTNode):
pass
@dataclass
class BinaryOp(Expression):
op: str
left: Expression
right: Expression
@dataclass
class UnaryOp(Expression):
op: str
operand: Expression
@dataclass
class AssignExpr(Expression):
name: str
value: Expression
@dataclass
class VarExpr(Expression):
name: str
@dataclass
class NumberExpr(Expression):
value: int
@dataclass
class CallExpr(Expression):
name: str
args: List[Expression]
class Parser:
def __init__(self, tokens: List[Token]):
self.tokens = tokens
self.pos = 0
def error(self, msg: str):
token = self.current()
raise SyntaxError(f"Parser error at line {token.line}, col {token.col}: {msg}")
def current(self) -> Token:
return self.tokens[self.pos] if self.pos < len(self.tokens) else self.tokens[-1]
def peek(self, offset: int = 0) -> Token:
pos = self.pos + offset
return self.tokens[pos] if pos < len(self.tokens) else self.tokens[-1]
def advance(self) -> Token:
token = self.current()
if self.pos < len(self.tokens) - 1:
self.pos += 1
return token
def expect(self, token_type: TokenType) -> Token:
token = self.current()
if token.type != token_type:
self.error(f"Expected {token_type.value}, got {token.type.value}")
return self.advance()
def parse(self) -> Program:
declarations = []
while self.current().type != TokenType.EOF:
declarations.append(self.parse_declaration())
return Program(declarations)
def parse_declaration(self) -> Declaration:
self.expect(TokenType.INT)
name = self.expect(TokenType.IDENTIFIER).value
if self.current().type == TokenType.LPAREN:
# Function declaration
self.advance()
params = []
if self.current().type != TokenType.RPAREN:
self.expect(TokenType.INT)
params.append(self.expect(TokenType.IDENTIFIER).value)
while self.current().type == TokenType.COMMA:
self.advance()
self.expect(TokenType.INT)
params.append(self.expect(TokenType.IDENTIFIER).value)
self.expect(TokenType.RPAREN)
body = self.parse_compound_stmt()
return FunctionDecl(name, params, body)
else:
# Variable declaration
init = None
if self.current().type == TokenType.ASSIGN:
self.advance()
init = self.parse_expression()
self.expect(TokenType.SEMICOLON)
return VarDecl(name, init)
def parse_compound_stmt(self) -> CompoundStmt:
self.expect(TokenType.LBRACE)
statements = []
while self.current().type != TokenType.RBRACE:
statements.append(self.parse_statement())
self.expect(TokenType.RBRACE)
return CompoundStmt(statements)
def parse_statement(self) -> Statement:
token = self.current()
if token.type == TokenType.LBRACE:
return self.parse_compound_stmt()
elif token.type == TokenType.IF:
return self.parse_if_stmt()
elif token.type == TokenType.WHILE:
return self.parse_while_stmt()
elif token.type == TokenType.RETURN:
return self.parse_return_stmt()
elif token.type == TokenType.INT:
# Local variable declaration
self.advance()
name = self.expect(TokenType.IDENTIFIER).value
init = None
if self.current().type == TokenType.ASSIGN:
self.advance()
init = self.parse_expression()
self.expect(TokenType.SEMICOLON)
return ExprStmt(AssignExpr(name, init) if init else None)
else:
expr = (
self.parse_expression()
if self.current().type != TokenType.SEMICOLON
else None
)
self.expect(TokenType.SEMICOLON)
return ExprStmt(expr)
def parse_if_stmt(self) -> IfStmt:
self.expect(TokenType.IF)
self.expect(TokenType.LPAREN)
condition = self.parse_expression()
self.expect(TokenType.RPAREN)
then_stmt = self.parse_statement()
else_stmt = None
if self.current().type == TokenType.ELSE:
self.advance()
else_stmt = self.parse_statement()
return IfStmt(condition, then_stmt, else_stmt)
def parse_while_stmt(self) -> WhileStmt:
self.expect(TokenType.WHILE)
self.expect(TokenType.LPAREN)
condition = self.parse_expression()
self.expect(TokenType.RPAREN)
body = self.parse_statement()
return WhileStmt(condition, body)
def parse_return_stmt(self) -> ReturnStmt:
self.expect(TokenType.RETURN)
expr = None
if self.current().type != TokenType.SEMICOLON:
expr = self.parse_expression()
self.expect(TokenType.SEMICOLON)
return ReturnStmt(expr)
def parse_expression(self) -> Expression:
return self.parse_assignment()
def parse_assignment(self) -> Expression:
expr = self.parse_comparison()
if self.current().type == TokenType.ASSIGN:
if not isinstance(expr, VarExpr):
self.error("Invalid assignment target")
self.advance()
value = self.parse_assignment()
return AssignExpr(expr.name, value)
return expr
def parse_comparison(self) -> Expression:
expr = self.parse_additive()
while self.current().type in [
TokenType.EQ,
TokenType.NE,
TokenType.LT,
TokenType.GT,
TokenType.LE,
TokenType.GE,
]:
op = self.advance().value
right = self.parse_additive()
expr = BinaryOp(op, expr, right)
return expr
def parse_additive(self) -> Expression:
expr = self.parse_multiplicative()
while self.current().type in [TokenType.PLUS, TokenType.MINUS]:
op = self.advance().value
right = self.parse_multiplicative()
expr = BinaryOp(op, expr, right)
return expr
def parse_multiplicative(self) -> Expression:
expr = self.parse_unary()
while self.current().type in [TokenType.STAR, TokenType.SLASH]:
op = self.advance().value
right = self.parse_unary()
expr = BinaryOp(op, expr, right)
return expr
def parse_unary(self) -> Expression:
if self.current().type in [TokenType.PLUS, TokenType.MINUS]:
op = self.advance().value
operand = self.parse_unary()
return UnaryOp(op, operand)
return self.parse_primary()
def parse_primary(self) -> Expression:
token = self.current()
if token.type == TokenType.NUMBER:
self.advance()
return NumberExpr(int(token.value))
elif token.type == TokenType.IDENTIFIER:
name = self.advance().value
if self.current().type == TokenType.LPAREN:
# Function call
self.advance()
args = []
if self.current().type != TokenType.RPAREN:
args.append(self.parse_expression())
while self.current().type == TokenType.COMMA:
self.advance()
args.append(self.parse_expression())
self.expect(TokenType.RPAREN)
return CallExpr(name, args)
else:
return VarExpr(name)
elif token.type == TokenType.LPAREN:
self.advance()
expr = self.parse_expression()
self.expect(TokenType.RPAREN)
return expr
else:
self.error(f"Unexpected token: {token.type.value}")
class CodeGenerator:
def __init__(self):
self.output = []
self.label_counter = 0
self.string_counter = 0
self.functions = {}
self.current_function = None
self.local_vars = {}
self.global_vars = {}
self.register_pool = [f"rg{i:x}" for i in range(16)]
self.used_registers = set()
def new_label(self, prefix: str = "L") -> str:
label = f"{prefix}{self.label_counter}"
self.label_counter += 1
return label
def allocate_register(self) -> str:
for reg in self.register_pool:
if reg not in self.used_registers:
self.used_registers.add(reg)
return reg
raise RuntimeError("Out of registers")
def free_register(self, reg: str):
self.used_registers.discard(reg)
def emit(self, code: str):
self.output.append(code)
def generate(self, program: Program) -> str:
# Emit data section
self.emit("// Global variables")
for decl in program.declarations:
if isinstance(decl, VarDecl):
self.global_vars[decl.name] = f"var_{decl.name}"
if decl.init:
if isinstance(decl.init, NumberExpr):
self.emit(f"dw var_{decl.name}: {decl.init.value}")
else:
self.emit(f"dw var_{decl.name}: 0")
else:
self.emit(f"dw var_{decl.name}: 0")
self.emit("")
self.emit("// Entry point")
self.emit("dw stack_bottom: 0x10000")
self.emit("")
self.emit("init:")
self.emit(" ldw stack_bottom, spr")
self.emit(" mov spr, bpr")
self.emit(" push zero")
self.emit(" call main")
self.emit(" pop rg0")
self.emit(" hlt")
self.emit("")
# Emit functions
for decl in program.declarations:
if isinstance(decl, FunctionDecl):
self.generate_function(decl)
return "\n".join(self.output)
def generate_function(self, func: FunctionDecl):
self.current_function = func.name
self.functions[func.name] = func
self.local_vars = {}
# Map parameters to stack offsets
# Parameters start at bpr+8 (after return addr at bpr+4)
for i, param in enumerate(func.params):
self.local_vars[param] = 8 + (i * 4)
self.emit(f"{func.name}:")
self.emit(" push bpr")
self.emit(" mov spr, bpr")
self.emit("")
# Generate function body
self.generate_compound_stmt(func.body)
# Default return if no explicit return
self.emit("// default return")
self.emit(f"{func.name}_end:")
self.emit(" mov bpr, spr")
self.emit(" pop bpr")
self.emit(" return")
self.emit("")
def generate_compound_stmt(self, stmt: CompoundStmt):
for s in stmt.statements:
self.generate_statement(s)
def generate_statement(self, stmt: Statement):
if isinstance(stmt, CompoundStmt):
self.generate_compound_stmt(stmt)
elif isinstance(stmt, ExprStmt):
if stmt.expr:
reg = self.generate_expression(stmt.expr)
self.free_register(reg)
elif isinstance(stmt, IfStmt):
self.generate_if_stmt(stmt)
elif isinstance(stmt, WhileStmt):
self.generate_while_stmt(stmt)
elif isinstance(stmt, ReturnStmt):
self.generate_return_stmt(stmt)
def generate_if_stmt(self, stmt: IfStmt):
else_label = self.new_label("else")
end_label = self.new_label("endif")
# Evaluate condition
cond_reg = self.generate_expression(stmt.condition)
self.emit(f" cmp {cond_reg}, zero")
self.free_register(cond_reg)
if stmt.else_stmt:
self.emit(f" jeq {else_label}")
else:
self.emit(f" jeq {end_label}")
# Then branch
self.generate_statement(stmt.then_stmt)
if stmt.else_stmt:
self.emit(f" jmp {end_label}")
self.emit(f"{else_label}:")
self.generate_statement(stmt.else_stmt)
self.emit(f"{end_label}:")
def generate_while_stmt(self, stmt: WhileStmt):
start_label = self.new_label("while_start")
end_label = self.new_label("while_end")
self.emit(f"{start_label}:")
# Evaluate condition
cond_reg = self.generate_expression(stmt.condition)
self.emit(f" cmp {cond_reg}, zero")
self.free_register(cond_reg)
self.emit(f" jeq {end_label}")
# Loop body
self.generate_statement(stmt.body)
self.emit(f" jmp {start_label}")
self.emit(f"{end_label}:")
def generate_return_stmt(self, stmt: ReturnStmt):
if stmt.expr:
reg = self.generate_expression(stmt.expr)
# Store return value at spr+8 according to calling convention
self.emit(f" stw {reg}, spr, 8")
self.free_register(reg)
self.emit(f" jmp {self.current_function}_end")
def generate_expression(self, expr: Expression) -> str:
if isinstance(expr, NumberExpr):
reg = self.allocate_register()
if expr.value <= 0xFFFF and expr.value >= 0:
self.emit(f" lli {expr.value}, {reg}")
if expr.value > 0xFF:
self.emit(f" lui {expr.value >> 16}, {reg}")
else:
self.emit(f" lli {expr.value & 0xFFFF}, {reg}")
self.emit(f" lui {(expr.value >> 16) & 0xFFFF}, {reg}")
return reg
elif isinstance(expr, VarExpr):
reg = self.allocate_register()
if expr.name in self.local_vars:
offset = self.local_vars[expr.name]
self.emit(f" ldw bpr, {reg}, {offset}")
elif expr.name in self.global_vars:
label = self.global_vars[expr.name]
self.emit(f" ldw {label}, {reg}")
else:
raise RuntimeError(f"Undefined variable: {expr.name}")
return reg
elif isinstance(expr, AssignExpr):
value_reg = self.generate_expression(expr.value)
if expr.name in self.local_vars:
offset = self.local_vars[expr.name]
self.emit(f" stw {value_reg}, bpr, {offset}")
elif expr.name in self.global_vars:
label = self.global_vars[expr.name]
self.emit(f" stw {value_reg}, {label}")
else:
# New local variable - allocate after params and return value space
# Start local variables at offset -4 from bpr (growing downward)
offset = -(len([v for v in self.local_vars.values() if v < 0]) + 1) * 4
self.local_vars[expr.name] = offset
self.emit(f" stw {value_reg}, bpr, {offset}")
return value_reg
elif isinstance(expr, BinaryOp):
return self.generate_binary_op(expr)
elif isinstance(expr, UnaryOp):
operand_reg = self.generate_expression(expr.operand)
result_reg = self.allocate_register()
if expr.op == "-":
self.emit(f" lwi 0, {result_reg}")
self.emit(f" sub {result_reg}, {operand_reg}, {result_reg}")
else: # +
self.emit(f" mov {operand_reg}, {result_reg}")
self.free_register(operand_reg)
return result_reg
elif isinstance(expr, CallExpr):
# First, make space for return value (must be pushed BEFORE arguments)
temp_reg = self.allocate_register()
# Then push arguments in reverse order
arg_regs = []
for arg in reversed(expr.args):
reg = self.generate_expression(arg)
self.emit(f" push {reg}")
arg_regs.append(reg)
# Call function
self.emit(f" call {expr.name}")
# Get return value (it's now on top of stack)
self.emit(f" pop {temp_reg}")
# Clean up remaining args
for i in range(len(arg_regs) - 1):
self.emit(f" pop zero")
# Free the arg registers
for reg in arg_regs:
self.free_register(reg)
return temp_reg
else:
raise RuntimeError(f"Unknown expression type: {type(expr)}")
def generate_binary_op(self, expr: BinaryOp) -> str:
# For operations that might contain function calls, we need to be careful
# about register allocation. Evaluate left, save it, evaluate right.
left_reg = self.generate_expression(expr.left)
# If right side contains a function call, we need to save left_reg
# For now, always save to be safe
saved_reg = self.allocate_register()
self.emit(f" mov {left_reg}, {saved_reg}")
self.free_register(left_reg)
right_reg = self.generate_expression(expr.right)
result_reg = self.allocate_register()
if expr.op == "+":
self.emit(f" add {left_reg}, {right_reg}, {result_reg}")
elif expr.op == "-":
self.emit(f" sub {left_reg}, {right_reg}, {result_reg}")
elif expr.op == "*":
# Simple multiplication using loop
temp_label = self.new_label("mult")
end_label = self.new_label("mult_end")
self.emit(f" lli 0, {result_reg}")
self.emit(f"{temp_label}:")
self.emit(f" cmp {right_reg}, zero")
self.emit(f" jeq {end_label}")
self.emit(f" add {result_reg}, {left_reg}, {result_reg}")
self.emit(f" dec {right_reg}")
self.emit(f" jmp {temp_label}")
self.emit(f"{end_label}:")
elif expr.op == "/":
# Simple division using loop
temp_label = self.new_label("div")
end_label = self.new_label("div_end")
self.emit(f" lli 0, {result_reg}")
self.emit(f"{temp_label}:")
self.emit(f" cmp {left_reg}, {right_reg}")
self.emit(f" jlt {end_label}")
self.emit(f" sub {left_reg}, {right_reg}, {left_reg}")
self.emit(f" inc {result_reg}")
self.emit(f" jmp {temp_label}")
self.emit(f"{end_label}:")
elif expr.op in ["==", "!=", "<", ">", "<=", ">="]:
self.emit(f" cmp {left_reg}, {right_reg}")
# Result is 1 if condition true, 0 otherwise
self.emit(f" lli 0, {result_reg}")
true_label = self.new_label("cmp_true")
end_label = self.new_label("cmp_end")
if expr.op == "==":
self.emit(f" jeq {true_label}")
elif expr.op == "!=":
self.emit(f" jne {true_label}")
elif expr.op == "<":
self.emit(f" jlt {true_label}")
elif expr.op == ">":
self.emit(f" jgt {true_label}")
elif expr.op == "<=":
self.emit(f" jle {true_label}")
elif expr.op == ">=":
self.emit(f" jge {true_label}")
self.emit(f" jmp {end_label}")
self.emit(f"{true_label}:")
self.emit(f" lli 1, {result_reg}")
self.emit(f"{end_label}:")
self.free_register(left_reg)
self.free_register(right_reg)
return result_reg
def compile_c_to_asm(source: str) -> str:
"""Compile C source code to DSA assembly."""
lexer = Lexer(source)
tokens = lexer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
codegen = CodeGenerator()
assembly = codegen.generate(ast)
return assembly
def main():
if len(sys.argv) < 2:
print("Usage: python compiler.py <input.c> [output.dsa]")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else input_file.replace(".c", ".dsa")
with open(input_file, "r") as f:
source = f.read()
try:
assembly = compile_c_to_asm(source)
with open(output_file, "w") as f:
f.write(assembly)
print(f"Successfully compiled {input_file} to {output_file}")
except (SyntaxError, RuntimeError) as e:
print(f"Compilation error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
# # Example usage
# if len(sys.argv) > 1:
# example_c = sys.argv[1]
# else:
# example_c = """
# int factorial(int n) {
# if (n <= 1) {
# return 1;
# }
# return n * factorial(n - 1);
# }
# int main() {
# int result;
# result = factorial(5);
# return result;
# }
# """
# print("Example C program:")
# print(example_c)
# print("\n" + "="*60 + "\n")
# print("Generated DSA assembly:")
# print(compile_c_to_asm(example_c))
+12
View File
@@ -0,0 +1,12 @@
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int main() {
int res = factorial(3);
printnum(res);
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
include print: "lib/io/print.dsa"
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int add_(int a, int b) {
return a + b;
}
int greater(int a, int b) {
if (a + a > b + b) {
return a;
} else {
return b + a;
}
}
int main() {
printnum(-5);
return 0;
}
+5
View File
@@ -0,0 +1,5 @@
// Imports
include maths: "./lib/maths/core.dsa"
// Reserved Memory
+106
View File
@@ -0,0 +1,106 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Register {
// general purpose registers
Rg0,
Rg1,
Rg2,
Rg3,
Rg4,
Rg5,
Rg6,
Rg7,
Rg8,
Rg9,
Rga,
Rgb,
Rgc,
Rgd,
Rge,
Rgf,
// special purpose registers
Acc,
Spr,
Bpr,
Ret,
Idr,
Mmr,
Zero,
NoReg,
// system registers - can't be written to by instructions.
Mar,
Mdr,
Sts,
Cir,
Pcx,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
#[non_exhaustive]
/// A list of all current instructions in the DSA Assembly language.
pub enum Instruction {
// No-op
Nop = 0x0,
// Data transfer instructions
Mov(Register, Register) = 0x1,
Movs(Register, Register) = 0x2,
Ldb(Register, Register, Option<u32>) = 0x3,
Ldbs(Register, Register, Option<u32>) = 0x4,
Ldh(Register, Register, Option<u32>) = 0x5,
Ldhs(Register, Register, Option<u32>) = 0x6,
Ldw(Register, Register, Option<u32>) = 0x7,
Stb(Register, Register, Option<u32>) = 0x8,
Sth(Register, Register, Option<u32>) = 0x9,
Stw(Register, Register, Option<u32>) = 0xA,
Lli(u16, Register) = 0xB,
Lui(u16, Register) = 0xC,
// Jump Instructions
Jump(u16, Register) = 0xD,
JumpEq(u16, Register) = 0xE,
JumpNeq(u16, Register) = 0xF,
JumpGt(u16, Register) = 0x10,
JumpGe(u16, Register) = 0x11,
JumpLt(u16, Register) = 0x12,
JumpLe(u16, Register) = 0x13,
// Comparison
Compare(Register, Register) = 0x14,
// // Arithmetic
// Add(args::RTypeArgs) = 0x19,
// Sub(args::RTypeArgs) = 0x1A,
// Increment(args::RTypeArgs) = 0x15,
// Decrement(args::RTypeArgs) = 0x16,
// ShiftLeft(args::RTypeArgs) = 0x17,
// ShiftRight(args::RTypeArgs) = 0x18,
// // Logical
// And(args::RTypeArgs) = 0x1B,
// Or(args::RTypeArgs) = 0x1C,
// Not(args::RTypeArgs) = 0x1D,
// Xor(args::RTypeArgs) = 0x1E,
// Nand(args::RTypeArgs) = 0x1F,
// Nor(args::RTypeArgs) = 0x20,
// Xnor(args::RTypeArgs) = 0x21,
// // Misc
// Interrupt(Interrupt) = 0x22,
// IntReturn = 0x23,
// Halt = 0x24,
// // Immediate Arithmetic
// AddImmediate(args::ITypeArgs) = 0x25,
// SubImmediate(args::ITypeArgs) = 0x26,
// Fake Instructions
Data(u32) = 0x3E,
Segment(u32) = 0x3F,
}
+599
View File
@@ -0,0 +1,599 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::LazyLock;
use std::sync::atomic::AtomicU32;
use std::time::SystemTime;
use chrono::{DateTime, Local};
use crate::registers::RegisterAllocator;
use crate::{block, cmd, comment, dsa};
use crate::parser::{
BinaryOperator, ConstExpr, Declaration, Expression, Parameter, Program, Statement,
UnaryOperator,
};
pub struct CodeGenerator {
ast: Program,
imports: HashMap<String, String>,
globals: Vec<String>,
functions: Vec<String>,
symbols: Vec<String>,
allocator: RegisterAllocator,
}
static GLOBAL_METHODS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from([("print", "print::print"), ("printnum", "print::print_num")])
});
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());
}
pub fn generate(&mut self) -> Result<String, String> {
// 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 { name, .. } => self.symbols.push(name),
Declaration::Function { name, .. } => self.symbols.push(name),
Declaration::Import { 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, String> {
let datetime: DateTime<Local> = SystemTime::now().into();
Ok(dsa![
"",
comment!("GENERATED BY DSA-C 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!("Function return boilerplate"),
block! [ "_ret"
dsa![mov bpr, spr],
dsa![pop bpr],
dsa![return]
],
// 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<(), String> {
match block {
Declaration::Variable { name, init } => self.generate_global(&name, init),
Declaration::Function {
name,
return_type,
params,
body,
} => {
let func = self.generate_function(&name, &params, &body).join("\n");
self.functions.push(format!("{func}\n"));
}
Declaration::Import { name, path } => {
self.imports.insert(name, path);
}
};
Ok(())
}
// Example: Generate code for a function
fn generate_function(
&mut self,
name: &str,
params: &[Parameter],
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(&param.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>, String> {
let mut code = Vec::new();
match stmt {
Statement::Assign {
name,
declare_type,
value,
} => {
if let Some(expr) = value {
// Evaluate expression
let (result_reg, expr_code) = self.generate_expression(expr)?;
code.extend(expr_code);
// Store result in variable
let store_code = self.allocator.store_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(name)?;
}
}
Statement::Return { expr } => {
if let Some(e) = expr {
let (result_reg, expr_code) = self.generate_expression(e)?;
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)?;
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)?;
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::Expression { expr } => {
let (result_reg, expr_code) = self.generate_expression(expr)?;
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,
) -> Result<(String, Vec<String>), String> {
let mut code = Vec::new();
match expr {
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, .. } => {
let (reg, load_code) = self.allocator.load_var(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)?;
code.extend(left_code);
// Evaluate right operand
let (right_reg, right_code) = self.generate_expression(right)?;
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));
}
_ => return Err(format!("Unsupported binary operator: {:?}", op)),
}
// 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 } => {
// Save caller-saved registers and track which ones we saved
let saved_regs = self.allocator.get_caller_saved_registers();
for reg in &saved_regs {
code.push(format!("\tpush {}", reg));
}
// Evaluate and push arguments in reverse order
let mut arg_regs = Vec::new();
for arg in args.iter().rev() {
let (arg_reg, arg_code) = self.generate_expression(arg)?;
code.extend(arg_code);
code.push(format!("\tpush {}", arg_reg));
arg_regs.push(arg_reg);
}
if GLOBAL_METHODS.contains_key(name.as_str()) {
code.push(format!("\tcall {}", GLOBAL_METHODS[name.as_str()]));
} else if self.symbols.contains(name) {
// Call local function
code.push(format!("\tcall {}", name));
} else {
return Err(format!("undefined function {name}"));
}
// Result is in rg0, allocate a register and move it
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
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());
}
}
// 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(&reg);
}
Ok((result_reg, code))
}
Expression::Unary { op, operand } => {
let (operand_reg, operand_code) = self.generate_expression(operand)?;
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));
}
}
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 well 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 reuses the same buffer.
write!(s, "{}\n", $arg).expect("write to String failed");
)*
s
}};
}
// ──────────────────────── dsa! ────────────────────────
// A tiny helper that just turns its tokenstream into a string.
// The trailing comma is kept its part of the syntax you want.
#[macro_export]
macro_rules! cmd {
($($tokens:tt)*) => {{
// Well 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 thats 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) }};
}
+335
View File
@@ -0,0 +1,335 @@
// ============================================================================
// Token Types
// ============================================================================
#[derive(Debug, Clone, PartialEq)]
pub enum TokenType {
// Keywords
Int,
If,
Else,
While,
Return,
Include,
// Identifiers and literals
Identifier(String),
Number(i32),
String(String),
Char(char),
// Operators
Plus,
Minus,
Star,
Slash,
Assign,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
// Delimiters
LParen,
RParen,
LBrace,
RBrace,
Semicolon,
Comma,
Colon,
Namespace,
Eof,
}
pub enum Type {
Int32,
Int16,
Int8,
Uint32,
Uint16,
Uint8,
Char,
}
#[derive(Debug, Clone)]
pub struct Token {
pub token_type: TokenType,
pub line: usize,
pub col: usize,
}
impl Token {
pub fn new(token_type: TokenType, line: usize, col: usize) -> Self {
Self {
token_type,
line,
col,
}
}
}
// ============================================================================
// Lexer
// ============================================================================
pub struct Lexer {
source: Vec<char>,
pos: usize,
line: usize,
col: usize,
}
impl Lexer {
pub fn new(source: &str) -> Self {
Self {
source: source.chars().collect(),
pos: 0,
line: 1,
col: 1,
}
}
fn error(&self, msg: &str) -> String {
format!(
"Lexer error at line {}, col {}: {}",
self.line, self.col, msg
)
}
fn peek(&self, offset: usize) -> Option<char> {
self.source.get(self.pos + offset).copied()
}
fn advance(&mut self) -> Option<char> {
if self.pos >= self.source.len() {
return None;
}
let ch = self.source[self.pos];
self.pos += 1;
if ch == '\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
Some(ch)
}
fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek(0) {
if ch.is_whitespace() {
self.advance();
} else {
break;
}
}
}
fn skip_comment(&mut self) {
if self.peek(0) == Some('/') && self.peek(1) == Some('/') {
while let Some(ch) = self.peek(0) {
if ch == '\n' {
break;
}
self.advance();
}
}
}
fn read_number(&mut self) -> i32 {
let mut num_str = String::new();
while let Some(ch) = self.peek(0) {
if ch.is_ascii_digit() {
num_str.push(ch);
self.advance();
} else {
break;
}
}
num_str.parse().unwrap_or(0)
}
fn read_identifier(&mut self) -> String {
let mut ident = String::new();
while let Some(ch) = self.peek(0) {
if ch.is_alphanumeric() || ch == '_' {
ident.push(ch);
self.advance();
} else {
break;
}
}
ident
}
fn read_string(&mut self) -> Result<String, String> {
let mut string = String::new();
self.advance(); // Consume the opening quote
while let Some(ch) = self.peek(0) {
if ch == '"' {
self.advance(); // Consume the closing quote
return Ok(string);
} else if ch == '\\' {
self.advance(); // Consume the backslash
if let Some(escaped_char) = self.peek(0) {
string.push(escaped_char);
self.advance();
}
} else {
string.push(ch);
self.advance();
}
}
Err(String::from("Unexpected EOF"))
}
fn read_char(&mut self) -> Result<char, String> {
self.advance(); // Consume the opening quote
if let Some(ch) = self.peek(0) {
self.advance();
if self.peek(0) == Some('\'') {
self.advance();
return Ok(ch);
} else {
Err(String::from("expected closing quote"))
}
} else {
Err(String::from("expected character"))
}
}
pub fn tokenize(&mut self) -> Result<Vec<Token>, String> {
let mut tokens = Vec::new();
loop {
self.skip_whitespace();
self.skip_comment();
if self.pos >= self.source.len() {
break;
}
let line = self.line;
let col = self.col;
let ch = self.peek(0).unwrap();
let token_type = if ch.is_ascii_digit() {
let num = self.read_number();
TokenType::Number(num)
} else if ch == '"' {
let string = self.read_string()?;
TokenType::String(string)
} else if ch == '\'' {
let char = self.read_char()?;
TokenType::Char(char)
} else if ch.is_alphabetic() || ch == '_' {
let ident = self.read_identifier();
match ident.as_str() {
"int" => TokenType::Int,
"if" => TokenType::If,
"else" => TokenType::Else,
"while" => TokenType::While,
"return" => TokenType::Return,
"include" => TokenType::Include,
_ => TokenType::Identifier(ident),
}
} else {
match ch {
':' if self.peek(1) == Some(':') => {
self.advance();
self.advance();
TokenType::Namespace
}
':' => {
self.advance();
TokenType::Colon
}
'=' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Eq
}
'!' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Ne
}
'<' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Le
}
'>' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Ge
}
'+' => {
self.advance();
TokenType::Plus
}
'-' => {
self.advance();
TokenType::Minus
}
'*' => {
self.advance();
TokenType::Star
}
'/' => {
self.advance();
TokenType::Slash
}
'=' => {
self.advance();
TokenType::Assign
}
'<' => {
self.advance();
TokenType::Lt
}
'>' => {
self.advance();
TokenType::Gt
}
'(' => {
self.advance();
TokenType::LParen
}
')' => {
self.advance();
TokenType::RParen
}
'{' => {
self.advance();
TokenType::LBrace
}
'}' => {
self.advance();
TokenType::RBrace
}
';' => {
self.advance();
TokenType::Semicolon
}
',' => {
self.advance();
TokenType::Comma
}
_ => return Err(self.error(&format!("Unexpected character: {}", ch))),
}
};
tokens.push(Token::new(token_type, line, col));
}
tokens.push(Token::new(TokenType::Eof, self.line, self.col));
Ok(tokens)
}
}
+74
View File
@@ -0,0 +1,74 @@
use std::fmt;
use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser};
// mod assembly;
pub mod codegen;
pub mod lexer;
pub mod parser;
mod registers;
// ============================================================================
// Main & Tests
// ============================================================================
fn main() {
// read from input file: syntax "c_compiler <src.c> [output.dsa]"
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: c_compiler <src.c> [output.dsa]");
return;
}
let input_file = &args[1];
let output_file = if args.len() > 2 {
&args[2]
} else {
"output.dsa"
};
// read input
let input = std::fs::read_to_string(input_file).expect("Failed to read input file");
// Lexing
let mut lexer = Lexer::new(&input);
let tokens = match lexer.tokenize() {
Ok(tokens) => tokens,
Err(e) => {
eprintln!("Lexing error: {}", e);
return;
}
};
println!("Tokens:");
for token in &tokens {
println!(" {:?}", token.token_type);
}
println!();
// Parsing
let mut parser = Parser::new(tokens);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => {
eprintln!("Parsing error: {}", e);
return;
}
};
println!("AST:");
println!("{:#?}", ast);
// Code Gen
let mut generator = CodeGenerator::new(ast);
let result = match generator.generate() {
Ok(code) => code,
Err(e) => {
eprintln!("Parsing error: {}", e);
return;
}
};
std::fs::write(output_file, &result).expect("Failed to write output");
println!("Result written to {}", output_file);
}
+610
View File
@@ -0,0 +1,610 @@
// ============================================================================
// AST Node Types
// ============================================================================
use std::fmt;
use crate::lexer::{Token, TokenType};
#[derive(Debug, Clone)]
pub struct Program {
pub declarations: Vec<Declaration>,
}
#[derive(Debug, Clone)]
pub enum Declaration {
Function {
name: String,
return_type: Type,
params: Vec<Parameter>,
body: Block,
},
Variable {
name: String,
init: Option<ConstExpr>,
},
Import {
name: String,
path: String,
},
}
#[derive(Debug, Clone)]
pub struct Parameter {
pub name: String,
pub param_type: Type,
}
#[derive(Debug, Clone)]
pub enum Type {
Int,
Long,
Float,
Double,
Char,
Void,
Ptr(Box<Type>),
Array(Box<Type>, usize),
Struct(String),
}
pub type Block = Vec<Statement>;
#[derive(Debug, Clone)]
pub enum Statement {
Block(Block),
Assign {
// left side
name: String,
declare_type: Option<Type>,
// right side
value: Option<Box<Expression>>,
},
Expression {
expr: Expression,
},
If {
condition: Expression,
then_stmt: Block,
else_stmt: Block,
},
While {
condition: Expression,
body: Vec<Statement>,
},
Return {
expr: Option<Expression>,
},
}
#[derive(Debug, Clone)]
pub enum ConstExpr {
Number(i32),
String(String),
}
impl fmt::Display for ConstExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConstExpr::Number(n) => write!(f, "{}", n),
ConstExpr::String(s) => write!(f, "\"{}\"", s),
}
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Empty,
Binary {
op: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
},
Unary {
op: UnaryOperator,
operand: Box<Expression>,
},
Variable {
name: String,
expr_type: Option<Type>,
},
Number {
value: i32,
},
Call {
name: String,
args: Vec<Expression>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator {
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BinaryOperator::Add => write!(f, "+"),
BinaryOperator::Sub => write!(f, "-"),
BinaryOperator::Mul => write!(f, "*"),
BinaryOperator::Div => write!(f, "/"),
BinaryOperator::Eq => write!(f, "=="),
BinaryOperator::Ne => write!(f, "!="),
BinaryOperator::Lt => write!(f, "<"),
BinaryOperator::Gt => write!(f, ">"),
BinaryOperator::Le => write!(f, "<="),
BinaryOperator::Ge => write!(f, ">="),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOperator {
Plus,
Minus,
}
impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UnaryOperator::Plus => write!(f, "+"),
UnaryOperator::Minus => write!(f, "-"),
}
}
}
// ============================================================================
// Parser
// ============================================================================
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, pos: 0 }
}
fn error(&self, msg: &str) -> String {
let token = self.current();
format!(
"Parser error at line {}, col {}: {}",
token.line, token.col, msg
)
}
fn current(&self) -> &Token {
self.tokens
.get(self.pos)
.unwrap_or_else(|| self.tokens.last().unwrap())
}
fn peek(&self, offset: usize) -> &Token {
self.tokens
.get(self.pos + offset)
.unwrap_or_else(|| self.tokens.last().unwrap())
}
fn advance(&mut self) -> &Token {
if self.pos < self.tokens.len() - 1 {
self.pos += 1;
}
self.current()
}
fn expect(&mut self, expected: TokenType) -> Result<Token, String> {
let token = self.current().clone();
if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected)
{
return Err(self.error(&format!(
"Expected {:?}, got {:?}",
expected, token.token_type
)));
}
self.advance();
Ok(token)
}
pub fn parse(&mut self) -> Result<Program, String> {
let mut declarations = Vec::new();
while !matches!(self.current().token_type, TokenType::Eof) {
declarations.push(self.parse_declaration()?);
}
Ok(Program { declarations })
}
fn parse_declaration(&mut self) -> Result<Declaration, String> {
// check for an import
if let TokenType::Include = self.current().token_type {
self.advance();
let name =
if let TokenType::Identifier(id) = self.current().clone().token_type {
Some(id)
} else {
None
}
.ok_or(String::from("Expected identifier"))?;
self.advance();
self.expect(TokenType::Colon)?;
let path = if let TokenType::String(id) = self.current().clone().token_type {
Some(id)
} else {
None
}
.ok_or(String::from("Expected string literal"))?;
self.advance();
return Ok(Declaration::Import { name, path });
}
self.expect(TokenType::Int)?;
let name = match &self.current().token_type {
TokenType::Identifier(s) => s.clone(),
_ => return Err(self.error("Expected identifier")),
};
self.advance();
match &self.current().token_type {
TokenType::LParen => {
// Function declaration
self.advance();
let mut params = Vec::<Parameter>::new();
if !matches!(self.current().token_type, TokenType::RParen) {
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Parameter {
name: s.clone(),
param_type: Type::Int,
});
self.advance();
}
_ => return Err(self.error("Expected parameter name")),
}
while matches!(self.current().token_type, TokenType::Comma) {
self.advance();
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Parameter {
name: s.clone(),
param_type: Type::Int,
});
self.advance();
}
_ => return Err(self.error("Expected parameter name")),
}
}
}
self.expect(TokenType::RParen)?;
let body = self.parse_block()?;
Ok(Declaration::Function {
name,
params,
body,
return_type: Type::Int,
})
}
_ => {
// Variable declaration
let init = if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
if let TokenType::Number(n) = self.current().token_type {
self.advance();
Some(ConstExpr::Number(n))
} else {
return Err(self
.error("Expected constant in global variable declaration"));
}
} else {
None
};
self.expect(TokenType::Semicolon)?;
Ok(Declaration::Variable { name, init })
}
}
}
fn parse_block(&mut self) -> Result<Block, String> {
self.expect(TokenType::LBrace)?;
let mut statements = Vec::new();
while !matches!(self.current().token_type, TokenType::RBrace) {
statements.push(self.parse_statement()?);
}
self.expect(TokenType::RBrace)?;
Ok(statements)
}
fn parse_statement(&mut self) -> Result<Statement, String> {
match &self.current().token_type {
TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)),
TokenType::If => self.parse_if_stmt(),
TokenType::While => self.parse_while_stmt(),
TokenType::Return => self.parse_return_stmt(),
TokenType::Identifier(name) => {
let name = name.clone();
// peek ahead for open paren (func call expr)
if matches!(self.peek(1).token_type, TokenType::LParen) {
let expr = self.parse_expression()?; // a function call expr
self.expect(TokenType::Semicolon)?;
return Ok(Statement::Expression { expr });
}
self.advance(); // advance past identifier
// assignment expression
if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
let expr = self.parse_expression()?;
self.expect(TokenType::Semicolon)?;
Ok(Statement::Assign {
name,
value: Some(Box::new(expr)),
declare_type: None,
})
}
// var expression
else {
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression {
expr: Expression::Variable {
name,
expr_type: None,
},
})
}
}
TokenType::Int => {
// Local variable declaration
self.advance();
let name = match &self.current().token_type {
TokenType::Identifier(s) => s.clone(),
_ => return Err(self.error("Expected variable name")),
};
self.advance();
let init = if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
Some(self.parse_expression()?)
} else {
None
};
self.expect(TokenType::Semicolon)?;
// Convert to assignment expression statement
let expr = if let Some(init_expr) = init {
Statement::Assign {
name,
value: Some(Box::new(init_expr)),
declare_type: Some(Type::Int),
}
} else {
Statement::Assign {
name,
value: None,
declare_type: Some(Type::Int),
}
};
Ok(expr)
}
_ => {
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
Expression::Empty
} else {
self.parse_expression()?
};
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression { expr })
}
}
}
fn parse_if_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::If)?;
self.expect(TokenType::LParen)?;
let condition = self.parse_expression()?;
self.expect(TokenType::RParen)?;
let then_stmt = self.parse_block()?;
let else_stmt = if matches!(self.current().token_type, TokenType::Else) {
self.advance();
self.parse_block()?
} else {
Vec::new()
};
Ok(Statement::If {
condition,
then_stmt,
else_stmt,
})
}
fn parse_while_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::While)?;
self.expect(TokenType::LParen)?;
let condition = self.parse_expression()?;
self.expect(TokenType::RParen)?;
let body = self.parse_block()?;
Ok(Statement::While { condition, body })
}
fn parse_return_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::Return)?;
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
None
} else {
Some(self.parse_expression()?)
};
self.expect(TokenType::Semicolon)?;
Ok(Statement::Return { expr })
}
fn parse_expression(&mut self) -> Result<Expression, String> {
self.parse_comparison()
}
fn parse_comparison(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_additive()?;
while let Some(op) = match &self.current().token_type {
TokenType::Eq => Some(BinaryOperator::Eq),
TokenType::Ne => Some(BinaryOperator::Ne),
TokenType::Lt => Some(BinaryOperator::Lt),
TokenType::Gt => Some(BinaryOperator::Gt),
TokenType::Le => Some(BinaryOperator::Le),
TokenType::Ge => Some(BinaryOperator::Ge),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_additive()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
};
}
Ok(expr)
}
fn parse_additive(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_multiplicative()?;
while let Some(op) = match &self.current().token_type {
TokenType::Plus => Some(BinaryOperator::Add),
TokenType::Minus => Some(BinaryOperator::Sub),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_multiplicative()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
};
}
Ok(expr)
}
fn parse_multiplicative(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_unary()?;
while let Some(op) = match &self.current().token_type {
TokenType::Star => Some(BinaryOperator::Mul),
TokenType::Slash => Some(BinaryOperator::Div),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_unary()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
};
}
Ok(expr)
}
fn parse_unary(&mut self) -> Result<Expression, String> {
let op = match &self.current().token_type {
TokenType::Plus => Some(UnaryOperator::Plus),
TokenType::Minus => Some(UnaryOperator::Minus),
_ => None,
};
if let Some(op) = op {
self.advance();
let operand = Box::new(self.parse_unary()?);
return Ok(Expression::Unary { op, operand });
}
self.parse_primary()
}
fn parse_primary(&mut self) -> Result<Expression, String> {
match &self.current().token_type.clone() {
TokenType::Number(n) => {
let value = *n;
self.advance();
Ok(Expression::Number { value })
}
TokenType::Identifier(name) => {
let name = name.clone();
self.advance();
if matches!(self.current().token_type, TokenType::LParen) {
// Function call
self.advance();
let mut args = Vec::new();
if !matches!(self.current().token_type, TokenType::RParen) {
args.push(self.parse_expression()?);
while matches!(self.current().token_type, TokenType::Comma) {
self.advance();
args.push(self.parse_expression()?);
}
}
self.expect(TokenType::RParen)?;
Ok(Expression::Call { name, args })
} else {
Ok(Expression::Variable {
name,
expr_type: None,
})
}
}
TokenType::LParen => {
self.advance();
let expr = self.parse_expression()?;
self.expect(TokenType::RParen)?;
Ok(expr)
}
_ => Err(self.error(&format!(
"Unexpected token: {:?}",
self.current().token_type
))),
}
}
}
+344
View File
@@ -0,0 +1,344 @@
use std::collections::HashMap;
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
available_registers: Vec<String>,
/// Maps variable names to their current location (register or stack offset)
variable_locations: HashMap<String, Location>,
/// Maps registers to the variables they currently hold
register_contents: HashMap<String, String>,
/// Current stack offset for local variables (relative to bpr)
/// Starts at -4 (going downward from base pointer)
stack_offset: i32,
/// Track which registers are currently in use
in_use: HashMap<String, bool>,
}
#[derive(Debug, Clone)]
pub enum Location {
Register(String),
Stack(i32), // offset from bpr
}
impl RegisterAllocator {
pub fn new() -> Self {
// Initialize with available GP registers (rg0-rgf = 16 registers)
let registers = vec![
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga",
"rgb", "rgc", "rgd", "rge", "rgf",
]
.into_iter()
.map(String::from)
.collect();
RegisterAllocator {
available_registers: registers,
variable_locations: HashMap::new(),
register_contents: HashMap::new(),
stack_offset: -4, // Start at -4 (first local below saved bpr)
in_use: HashMap::new(),
}
}
/// Allocate a temporary register for expression evaluation
/// Returns the register name and optionally assembly code to save it
pub fn alloc_temp(&mut self) -> Result<(String, Vec<String>), String> {
let mut code = Vec::new();
// Try to find an unused register
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
self.in_use.insert(reg.clone(), true);
return Ok((reg.clone(), code));
}
}
// All registers in use - need to spill one
// Choose the first register with a variable we can spill
// Find a register to spill
let reg_to_spill = self
.available_registers
.iter()
.find(|reg| self.register_contents.contains_key(*reg))
.cloned();
if let Some(reg) = reg_to_spill {
// Spill this variable to stack
let spill_code = self.spill_register(&reg)?;
code.extend(spill_code);
self.in_use.insert(reg.clone(), true);
return Ok((reg, code));
}
Err("No registers available and nothing to spill".to_string())
}
/// Free a temporary register after use
/// NOTE: This will NOT free registers that contain variables!
/// Variables persist throughout their scope and must not be freed
pub fn free_temp(&mut self, reg: &str) {
// Check if this register contains a variable
if self.register_contents.contains_key(reg) {
// This register holds a variable - don't free it!
// Variables are only freed when they go out of scope via free_var()
return;
}
// This is a true temporary - safe to free
self.in_use.insert(reg.to_string(), false);
}
/// Allocate a register for a named variable
/// Returns the register and any necessary assembly code
pub fn alloc_var(&mut self, var_name: &str) -> Result<(String, Vec<String>), String> {
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name).cloned() {
match location {
Location::Register(reg) => {
return Ok((reg.clone(), Vec::new()));
}
Location::Stack(offset) => {
// Variable is on stack, load it into a register
let (reg, mut code) = self.alloc_temp()?;
code.push(format!("\tldw bpr, {}, {}", reg, offset));
// Update location to register
self.variable_locations
.insert(var_name.to_string(), Location::Register(reg.clone()));
self.register_contents
.insert(reg.clone(), var_name.to_string());
return Ok((reg, code));
}
}
}
// Variable doesn't have a location yet, allocate a new register
let (reg, code) = self.alloc_temp()?;
self.variable_locations
.insert(var_name.to_string(), Location::Register(reg.clone()));
self.register_contents
.insert(reg.clone(), var_name.to_string());
Ok((reg, code))
}
/// Get the current location of a variable
pub fn get_var_location(&self, var_name: &str) -> Option<&Location> {
self.variable_locations.get(var_name)
}
/// Load a variable into a register (allocating if necessary)
/// Returns the register and assembly code to load it
pub fn load_var(&mut self, var_name: &str) -> Result<(String, Vec<String>), String> {
self.alloc_var(var_name)
}
/// Store a value from a register into a variable
/// Updates tracking and returns any necessary assembly code
pub fn store_var(&mut self, var_name: &str, source_reg: &str) -> Vec<String> {
let mut code = Vec::new();
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name) {
match location {
Location::Register(dest_reg) => {
if dest_reg != source_reg {
code.push(format!("\tmov {}, {}", source_reg, dest_reg));
}
}
Location::Stack(offset) => {
code.push(format!("\tstw {}, bpr, {}", source_reg, offset));
}
}
} else {
// Variable doesn't exist yet - try to allocate a register
if let Some(free_reg) = self.find_free_register() {
if &free_reg != source_reg {
code.push(format!("\tmov {}, {}", source_reg, free_reg));
}
self.variable_locations
.insert(var_name.to_string(), Location::Register(free_reg.clone()));
self.register_contents
.insert(free_reg.clone(), var_name.to_string());
self.in_use.insert(free_reg, true);
} else {
// No free registers - allocate on stack
code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset));
self.variable_locations
.insert(var_name.to_string(), Location::Stack(self.stack_offset));
self.stack_offset -= 4; // Move to next stack slot
}
}
code
}
/// Spill a register to the stack
/// Returns assembly code to perform the spill
fn spill_register(&mut self, reg: &str) -> Result<Vec<String>, String> {
let mut code = Vec::new();
if let Some(var_name) = self.register_contents.get(reg).cloned() {
// Store register content to stack
code.push(format!("\tstw {}, bpr, {}", reg, self.stack_offset));
// Update variable location
self.variable_locations
.insert(var_name.clone(), Location::Stack(self.stack_offset));
// Remove from register tracking
self.register_contents.remove(reg);
// Move to next stack slot
self.stack_offset -= 4;
}
Ok(code)
}
/// Find a free register (not currently in use)
fn find_free_register(&self) -> Option<String> {
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
return Some(reg.clone());
}
}
None
}
/// Spill all registers to stack (useful before function calls)
pub fn spill_all(&mut self) -> Vec<String> {
let mut code = Vec::new();
let regs_to_spill: Vec<String> = self.register_contents.keys().cloned().collect();
for reg in regs_to_spill {
if let Ok(spill_code) = self.spill_register(&reg) {
code.extend(spill_code);
}
}
code
}
/// Get the total stack space needed for local variables
pub fn get_stack_size(&self) -> i32 {
-self.stack_offset // Convert negative offset to positive size
}
/// Reset allocator for a new function
pub fn reset(&mut self) {
self.variable_locations.clear();
self.register_contents.clear();
self.stack_offset = -4;
self.in_use.clear();
}
/// Mark a variable as dead (no longer needed)
/// Frees its register if it's in one
pub fn free_var(&mut self, var_name: &str) {
if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) {
let reg = reg.clone();
self.register_contents.remove(&reg);
self.in_use.insert(reg, false);
}
self.variable_locations.remove(var_name);
}
/// Get list of registers that contain variables and are in use
/// These need to be saved before function calls
pub fn get_caller_saved_registers(&self) -> Vec<String> {
self.register_contents
.iter()
.filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false))
.map(|(reg, _)| reg.clone())
.collect()
}
/// Save caller-saved registers before a function call
/// Returns assembly code to save them
pub fn save_caller_saved(&mut self) -> Vec<String> {
let mut code = Vec::new();
// For simplicity, save all currently used registers
// In a more sophisticated compiler, you'd only save registers that are live
for (reg, var_name) in self.register_contents.clone() {
if *self.in_use.get(&reg).unwrap_or(&false) {
code.push(format!("\tpush {}", reg));
}
}
code
}
/// Restore caller-saved registers after a function call
/// Returns assembly code to restore them
pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec<String> {
let mut code = Vec::new();
// Restore in reverse order (LIFO)
for reg in saved_regs.iter().rev() {
code.push(format!("\tpop {}", reg));
}
code
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg1, code1) = allocator.alloc_temp().unwrap();
assert_eq!(code1.len(), 0); // No spill needed
assert_eq!(reg1, "rg0");
let (reg2, code2) = allocator.alloc_temp().unwrap();
assert_eq!(code2.len(), 0);
assert_eq!(reg2, "rg1");
allocator.free_temp(&reg1);
let (reg3, code3) = allocator.alloc_temp().unwrap();
assert_eq!(code3.len(), 0);
assert_eq!(reg3, "rg0"); // Reuses freed register
}
#[test]
fn test_variable_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg, "rg0");
// Requesting same variable again should return same register
let (reg2, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg2, "rg0");
}
#[test]
fn test_stack_allocation() {
let mut allocator = RegisterAllocator::new();
// Allocate all 16 registers
for i in 0..16 {
allocator.alloc_var(&format!("var{}", i)).unwrap();
}
// Next allocation should spill to stack
let (reg, code) = allocator.alloc_var("var16").unwrap();
assert!(code.len() > 0); // Should have spill code
}
}
+14 -6
View File
@@ -3,13 +3,19 @@ use crate::{instructions::encode::Encode, prelude::*};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Interrupt {
Software(u8),
Breakpoint,
HardFault,
}
pub type Address = u32;
impl Interrupt {
const fn as_u8(self) -> u8 {
// someone tell clippy to stfu.
#[allow(clippy::must_use_candidate)]
pub const fn as_u8(self) -> u8 {
match self {
Self::Breakpoint => 0,
Self::HardFault => 1,
Self::Software(code) => code,
}
}
@@ -19,10 +25,11 @@ impl Interrupt {
impl From<u8> for Interrupt {
#[allow(unreachable_code)]
fn from(code: u8) -> Self {
return Self::Software(code);
todo!("Implement this once a hardware interrupt convention is established.");
// Self::Software(_code)
match code {
0 => Self::Breakpoint,
1 => Self::HardFault,
_ => Self::Software(code),
}
}
}
@@ -73,7 +80,8 @@ pub enum Register {
}
impl Register {
#[must_use]
// this is here so clippy shuts up about the must_use tag.
#[allow(clippy::must_use_candidate)]
pub fn general() -> Vec<Self> {
vec![
Self::Rg0,
+5 -3
View File
@@ -54,12 +54,14 @@ impl Encode for Instruction {
],
no_args: [Nop, IntReturn, Halt],
special: [
Self::Interrupt(_) => todo!(),
Self::Data(data) => data,
Self::Interrupt(interrupt) => {
let opcode = u32::from(self.opcode());
(opcode << 26) | u32::from(interrupt.as_u8())
},
Self::Segment(segment) => {
let opcode = u32::from(self.opcode());
let segment = segment as u8;
(opcode << 26) | u32::from(segment)
(opcode << 26) | u32::from(segment as u8)
}
]
)
+1
View File
@@ -13,6 +13,7 @@
)]
pub mod instructions;
pub mod logging;
pub mod prelude {
//! A collection of types you should definitely import when working with this crate.
+4
View File
@@ -0,0 +1,4 @@
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
pub fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "compiler"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
chrono = "0.4.43"
common = { path = "../common" }
+129
View File
@@ -0,0 +1,129 @@
# This is a configuration file for the bacon tool
#
# Complete help on configuration: https://dystroy.org/bacon/config/
#
# You may check the current default at
# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml
default_job = "check"
[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false
# Run clippy on the default target
[jobs.clippy]
command = [
"cargo", "clippy",
"--color", "always",
]
need_stdout = false
# Run clippy on all targets
# To disable some lints, you may change the job this way:
# [jobs.clippy-all]
# command = [
# "cargo", "clippy",
# "--all-targets",
# "--color", "always",
# "--",
# "-A", "clippy::bool_to_int_with_if",
# "-A", "clippy::collapsible_if",
# "-A", "clippy::derive_partial_eq_without_eq",
# ]
# need_stdout = false
[jobs.clippy-all]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
need_stdout = false
# This job lets you run
# - all tests: bacon test
# - a specific test: bacon test -- config::test_default_files
# - the tests of a package: bacon test -- -- -p config
[jobs.test]
command = [
"cargo", "test", "--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.nextest]
command = [
"cargo", "nextest", "run",
"--color", "always",
"--hide-progress-bar", "--failure-output", "final"
]
need_stdout = true
analyzer = "nextest"
[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# if it makes sense for this crate.
# Don't forget the `--color always` part or the errors won't be
# properly parsed.
[jobs.run]
command = [
"cargo", "run",
"--color", "always",
"--",
"../resources/dsa/example.dsc",
"../resources/dsa/example.dsa"
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true
# Run your long-running application (eg server) and have the result displayed in bacon.
# For programs that never stop (eg a server), `background` is set to false
# to have the cargo run output immediately displayed instead of waiting for
# program's end.
# 'on_change_strategy' is set to `kill_then_restart` to have your program restart
# on every change (an alternative would be to use the 'F5' key manually in bacon).
# If you often use this job, it makes sense to override the 'r' key by adding
# a binding `r = job:run-long` at the end of this file .
[jobs.run-long]
command = [
"cargo", "run",
"--color", "always",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = false
on_change_strategy = "kill_then_restart"
# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
# bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--color", "always", "--example"]
need_stdout = true
allow_warnings = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
+756
View File
@@ -0,0 +1,756 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::LazyLock;
use std::sync::atomic::AtomicU32;
use std::time::SystemTime;
use chrono::{DateTime, Local};
use crate::registers::{Location, RegisterAllocator};
use crate::{block, cmd, comment, dsa};
use crate::parser::{
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,
}
static GLOBAL_METHODS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from([
// ("print", "print::print"),
// ("println", "print::println"),
// ("printnum", "print::print_num"),
// ("print_space", "print::print_whitespace"),
// ("print_newline", "print::print_newline"),
// ("print_char", "print::print_byte"),
// ("print_word", "print::print_word"),
// ("print_hex", "print::print_hex_word"),
])
});
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,
return_type,
params,
body,
} => {
let func = self.generate_function(&name, &params, &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(&param.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(&reg);
}
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 well 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 reuses the same buffer.
write!(s, "{}\n", $arg).expect("write to String failed");
)*
s
}};
}
// ──────────────────────── dsa! ────────────────────────
// A tiny helper that just turns its tokenstream into a string.
// The trailing comma is kept its part of the syntax you want.
#[macro_export]
macro_rules! cmd {
($($tokens:tt)*) => {{
// Well 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 thats 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) }};
}
+627
View File
@@ -0,0 +1,627 @@
use std::iter::Peekable;
use std::str::Chars;
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
// Keywords
Fn,
Let,
If,
Else,
Loop,
While,
Break,
Return,
Continue,
Include,
Static,
Const,
// Identifiers and literals
Identifier(Name),
String(String),
Integer(u64),
Char(char),
// Symbols
LeftParen, // (
RightParen, // )
LeftBrace, // {
RightBrace, // }
Semicolon, // ;
Colon, // :
Comma, // ,
// Operators
Plus, // +
Minus, // -
Star, // *
Amphersand, // &
Slash, // /
Assign, // =
EqualEqual, // ==
Bang, // !
BangEqual, // !=
Less, // <
LessEqual, // <=
Greater, // >
GreaterEqual, // >=
RightArrow, // ->
// Special
Eof,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Name {
pub name: String,
pub namespace: Option<String>,
}
use std::fmt;
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref ns) = self.namespace {
write!(f, "{}::{}", ns, self.name)
} else {
write!(f, "{}", self.name)
}
}
}
impl Token {
pub fn tt(&self) -> &str {
match self {
Token::Const => "Const",
Token::Static => "Static",
Token::Include => "Include",
Token::Fn => "Fn",
Token::If => "If",
Token::Let => "Let",
Token::Else => "Else",
Token::Loop => "Loop",
Token::While => "While",
Token::Break => "Break",
Token::Return => "Return",
Token::Continue => "Continue",
Token::Identifier(_) => "Identifier",
Token::String(_) => "String",
Token::Integer(_) => "UnsignedInt",
Token::Char(_) => "Char",
Token::LeftParen => "LeftParen",
Token::RightParen => "RightParen",
Token::LeftBrace => "LeftBrace",
Token::RightBrace => "RightBrace",
Token::Semicolon => "Semicolon",
Token::Colon => "Colon",
Token::Comma => "Comma",
Token::RightArrow => "RightArrow",
Token::Plus => "Plus",
Token::Minus => "Minus",
Token::Star => "Star",
Token::Amphersand => "Amphersand",
Token::Slash => "Slash",
Token::Assign => "Assign",
Token::EqualEqual => "EqualEqual",
Token::Bang => "Bang",
Token::BangEqual => "BangEqual",
Token::Less => "Less",
Token::LessEqual => "LessEqual",
Token::Greater => "Greater",
Token::GreaterEqual => "GreaterEqual",
Token::Eof => "Eof",
}
}
}
#[derive(Debug)]
pub struct Lexer<'a> {
chars: Peekable<Chars<'a>>,
current: Option<char>,
line: usize,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
let mut chars = input.chars().peekable();
let current = chars.next();
Lexer {
chars,
current,
line: 1,
}
}
fn advance(&mut self) -> Option<char> {
self.current = self.chars.next();
self.current
}
fn peek(&mut self) -> Option<&char> {
self.chars.peek()
}
fn skip_whitespace(&mut self) {
while let Some(c) = self.current {
if !c.is_whitespace() {
break;
}
if c == '\n' {
self.line += 1;
}
self.advance();
}
}
fn skip_line_comment(&mut self) {
// Skip the two slashes
self.advance(); // first /
self.advance(); // second /
// Skip until newline or EOF
while let Some(c) = self.current {
if c == '\n' {
self.line += 1;
self.advance();
break;
}
self.advance();
}
}
fn skip_block_comment(&mut self) -> Result<(), String> {
// Skip the /*
self.advance(); // /
self.advance(); // *
let start_line = self.line;
// Look for */
while let Some(c) = self.current {
if c == '\n' {
self.line += 1;
}
if c == '*' {
if let Some(&next) = self.peek() {
if next == '/' {
self.advance(); // *
self.advance(); // /
return Ok(());
}
}
}
self.advance();
}
Err(format!(
"Unterminated block comment starting at line {}",
start_line
))
}
fn skip_whitespace_and_comments(&mut self) {
loop {
self.skip_whitespace();
// Check for comments
if let Some('/') = self.current {
if let Some(&next) = self.peek() {
match next {
'/' => {
self.skip_line_comment();
continue;
}
'*' => {
if let Err(e) = self.skip_block_comment() {
eprintln!("Lexer error: {}", e);
}
continue;
}
_ => break,
}
}
}
break;
}
}
fn read_identifier(&mut self) -> String {
let mut ident = String::new();
// Include the current character if it's valid
if let Some(c) = self.current {
if c.is_alphabetic() || c == '_' {
ident.push(c);
}
}
// Read remaining characters
while let Some(&c) = self.peek() {
if c.is_alphanumeric() || c == '_' {
self.advance();
ident.push(c);
} else {
break;
}
}
ident
}
fn keyword_or_identifier(&mut self) -> Token {
let first_ident = self.read_identifier();
// Check if it's a keyword first (keywords can't have namespaces)
let keyword = match first_ident.as_str() {
"fn" => Some(Token::Fn),
"if" => Some(Token::If),
"else" => Some(Token::Else),
"while" => Some(Token::While),
"loop" => Some(Token::Loop),
"break" => Some(Token::Break),
"return" => Some(Token::Return),
"continue" => Some(Token::Continue),
"include" => Some(Token::Include),
"let" => Some(Token::Let),
"const" => Some(Token::Const),
"static" => Some(Token::Static),
_ => None,
};
if let Some(kw) = keyword {
return kw;
}
// Not a keyword - check for namespace separator (::)
// We need to peek TWO characters ahead without consuming anything
if let Some(&':') = self.peek() {
// We see one colon, but we need to check if there's another one after it
// We can't peek two ahead directly, so we need a different approach
// Save the current position by using a temporary peekable iterator
// Actually, we can't do that easily. Instead, let's just check:
// If we see ':', temporarily advance and check the next char
// Create a temporary check
let mut temp_chars = self.chars.clone();
let first_peek = temp_chars.next(); // This is the ':' we already saw
let second_peek = temp_chars.peek();
if let Some(&':') = second_peek {
// It's :: - consume both colons
self.advance(); // consume first :
self.advance(); // consume second :
// Read the second identifier (the actual name)
let second_ident = self.read_identifier();
// Return namespaced identifier
return Token::Identifier(Name {
namespace: Some(first_ident),
name: second_ident,
});
}
// else: It's a single colon (type annotation) - DON'T consume it
// Just fall through and return the identifier
}
// No namespace separator - just a regular identifier
Token::Identifier(Name {
namespace: None,
name: first_ident,
})
}
fn read_number(&mut self) -> Result<u64, String> {
let current = self.current.unwrap();
// Check for hex (0x) or binary (0b) prefix
if current == '0' {
if let Some(&next_char) = self.peek() {
match next_char {
'x' | 'X' => {
self.advance(); // consume '0'
self.advance(); // consume 'x'
return self.read_hex_number();
}
'b' | 'B' => {
self.advance(); // consume '0'
self.advance(); // consume 'b'
return self.read_binary_number();
}
_ => {}
}
}
}
// Read decimal number
self.read_decimal_number()
}
fn read_decimal_number(&mut self) -> Result<u64, String> {
let mut num_str = String::new();
if let Some(c) = self.current {
num_str.push(c);
}
while let Some(&c) = self.peek() {
if c.is_ascii_digit() {
self.advance();
num_str.push(c);
} else {
break;
}
}
num_str
.parse::<u64>()
.map_err(|_| format!("Invalid decimal number: {}", num_str))
}
fn read_hex_number(&mut self) -> Result<u64, String> {
let mut num_str = String::new();
// Read current character if it's a hex digit
if let Some(c) = self.current {
if c.is_ascii_hexdigit() {
num_str.push(c);
}
}
while let Some(&c) = self.peek() {
if c.is_ascii_hexdigit() {
self.advance();
num_str.push(c);
} else {
break;
}
}
if num_str.is_empty() {
return Err("Invalid hexadecimal number: no digits after 0x".to_string());
}
u64::from_str_radix(&num_str, 16)
.map_err(|_| format!("Invalid hexadecimal number: {}", num_str))
}
fn read_binary_number(&mut self) -> Result<u64, String> {
let mut num_str = String::new();
// Read current character if it's a binary digit
if let Some(c) = self.current {
if c == '0' || c == '1' {
num_str.push(c);
}
}
while let Some(&c) = self.peek() {
if c == '0' || c == '1' {
self.advance();
num_str.push(c);
} else {
break;
}
}
if num_str.is_empty() {
return Err("Invalid binary number: no digits after 0b".to_string());
}
u64::from_str_radix(&num_str, 2)
.map_err(|_| format!("Invalid binary number: {}", num_str))
}
fn read_string(&mut self) -> Result<String, String> {
self.advance(); // Skip the opening quote
let mut s = String::new();
while let Some(c) = self.current {
if c == '"' {
return Ok(s);
}
// Handle escape sequences
if c == '\\' {
self.advance();
if let Some(escaped) = self.current {
let escaped_char = match escaped {
'n' => '\n',
't' => '\t',
'r' => '\r',
'\\' => '\\',
'"' => '"',
_ => escaped, // For now, just use the character as-is
};
s.push(escaped_char);
} else {
return Err("Unexpected end of string after escape".to_string());
}
} else {
s.push(c);
}
self.advance();
}
Err("Unterminated string literal".to_string())
}
fn match_next(&mut self, expected: char) -> bool {
match self.peek() {
Some(&c) if c == expected => {
self.advance();
true
}
_ => false,
}
}
fn scan_single_char_token(&mut self, c: char) -> Option<Token> {
match c {
'(' => Some(Token::LeftParen),
')' => Some(Token::RightParen),
'{' => Some(Token::LeftBrace),
'}' => Some(Token::RightBrace),
';' => Some(Token::Semicolon),
',' => Some(Token::Comma),
'&' => Some(Token::Amphersand),
'+' => Some(Token::Plus),
'*' => Some(Token::Star),
_ => None,
}
}
fn scan_operator(&mut self, c: char) -> Option<Token> {
match c {
'-' => Some(if self.match_next('>') {
Token::RightArrow
} else {
Token::Minus
}),
'!' => Some(if self.match_next('=') {
Token::BangEqual
} else {
Token::Bang
}),
'=' => Some(if self.match_next('=') {
Token::EqualEqual
} else {
Token::Assign
}),
'<' => Some(if self.match_next('=') {
Token::LessEqual
} else {
Token::Less
}),
'>' => Some(if self.match_next('=') {
Token::GreaterEqual
} else {
Token::Greater
}),
':' => {
// Single colon (for type annotations)
// Note: :: is handled in keyword_or_identifier for namespaces
Some(Token::Colon)
}
'/' => {
// Check if it's a comment or division
if let Some(&next) = self.peek() {
if next == '/' || next == '*' {
// It's a comment, don't consume it here
// Let skip_whitespace_and_comments handle it
None
} else {
Some(Token::Slash)
}
} else {
Some(Token::Slash)
}
}
_ => None,
}
}
pub fn next_token(&mut self) -> Token {
self.skip_whitespace_and_comments();
let Some(c) = self.current else {
return Token::Eof;
};
// Try single-character tokens first
if let Some(token) = self.scan_single_char_token(c) {
self.advance();
return token;
}
// Try operators (may be multi-character)
if let Some(token) = self.scan_operator(c) {
self.advance();
return token;
}
// String literals
if c == '"' {
let token = match self.read_string() {
Ok(s) => Token::String(s),
Err(e) => {
eprintln!("Lexer error on line {}: {}", self.line, e);
// Skip to next quote or end
while let Some(ch) = self.current {
if ch == '"' || ch == '\n' {
break;
}
self.advance();
}
Token::String(String::new())
}
};
self.advance();
return token;
}
// Identifiers and keywords (including namespaced identifiers)
if c.is_alphabetic() || c == '_' {
let token = self.keyword_or_identifier();
self.advance();
return token;
}
// Numbers (decimal, hex, binary)
if c.is_ascii_digit() {
let token = match self.read_number() {
Ok(num) => Token::Integer(num),
Err(e) => {
eprintln!("Lexer error on line {}: {}", self.line, e);
// Skip invalid number
while let Some(&ch) = self.peek() {
if !ch.is_alphanumeric() {
break;
}
self.advance();
}
Token::Integer(0)
}
};
self.advance();
return token;
}
// Unknown character - skip it
eprintln!(
"Lexer warning on line {}: Skipping unknown character '{}'",
self.line, c
);
self.advance();
self.next_token()
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
match self.next_token() {
Token::Eof => None,
token => Some(token),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
// Placeholder test
assert!(true);
}
}
+74
View File
@@ -0,0 +1,74 @@
#![feature(try_trait_v2)]
use std::path::Path;
use common::logging::log;
use crate::{
codegen::CodeGenerator,
parser::{ParseResult, Parser},
semantic_analyser::Analyser,
};
mod codegen;
mod lexer;
mod parser;
mod registers;
mod semantic_analyser;
pub fn compile_file(
input_path: &Path,
output_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let input = std::fs::read_to_string(input_path).expect("Failed to read input file");
log("Tokenising Input...");
let lexer = lexer::Lexer::new(&input);
let tokens = lexer.collect::<Vec<_>>();
// println!("{tokens:?}");
log(&format!("Parsing {} Tokens...", tokens.len()));
let mut parser = Parser::new(tokens);
let ast = match parser.parse() {
ParseResult::Accept(ast) => ast,
ParseResult::Reject(e) => {
eprintln!("Error: {e:?}");
return Err("Parsing error".into());
}
ParseResult::Deny => {
panic!("Parser denied parsing")
}
};
// println!("{ast:#?}");
log("Analyzing AST...");
log("Checking Type Information...");
let analyser = Analyser::new();
analyser.analyse(ast.clone()).unwrap();
log("Generating Code...");
// Code Gen
let mut generator = CodeGenerator::new(ast);
let result = match generator.generate() {
Ok(code) => code,
Err(e) => {
eprintln!("Parsing error: {:?}", e);
return Err("Code generation error".into());
}
};
// println!("{result}");
std::fs::write(output_path, &result).expect("Failed to write output");
log(&format!(
"Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n",
input_path.display(),
output_path.display(),
));
Ok(())
}
+21
View File
@@ -0,0 +1,21 @@
use std::path::Path;
use compiler;
fn main() {
// read from input file: syntax "c_compiler <src.c> [output.dsa]"
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: c_compiler <src.c> [output.dsa]");
return;
}
let input_file = &args[1];
let output_file = if args.len() > 2 {
&args[2]
} else {
"output.dsa"
};
compiler::compile_file(Path::new(input_file), Path::new(output_file)).unwrap();
}
+790
View File
@@ -0,0 +1,790 @@
use crate::lexer::{Name, Token};
use crate::{expect_tt, expect_value};
use core::fmt;
use std::ops::{ControlFlow, FromResidual, Try};
#[derive(Debug, Clone)]
pub enum ParseResult<T, E> {
Accept(T),
Deny,
Reject(E),
}
#[derive(Debug, Clone)]
pub enum CompilerError {
UnexpectedToken(Token),
UnexpectedEndOfInput,
UnexpectedCharacter(char),
Undefined(Name),
InvalidSyntax(String),
Generic(String),
}
pub struct Parser {
tokens: Vec<Token>,
idx: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, idx: 0 }
}
pub fn parse(&mut self) -> ParseResult<Program, CompilerError> {
let mut declarations = Vec::new();
while let ParseResult::Accept(_) = self.peek_next() {
declarations.push(self.parse_declaration()?);
}
ParseResult::Accept(Program { declarations })
}
fn parse_declaration(&mut self) -> ParseResult<Declaration, CompilerError> {
if expect_tt!(self.peek_next()?, Fn).accepted() {
return self.parse_func();
}
if expect_tt!(self.peek_next()?, Include).accepted() {
// expect include keyword
let _ = self.next();
// expect namespace identifier
let name = expect_value!(self.next()?, Identifier)?;
// expect colon
let _ = expect_tt!(self.next()?, Colon)?;
// expect string literal (module path)
let path = expect_value!(self.next()?, String)?;
// expect semicolon
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Declaration::Dependency(Dependency {
name: name.name,
path,
}));
}
if expect_tt!(self.peek_next()?, Const, Static).accepted() {
let is_const = match self.next()? {
Token::Const => true,
Token::Static => false,
_ => {
return ParseResult::Reject(CompilerError::Generic(String::from(
"This can't happen!",
)));
}
};
let var = self.parse_var_decl()?;
let _ = expect_tt!(self.next()?, Assign)?;
let value = self.next()?;
let init = match value {
Token::String(x) => Some(ConstExpr::String(x)),
Token::Integer(x) => Some(ConstExpr::Number(x as i32)),
_ => return ParseResult::Reject(CompilerError::UnexpectedToken(value)),
};
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Declaration::Variable {
var,
init,
is_const,
});
}
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
}
fn parse_func(&mut self) -> ParseResult<Declaration, CompilerError> {
// expect function keyword
let _ = expect_tt!(self.next()?, Fn);
// expect function name
let name = expect_value!(self.next()?, Identifier)?;
// expect left paren
let _ = expect_tt!(self.next()?, LeftParen)?;
let mut params = Vec::new();
while expect_tt!(self.peek_next()?, Identifier).accepted() {
let arg = self.parse_var_decl()?;
params.push(arg);
if expect_tt!(self.peek_next()?, Comma).accepted() {
self.next()?;
} else {
break;
}
}
// expect right paren
let _ = expect_tt!(self.next()?, RightParen)?;
// see if we can parse the return type!
let mut return_type = TypeId::Void;
if expect_tt!(self.peek_next()?, RightArrow).accepted() {
let _ = self.next();
return_type = self.parse_type()?;
}
// expect vald block
if expect_tt!(self.peek_next()?, LeftBrace).accepted() {
ParseResult::Accept(Declaration::Function {
name: name.name,
params,
return_type,
body: self.parse_block()?,
})
} else {
ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?))
}
}
fn parse_block(&mut self) -> ParseResult<Block, CompilerError> {
// expect left brace
let _ = expect_tt!(self.next()?, LeftBrace)?;
let mut block = Vec::new();
while !expect_tt!(self.peek_next()?, RightBrace).accepted() {
block.push(self.parse_statement()?);
}
// expect right brace
let _ = expect_tt!(self.next()?, RightBrace)?;
ParseResult::Accept(block)
}
fn parse_statement(&mut self) -> ParseResult<Statement, CompilerError> {
// handle if statements
if expect_tt!(self.peek_next()?, If).accepted() {
self.next()?;
let condition = self.parse_expression()?;
let then_stmt = self.parse_block()?;
if !expect_tt!(self.peek_next()?, Else).accepted() {
return ParseResult::Accept(Statement::If {
condition,
then_stmt,
else_stmt: vec![],
});
}
let _ = expect_tt!(self.next()?, Else)?;
let else_stmt = self.parse_block()?;
return ParseResult::Accept(Statement::If {
condition,
then_stmt,
else_stmt,
});
}
// handle while loops
if expect_tt!(self.peek_next()?, While).accepted() {
self.next()?;
// expect valid expression
let expr = self.parse_expression()?;
// expect valid block after
let block = self.parse_block()?;
// return result
return ParseResult::Accept(Statement::While {
condition: expr,
body: block,
});
}
// handle indefinite loops
if expect_tt!(self.peek_next()?, Loop).accepted() {
self.next()?;
// parse the inner block
return ParseResult::Accept(Statement::Loop(self.parse_block()?));
}
if expect_tt!(self.peek_next()?, Return).accepted() {
self.next()?;
// handle case where nothing is returned
if expect_tt!(self.peek_next()?, Semicolon).accepted() {
return ParseResult::Accept(Statement::Return(None));
}
let expr = self.parse_expression()?;
expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Statement::Return(Some(expr)));
}
if expect_tt!(self.peek_next()?, Break).accepted() {
self.next()?;
// expect semicolon
expect_tt!(self.next()?, Semicolon)?;
// return result
return ParseResult::Accept(Statement::Break);
}
if expect_tt!(self.peek_next()?, Continue).accepted() {
self.next()?;
// expect semicolon
expect_tt!(self.next()?, Semicolon)?;
// return result
return ParseResult::Accept(Statement::Continue);
}
// handle writes to pointers!
if expect_tt!(self.peek_next()?, Star).accepted() {
self.next()?;
let left = if expect_tt!(self.peek_next()?, Identifier).accepted() {
let identifier = expect_value!(self.next()?, Identifier)?;
Expression::Variable {
name: identifier,
expr_type: None,
}
} else if expect_tt!(self.peek_next()?, LeftParen).accepted() {
self.next()?;
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, RightParen).accepted();
expr
} else {
return ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?,
));
};
let _ = expect_tt!(self.next()?, Assign)?;
let right = self.parse_expression()?;
// expect semicolon
expect_tt!(self.next()?, Semicolon)?;
// return result
return ParseResult::Accept(Statement::PtrWrite {
ptr: left,
value: right,
});
}
// handle let statements (declarations)
if expect_tt!(self.peek_next()?, Let).accepted() {
self.next();
// expect variable name and type.
let name = self.parse_var_decl()?;
// handle uninitialised variable case
if expect_tt!(self.peek_next()?, Semicolon).accepted() {
self.next();
return ParseResult::Accept(Statement::Declaration {
var: name,
value: None,
});
}
// handle initialised case
// expect equals
let _ = expect_tt!(self.next()?, Assign)?;
// expect a valid expression
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon);
// return statement
return ParseResult::Accept(Statement::Declaration {
var: name,
value: Some(expr),
});
}
// handle assignment without "let"
let name = expect_value!(self.peek_next()?, Identifier);
if name.accepted() {
let varname = name?;
if expect_tt!(self.peek(1)?, LeftParen).accepted() {
let expr = self.parse_expression()?; // a function call expr
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Statement::Expression { expr });
}
self.next()?;
let _ = expect_tt!(self.next()?, Assign)?;
let value = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon);
return ParseResult::Accept(Statement::Assign {
varname: varname.name,
value,
});
}
ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?))
}
fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> {
self.parse_comparison()
}
fn parse_comparison(&mut self) -> ParseResult<Expression, CompilerError> {
let mut expr = self.parse_additive()?;
while let Some(op) = match self.peek_next()? {
Token::EqualEqual => Some(BinaryOperator::Ne),
Token::BangEqual => Some(BinaryOperator::Ne),
Token::Less => Some(BinaryOperator::Lt),
Token::Greater => Some(BinaryOperator::Gt),
Token::LessEqual => Some(BinaryOperator::Le),
Token::GreaterEqual => Some(BinaryOperator::Ge),
_ => None,
} {
self.next()?;
let right = Box::new(self.parse_additive()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
}
}
ParseResult::Accept(expr)
}
fn parse_additive(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_multiplicative()?;
let op = match self.peek_next()? {
Token::Plus => BinaryOperator::Add,
Token::Minus => BinaryOperator::Sub,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_additive()?),
})
}
fn parse_multiplicative(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_unary()?;
let op = match self.peek_next()? {
Token::Star => BinaryOperator::Mul,
Token::Slash => BinaryOperator::Div,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_multiplicative()?),
})
}
fn parse_unary(&mut self) -> ParseResult<Expression, CompilerError> {
let op = match self.peek_next()? {
Token::Plus => UnaryOperator::Plus,
Token::Minus => UnaryOperator::Minus,
Token::Star => UnaryOperator::Dereference,
Token::Amphersand => UnaryOperator::Reference,
_ => return ParseResult::Accept(self.parse_primary()?),
};
self.next()?;
let operand = Box::new(self.parse_unary()?);
ParseResult::Accept(Expression::Unary { op, operand })
}
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> {
match self.peek_next()? {
Token::Integer(value) => {
self.next()?;
ParseResult::Accept(Expression::Number(value as isize))
}
Token::String(value) => {
self.next()?;
ParseResult::Accept(Expression::StringLiteral(value))
}
Token::Identifier(_) => {
let name = expect_value!(self.next()?, Identifier)?;
if matches!(self.peek_next()?, Token::LeftParen) {
// Function call
self.next()?;
let mut args = Vec::new();
if !matches!(self.peek_next()?, Token::RightParen) {
args.push(self.parse_expression()?);
while matches!(self.peek_next()?, Token::Comma) {
self.next()?;
args.push(self.parse_expression()?);
}
}
let _ = expect_tt!(self.next()?, RightParen)?;
ParseResult::Accept(Expression::Call { name, args })
} else {
ParseResult::Accept(Expression::Variable {
name,
expr_type: None,
})
}
}
Token::LeftParen => {
self.next()?;
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, RightParen)?;
ParseResult::Accept(expr)
}
_ => ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?)),
}
}
fn parse_var_decl(&mut self) -> ParseResult<Variable, CompilerError> {
let name = expect_value!(self.next()?, Identifier)?;
let _ = expect_tt!(self.next()?, Colon)?;
let type_id = self.parse_type()?;
ParseResult::Accept(Variable {
name: name.name,
type_id,
})
}
fn parse_type(&mut self) -> ParseResult<TypeId, CompilerError> {
// get the type name incl namespace
let typename = expect_value!(self.next()?, Identifier)?;
match typename.name.as_str() {
"u32" => ParseResult::Accept(TypeId::U32),
"u16" => ParseResult::Accept(TypeId::U16),
"u8" => ParseResult::Accept(TypeId::U8),
"i32" => ParseResult::Accept(TypeId::I32),
"i16" => ParseResult::Accept(TypeId::I16),
"i8" => ParseResult::Accept(TypeId::I8),
"void" => ParseResult::Accept(TypeId::Void),
"char" => ParseResult::Accept(TypeId::Char),
"str" => ParseResult::Accept(TypeId::Ptr(Box::new(TypeId::Char))),
_ => todo!("Implement parsing for other types!!"),
}
}
fn next(&mut self) -> ParseResult<Token, CompilerError> {
if self.idx >= self.tokens.len() {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} else {
let token = self.tokens[self.idx].clone();
self.idx += 1;
ParseResult::Accept(token)
}
}
fn peek_next(&self) -> ParseResult<Token, CompilerError> {
if self.idx >= self.tokens.len() {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} else {
ParseResult::Accept(self.tokens[self.idx].clone())
}
}
fn peek(&self, offset: usize) -> ParseResult<Token, CompilerError> {
if self.idx + offset >= self.tokens.len() {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} else {
ParseResult::Accept(self.tokens[self.idx + offset].clone())
}
}
}
#[derive(Debug, Clone)]
pub struct Program {
pub declarations: Vec<Declaration>,
}
#[derive(Debug, Clone)]
pub enum Declaration {
Function {
name: String,
return_type: TypeId,
params: Vec<Variable>,
body: Block,
},
Variable {
var: Variable,
init: Option<ConstExpr>,
is_const: bool,
},
Dependency(Dependency),
}
#[derive(Debug, Clone)]
pub struct Dependency {
pub name: String,
pub path: String,
}
#[derive(Debug, Clone)]
pub enum TypeId {
U8,
U16,
U32,
I8,
I16,
I32,
Char,
Void,
Ptr(Box<TypeId>),
Ref(Box<TypeId>),
Array(Box<TypeId>, usize),
Struct { name: Name, fields: Vec<Variable> },
}
pub type Block = Vec<Statement>;
#[derive(Debug, Clone)]
pub struct Variable {
pub name: String,
pub type_id: TypeId,
}
#[derive(Debug, Clone)]
pub enum Statement {
Block(Block),
Declaration {
var: Variable,
value: Option<Expression>,
},
Assign {
varname: String,
value: Expression,
},
PtrWrite {
ptr: Expression,
value: Expression,
},
Expression {
expr: Expression,
},
If {
condition: Expression,
then_stmt: Block,
else_stmt: Block,
},
While {
condition: Expression,
body: Vec<Statement>,
},
Loop(Block),
Break,
Continue,
Return(Option<Expression>),
}
#[derive(Debug, Clone)]
pub enum ConstExpr {
Number(i32),
String(String),
}
impl fmt::Display for ConstExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConstExpr::Number(n) => write!(f, "{}", n),
ConstExpr::String(s) => write!(f, "\"{}\"", s),
}
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Empty,
Binary {
op: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
},
Unary {
op: UnaryOperator,
operand: Box<Expression>,
},
Variable {
name: Name,
expr_type: Option<TypeId>,
},
Call {
name: Name,
args: Vec<Expression>,
},
Number(isize),
StringLiteral(String),
CharLiteral(char),
}
impl Expression {
pub fn is_pure(&self) -> bool {
match self {
Expression::Number(_) => true,
Expression::StringLiteral(_) => true,
Expression::CharLiteral(_) => true,
Expression::Call { name, args } => false, /* TODO: will require checking */
// if the associated function
// body is pure
Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
Expression::Unary { op, operand } => operand.is_pure(),
Expression::Empty => true,
Expression::Variable { name, expr_type } => true,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator {
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BinaryOperator::Add => write!(f, "+"),
BinaryOperator::Sub => write!(f, "-"),
BinaryOperator::Mul => write!(f, "*"),
BinaryOperator::Div => write!(f, "/"),
BinaryOperator::Eq => write!(f, "=="),
BinaryOperator::Ne => write!(f, "!="),
BinaryOperator::Lt => write!(f, "<"),
BinaryOperator::Gt => write!(f, ">"),
BinaryOperator::Le => write!(f, "<="),
BinaryOperator::Ge => write!(f, ">="),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOperator {
Plus,
Minus,
Reference,
Dereference,
}
impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UnaryOperator::Plus => write!(f, "+"),
UnaryOperator::Minus => write!(f, "-"),
UnaryOperator::Dereference => write!(f, "*"),
UnaryOperator::Reference => write!(f, "&"),
}
}
}
impl<T, E> ParseResult<T, E> {
pub fn accepted(&self) -> bool {
matches!(self, ParseResult::Accept(_))
}
}
pub enum ParseResultResidual<T> {
Deny,
Reject(T),
}
impl<T, E> Try for ParseResult<T, E> {
type Output = T;
type Residual = ParseResultResidual<E>;
fn from_output(output: T) -> Self {
ParseResult::Accept(output)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
ParseResult::Accept(v) => ControlFlow::Continue(v),
ParseResult::Deny => ControlFlow::Break(ParseResultResidual::Deny),
ParseResult::Reject(e) => ControlFlow::Break(ParseResultResidual::Reject(e)),
}
}
}
impl<T, E> FromResidual for ParseResult<T, E> {
fn from_residual(residual: ParseResultResidual<E>) -> Self {
match residual {
ParseResultResidual::Deny => ParseResult::Deny,
ParseResultResidual::Reject(e) => ParseResult::Reject(e),
}
}
}
#[macro_export]
macro_rules! expect_tt {
($token:expr, $($variant:ident),+) => {{
let token = $token.clone();
let tt = token.tt().to_string();
let mut vs = String::new();
$(
let s = stringify!($variant);
vs.push_str(s);
vs.push_str("|");
)+
match tt.as_str() {
$(
stringify!($variant) => ParseResult::Accept(token),
)+
_ => {
// let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | "));
ParseResult::Reject(CompilerError::UnexpectedToken(token))
}
}
}};
}
#[macro_export]
macro_rules! expect_value {
($expr:expr, $variant:ident) => {{
let tok = $expr;
match tok.clone() {
Token::$variant(value) => ParseResult::Accept(value),
_ => ParseResult::Reject(CompilerError::UnexpectedToken(tok)),
}
}};
}
+398
View File
@@ -0,0 +1,398 @@
use std::collections::HashMap;
use crate::parser::CompilerError;
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
available_registers: Vec<String>,
/// Maps variable names to their current location (register or stack offset)
variable_locations: HashMap<String, Location>,
/// Maps registers to the variables they currently hold
register_contents: HashMap<String, String>,
/// Current stack offset for local variables (relative to bpr)
/// Starts at -4 (going downward from base pointer)
stack_offset: i32,
/// Track which registers are currently in use
in_use: HashMap<String, bool>,
}
#[derive(Debug, Clone)]
pub enum Location {
Register(String),
Stack(i32), // offset from bpr
}
impl RegisterAllocator {
pub fn new() -> Self {
// Initialize with available GP registers (rg0-rgf = 16 registers)
let registers = vec![
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga",
"rgb", "rgc", "rgd", "rge", "rgf",
]
.into_iter()
.map(String::from)
.collect();
RegisterAllocator {
available_registers: registers,
variable_locations: HashMap::new(),
register_contents: HashMap::new(),
stack_offset: -4, // Start at -4 (first local below saved bpr)
in_use: HashMap::new(),
}
}
/// Allocate a temporary register for expression evaluation
/// Returns the register name and optionally assembly code to save it
pub fn alloc_temp(&mut self) -> Result<(String, Vec<String>), CompilerError> {
let mut code = Vec::new();
// Try to find an unused register
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
self.in_use.insert(reg.clone(), true);
return Ok((reg.clone(), code));
}
}
// All registers in use - need to spill one
// Choose the first register with a variable we can spill
// Find a register to spill
let reg_to_spill = self
.available_registers
.iter()
.find(|reg| self.register_contents.contains_key(*reg))
.cloned();
if let Some(reg) = reg_to_spill {
// Spill this variable to stack
let spill_code = self.spill_register(&reg)?;
code.extend(spill_code);
self.in_use.insert(reg.clone(), true);
return Ok((reg, code));
}
Err(CompilerError::Generic(
"All registers are used up yet there are no variables to spill to the stack"
.to_string(),
))
}
/// Free a temporary register after use
/// NOTE: This will NOT free registers that contain variables!
/// Variables persist throughout their scope and must not be freed
pub fn free_temp(&mut self, reg: &str) {
// Check if this register contains a variable
if self.register_contents.contains_key(reg) {
// This register holds a variable - don't free it!
// Variables are only freed when they go out of scope via free_var()
return;
}
// This is a true temporary - safe to free
self.in_use.insert(reg.to_string(), false);
}
/// Allocate a register for a named variable
/// Returns the register and any necessary assembly code
pub fn alloc_var(
&mut self,
var_name: &str,
) -> Result<(String, Vec<String>), CompilerError> {
if let Some(location) = self.variable_locations.get(var_name).cloned() {
match location {
Location::Register(reg) => {
return Ok((reg.clone(), Vec::new()));
}
Location::Stack(offset) => {
// Variable was pushed, need to calculate actual position
let (reg, mut code) = self.alloc_temp()?;
// Load from bpr + offset (offset is negative)
code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg));
code.push(format!(
"\tldw {}, {} // bpr{}: {}",
reg,
reg,
offset - 4,
var_name
));
// Update location to register
self.variable_locations
.insert(var_name.to_string(), Location::Register(reg.clone()));
self.register_contents
.insert(reg.clone(), var_name.to_string());
return Ok((reg, code));
}
}
}
// Variable doesn't have a location yet, allocate a new register
let (reg, code) = self.alloc_temp()?;
self.variable_locations
.insert(var_name.to_string(), Location::Register(reg.clone()));
self.register_contents
.insert(reg.clone(), var_name.to_string());
Ok((reg, code))
}
/// Get the current location of a variable
pub fn get_var_location(&self, var_name: &str) -> Option<&Location> {
self.variable_locations.get(var_name)
}
/// Load a variable into a register (allocating if necessary)
/// Returns the register and assembly code to load it
pub fn load_var(
&mut self,
var_name: &str,
) -> Result<(String, Vec<String>), CompilerError> {
self.alloc_var(var_name)
}
/// Store a value from a register into a variable
/// Updates tracking and returns any necessary assembly code
pub fn store_var(&mut self, var_name: &str, source_reg: &str) -> Vec<String> {
let mut code = Vec::new();
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name) {
match location {
Location::Register(dest_reg) => {
if dest_reg != source_reg {
code.push(format!(
"\tmov {}, {} // var {}",
source_reg, dest_reg, var_name
));
}
}
Location::Stack(offset) => {
code.push(format!(
"\tstw {}, bpr, {} // var {}",
source_reg, offset, var_name
));
}
}
} else {
// Variable doesn't exist yet, we can just use the same reg.
// self.variable_locations.insert(
// var_name.to_string(),
// Location::Register(source_reg.to_string()),
// );
// self.register_contents
// .insert(source_reg.to_string(), var_name.to_string());
// self.in_use.insert(source_reg.to_string(), true);
let source_reg = source_reg.to_string();
// if we can avoid a move, absolutely do that.
if self.available_registers.contains(&source_reg) {
self.variable_locations
.insert(var_name.to_string(), Location::Register(source_reg.clone()));
self.register_contents
.insert(source_reg.clone(), var_name.to_string());
self.in_use.insert(source_reg, true);
} else if let Some(free_reg) = self.find_free_register() {
code.push(format!("\tmov {}, {}", source_reg, free_reg));
self.variable_locations
.insert(var_name.to_string(), Location::Register(free_reg.clone()));
self.register_contents
.insert(free_reg.clone(), var_name.to_string());
self.in_use.insert(free_reg, true);
} else {
// No free registers - allocate on stack
// code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset));
// self.variable_locations
// .insert(var_name.to_string(), Location::Stack(self.stack_offset));
// self.stack_offset -= 4; // Move to next stack slot
//
todo!(
"we should spill other registers and keep this variable on the stack as it's more recent!"
);
}
}
code
}
/// Spill a register to the stack
/// Returns assembly code to perform the spill
pub fn spill_register(&mut self, reg: &str) -> Result<Vec<String>, CompilerError> {
let mut code = Vec::new();
if let Some(var_name) = self.register_contents.get(reg).cloned() {
// PUSH register to stack (spr decrements automatically)
code.push(format!(
"\tpush {} // bpr{}: {}",
reg, self.stack_offset, var_name
));
// Track that we pushed one word
self.stack_offset -= 4;
// Update variable location - it's now at current spr
// Note: We track offset from bpr for consistency
self.variable_locations
.insert(var_name.clone(), Location::Stack(self.stack_offset));
// Remove from register tracking
self.register_contents.remove(reg);
}
Ok(code)
}
/// Find a free register (not currently in use)
fn find_free_register(&self) -> Option<String> {
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
return Some(reg.clone());
}
}
None
}
/// Spill all registers to stack (useful before function calls)
pub fn spill_all(&mut self) -> Vec<String> {
let mut code = Vec::new();
let regs_to_spill: Vec<String> = self.register_contents.keys().cloned().collect();
for reg in regs_to_spill {
if let Ok(spill_code) = self.spill_register(&reg) {
code.extend(spill_code);
}
}
code
}
/// Get the total stack offset
pub fn get_stack_offset(&self) -> i32 {
self.stack_offset
}
/// Get the total stack space needed for local variables
pub fn get_stack_size(&self) -> i32 {
-self.stack_offset // Convert negative offset to positive size
}
/// Reset allocator for a new function
pub fn reset(&mut self) {
self.variable_locations.clear();
self.register_contents.clear();
self.stack_offset = -4;
self.in_use.clear();
}
/// Mark a variable as dead (no longer needed)
/// Frees its register if it's in one
pub fn free_var(&mut self, var_name: &str) {
if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) {
let reg = reg.clone();
self.register_contents.remove(&reg);
self.in_use.insert(reg, false);
}
self.variable_locations.remove(var_name);
}
/// Get list of registers that contain variables and are in use
/// These need to be saved before function calls
pub fn get_caller_saved_registers(&self) -> Vec<String> {
self.register_contents
.iter()
.filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false))
.map(|(reg, _)| reg.clone())
.collect()
}
/// Save caller-saved registers before a function call
/// Returns assembly code to save them
pub fn save_caller_saved(&mut self) -> Vec<String> {
let mut code = Vec::new();
// For simplicity, save all currently used registers
// In a more sophisticated compiler, you'd only save registers that are live
for (reg, var_name) in self.register_contents.clone() {
if *self.in_use.get(&reg).unwrap_or(&false) {
code.push(format!("\tpush {}", reg));
}
}
code
}
/// Restore caller-saved registers after a function call
/// Returns assembly code to restore them
pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec<String> {
let mut code = Vec::new();
// Restore in reverse order (LIFO)
for reg in saved_regs.iter().rev() {
code.push(format!("\tpop {}", reg));
}
code
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg1, code1) = allocator.alloc_temp().unwrap();
assert_eq!(code1.len(), 0); // No spill needed
assert_eq!(reg1, "rg0");
let (reg2, code2) = allocator.alloc_temp().unwrap();
assert_eq!(code2.len(), 0);
assert_eq!(reg2, "rg1");
allocator.free_temp(&reg1);
let (reg3, code3) = allocator.alloc_temp().unwrap();
assert_eq!(code3.len(), 0);
assert_eq!(reg3, "rg0"); // Reuses freed register
}
#[test]
fn test_variable_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg, "rg0");
// Requesting same variable again should return same register
let (reg2, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg2, "rg0");
}
#[test]
fn test_stack_allocation() {
let mut allocator = RegisterAllocator::new();
// Allocate all 16 registers
for i in 0..16 {
allocator.alloc_var(&format!("var{}", i)).unwrap();
}
// Next allocation should spill to stack
let (reg, code) = allocator.alloc_var("var16").unwrap();
assert!(code.len() > 0); // Should have spill code
}
}
+13
View File
@@ -0,0 +1,13 @@
use crate::parser::{CompilerError, Program};
pub struct Analyser;
impl Analyser {
pub fn new() -> Self {
Self
}
pub fn analyse(&self, ast: Program) -> Result<(), CompilerError> {
Ok(())
}
}
-3985
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -16,8 +16,8 @@ required-features = ["config"]
[dependencies]
common = { path = "../common" }
assembler = { path = "../assembler" }
compiler = { path = "../compiler" }
dsa_editor = { path = "../dsa_editor" }
eframe = { version = "0.31.1" }
egui = "0.31.1"
dirs = "6.0.0"
discord-presence = { version = "1.6.0", optional = true }
@@ -30,7 +30,7 @@ default = ["config"]
discord-rpc = ["dep:discord-presence"]
config = ["dep:toml", "dep:serde"]
# Add support for Android for the fun of it.
# Add support for Android for the fun of it. Currently crashes lol.
[target.'cfg(target_os = "android")'.dependencies]
winit = { version = "0.30.11", features = ["android-native-activity"] }
# jni = "0.21.1"
@@ -38,3 +38,6 @@ winit = { version = "0.30.11", features = ["android-native-activity"] }
[target.'cfg(target_os = "android")'.dependencies.eframe]
version = "0.31.1"
features = ["android-native-activity"]
[target.'cfg(not(target_os = "android"))'.dependencies.eframe]
version = "0.31.1"
+134 -97
View File
@@ -1,15 +1,12 @@
use std::sync::Arc;
use std::{
sync::mpsc::{self, Receiver, Sender},
thread,
time::Duration,
};
use std::sync::mpsc::{self, Receiver, Sender};
#[allow(unused_imports)]
use crate::emulator::misc::rpc::{Activity, RpcClient};
use crate::emulator::system::model::StateUpdate;
use crate::emulator::system::{
model::{Command, PersistentState, Running, State},
model::{Command, Running},
processor::Processor,
};
@@ -19,28 +16,33 @@ use common::prelude::*;
#[allow(unused_variables)]
pub fn run_emulator(
cmd_rx: &Receiver<Command>,
state_tx: &Sender<State>,
state_tx: &Sender<StateUpdate>,
mut processor: Processor,
rpc_client: Option<&Arc<RpcClient>>,
) {
println!("INFO: Starting emulator.");
let mut running = Running::Paused;
let mut addr = 0u32;
let mut step = 0;
let mut addr;
let mut history = Vec::<(u32, Instruction)>::new();
let size = 256;
let memory_view = processor.memory.read_range(addr, size);
let initial_state = state(&mut processor, running, 0, memory_view, &mut history);
let _ = state_tx.send(initial_state);
state_tx
.send(StateUpdate::Running(Running::Paused))
.expect("Failed to send initial state!");
let mut instruction_count = 0;
let mut update = false;
loop {
let cmd = if running == Running::Running {
let cmd = if running == Running::Running || step > 0 {
match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd),
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Empty) => {
update = false;
None
}
Err(mpsc::TryRecvError::Disconnected) => break,
}
} else {
@@ -91,118 +93,153 @@ pub fn run_emulator(
processor.reset();
}
Command::Step => {
running = Running::Paused;
// Execute one cycle.
match processor.cycle() {
Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
);
continue;
}
}
instruction_count += 1;
}
Command::Read(new, _size) => {
addr = new;
Command::Step(x) => {
step = x;
}
Command::Write(offset, data) => {
processor.memory.write_range(offset, data);
update = true;
processor
.memory
.write_range(offset, data)
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory range!",
&mut processor,
);
});
}
Command::Interrupt(_interrupt) => {
update = true;
todo!("implement interrupts")
}
Command::MemRequest(new, size) if update => {
addr = new;
let _ = state_tx.send(StateUpdate::MemoryView(
processor.memory.read_range(addr, size).unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to read memory range!",
&mut processor,
);
Vec::new()
}),
));
}
Command::DisplayRequest if update => {
let _ = state_tx.send(StateUpdate::DisplayView(
processor.display().unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to read display!",
&mut processor,
);
Vec::new()
}),
));
}
Command::StackRequest if update => {
let _ = state_tx.send(StateUpdate::StackView(
processor.get_stack(32).unwrap_or_else(|_| {
report_err(state_tx, "Failed to read stack!", &mut processor);
Vec::new()
}),
));
}
Command::RegisterRequest if update => {
let _ = state_tx.send(StateUpdate::Registers(processor.registers));
}
Command::RunningRequest if update => {
let _ = state_tx.send(StateUpdate::Running(running));
}
Command::HistoryRequest if update => {
let hsc = history.clone();
history.clear();
let _ = state_tx.send(StateUpdate::InstructionHistory(hsc));
}
Command::InstructionCountRequest if update => {
let _ = state_tx.send(StateUpdate::Instructions(instruction_count));
}
Command::WriteBlock(addr, block) => {
processor
.memory
.write_range(addr, block.to_vec())
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory block!",
&mut processor,
);
});
}
_ => {}
}
}
let memory_view = processor.memory.read_range(addr, size);
let state = state(
&mut processor,
running,
instruction_count,
memory_view,
&mut history,
);
if step > 0 {
step -= 1;
update = true;
running = Running::Paused;
println!("state");
let _ = state_tx.send(state);
// Execute one cycle.
match processor.cycle() {
Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor
.get(Register::Pcx)
.expect("SPR should never be invalid");
report_err(
state_tx,
&format!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
),
&mut processor,
);
}
}
instruction_count += 1;
continue;
}
if running == Running::Running {
let mut update = false;
update = true;
// Execute one cycle.
let instruction = match processor.cycle() {
Ok(instruction) => instruction,
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
let pcx = processor
.get(Register::Pcx)
.expect("PCX should never be invalid");
report_err(
state_tx,
&format!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
),
&mut processor,
);
(pcx, Instruction::Nop)
}
};
history.push(instruction);
// let instruction = match Instruction::decode(cpu_lock.get(Register::Cir))
// {};
if matches!(instruction.1, Instruction::Halt) {
running = Running::Halted;
update = true;
}
instruction_count += 1;
// Send state updates every 100 instructions
if instruction_count % 100 == 0 {
update = true;
}
if update {
let memory_view = processor.memory.read_range(addr, size);
let state = state(
&mut processor,
running,
instruction_count,
memory_view,
&mut history,
);
println!("running state");
// println!("state!!! {:?}", state.history);
let _ = state_tx.send(state);
}
} else {
thread::sleep(Duration::from_millis(1));
}
}
}
fn state(
cpu_lock: &mut Processor,
running: Running,
instruction_count: usize,
memory_view: Vec<u8>,
history: &mut Vec<(u32, Instruction)>,
) -> State {
let hsclone = history.clone();
history.clear();
State {
// TODO: Replace with actual register access from your CPU.
reg_file: cpu_lock.registers,
running,
instructions: instruction_count,
stack_view: cpu_lock.get_stack(32),
memory_view,
display_view: cpu_lock.display(),
error: None,
persistent: PersistentState { history: hsclone },
}
fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) {
processor
.begin_interrupt(Interrupt::HardFault)
.expect("What kind of goofy ahh shenanigans did you do with your fault handler? At this point, the emulator can just crash. this is on you.");
let _ = state_tx.send(StateUpdate::Error(why.to_string()));
}
+71 -23
View File
@@ -1,13 +1,43 @@
use std::collections::HashMap;
use crate::emulator::system::model::ProcessorError;
pub trait MemoryUnit: Send + Sync {
fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> u8;
fn write_byte(&mut self, addr: u32, value: u8);
fn read_word(&mut self, addr: u32) -> u32;
fn write_word(&mut self, addr: u32, value: u32);
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
fn write_range(&mut self, addr: u32, value: Vec<u8>);
fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError>;
fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>;
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>;
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>;
fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> {
let mut data = Vec::with_capacity(size as usize);
for i in 0..size {
data.push(self.read_byte(addr + i)?);
}
Ok(data)
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
for (i, byte) in value.into_iter().enumerate() {
self.write_byte(addr + i as u32, byte)?;
}
Ok(())
}
fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> {
let mut data = [0; 256];
for (i, byte) in data.iter_mut().enumerate() {
*byte = self.read_byte(addr + i as u32)?;
}
Ok(data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> {
for (i, byte) in data.iter().enumerate() {
self.write_byte(addr + i as u32, *byte)?;
}
Ok(())
}
}
pub struct MainStore {
@@ -64,59 +94,77 @@ impl MemoryUnit for MainStore {
self.data.clear();
}
fn read_byte(&mut self, addr: u32) -> u8 {
fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError> {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.block(block_addr);
block.data[offset as usize]
Ok(block.data[offset as usize])
}
fn read_word(&mut self, addr: u32) -> u32 {
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> {
if addr % 4 != 0 {
return Err(ProcessorError::BadMemoryAccess(addr));
}
let (block_addr, offset) = Self::segment_addr(addr);
println!("reading word from {block_addr:x?} + {offset}");
let block = self.mut_block(block_addr);
let mut bytes = [0; 4];
bytes[0] = block.data[offset as usize];
bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize];
u32::from_be_bytes(bytes)
Ok(u32::from_be_bytes(bytes))
}
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> {
let mut data = Vec::with_capacity(size as usize);
for i in 0..size {
data.push(self.read_byte(addr + i));
data.push(self.read_byte(addr + i)?);
}
// println!("reading {data:?} from {addr:x?}");
data
Ok(data)
}
fn write_byte(&mut self, addr: u32, value: u8) {
fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError> {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = value;
Ok(())
}
fn write_word(&mut self, addr: u32, value: u32) {
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> {
if addr % 4 != 0 {
return Err(ProcessorError::BadMemoryAccess(addr));
}
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8;
block.data[(offset + 1) as usize] = (value >> 16) as u8;
block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8;
Ok(())
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) {
// println!("writing {value:?} to {addr:x?}");
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
for (i, byte) in value.into_iter().enumerate() {
let (block_addr, offset) = Self::segment_addr(addr + i as u32);
let block = self.mut_block(block_addr);
block.data[offset as usize] = byte;
}
Ok(())
}
fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> {
let (block_addr, _) = Self::segment_addr(addr);
let block = self.block(block_addr);
Ok(block.data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> {
let (block_addr, _) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data = data;
Ok(())
}
}
+142 -56
View File
@@ -1,3 +1,5 @@
use std::sync::mpsc::{self, Receiver, Sender};
use common::prelude::*;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
@@ -16,15 +18,143 @@ pub trait IODevice: Send + Sync {
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Command {
// set emulator state.
Start,
Stop,
Step,
Step(usize),
Reset(usize),
Interrupt(Interrupt),
// Performs direct read/write operations on the emulator's memory.
Read(Address, u32),
Write(Address, Vec<u8>),
WriteBlock(Address, Box<[u8; 256]>),
// request emulator state.
MemRequest(Address, u32),
DisplayRequest,
StackRequest,
RegisterRequest,
RunningRequest,
HistoryRequest,
InstructionCountRequest,
}
#[derive(Debug)]
pub enum ProcessorError {
InvalidInstruction(u32),
InvalidRegister(u8),
BadMemoryAccess(u32),
}
impl std::error::Error for ProcessorError {}
impl std::fmt::Display for ProcessorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidInstruction(instruction) => {
write!(f, "Invalid instruction: {instruction}")
}
Self::InvalidRegister(register) => {
write!(f, "Invalid register: {register}")
}
Self::BadMemoryAccess(address) => {
write!(f, "Bad memory access: {address}")
}
}
}
}
pub struct State {
pub state_receiver: Receiver<StateUpdate>,
pub cmd_sender: Sender<Command>,
// Processor state
pub reg_file: RegFile,
pub running: Running,
pub instructions: usize,
// Memory access views
pub stack_view: Vec<u8>,
pub memory_view: Vec<u8>,
pub display_view: Vec<u8>,
pub error_log: Vec<String>,
pub instruction_history: Vec<(u32, Instruction)>,
}
impl State {
#[must_use]
pub fn new(sender: Sender<Command>, receiver: Receiver<StateUpdate>) -> Self {
Self {
state_receiver: receiver,
cmd_sender: sender,
reg_file: RegFile::default(),
running: Running::Paused,
instructions: 0,
stack_view: vec![],
memory_view: vec![],
display_view: vec![],
error_log: vec![],
instruction_history: vec![],
}
}
pub fn send(&mut self, cmd: Command) {
if let Err(e) = self.cmd_sender.send(cmd) {
self.error_log.push(e.to_string());
}
}
pub fn update(&mut self) -> Result<(), mpsc::TryRecvError> {
while let Ok(update) = self.state_receiver.try_recv() {
match update {
StateUpdate::Registers(reg_file) => self.reg_file = reg_file,
StateUpdate::Running(running) => self.running = running,
StateUpdate::Instructions(instructions) => {
self.instructions = instructions;
}
StateUpdate::StackView(stack_view) => self.stack_view = stack_view,
StateUpdate::MemoryView(memory_view) => self.memory_view = memory_view,
StateUpdate::DisplayView(display_view) => {
self.display_view = display_view;
}
StateUpdate::Error(err_state) => self.error_log.push(err_state),
StateUpdate::InstructionHistory(history) => {
self.instruction_history.extend(history);
}
}
if self.error_log.len() > 256 {
self.error_log.drain(0..self.error_log.len() - 256);
}
if self.instruction_history.len() > 1024 {
self.instruction_history
.drain(0..self.instruction_history.len() - 1024);
}
}
if let Err(e) = self.state_receiver.try_recv() {
match e {
mpsc::TryRecvError::Empty => {}
mpsc::TryRecvError::Disconnected => {
return Err(e);
}
}
}
Ok(())
}
}
pub enum StateUpdate {
Registers(RegFile),
Running(Running),
Instructions(usize),
StackView(Vec<u8>),
MemoryView(Vec<u8>),
DisplayView(Vec<u8>),
Error(String),
InstructionHistory(Vec<(u32, Instruction)>),
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -127,8 +257,8 @@ impl RegFile {
self.pcx = 0;
}
pub fn reg(&mut self, reg: Register) -> &mut u32 {
match reg {
pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
Ok(match reg {
Register::Rg0 => &mut self.rg0,
Register::Rg1 => &mut self.rg1,
Register::Rg2 => &mut self.rg2,
@@ -156,13 +286,13 @@ impl RegFile {
Register::Sts => &mut self.sts,
Register::Cir => &mut self.cir,
Register::Pcx => &mut self.pcx,
_ => panic!("Invalid register."),
}
_ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)),
})
}
#[must_use]
pub fn get(&self, reg: Register) -> u32 {
match reg {
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
Ok(match reg {
Register::Rg0 => self.rg0,
Register::Rg1 => self.rg1,
Register::Rg2 => self.rg2,
@@ -191,51 +321,7 @@ impl RegFile {
Register::Cir => self.cir,
Register::Pcx => self.pcx,
Register::Zero => 0,
_ => panic!("Invalid register."),
}
}
}
pub struct State {
pub reg_file: RegFile,
pub running: Running,
pub instructions: usize,
// Memory access views
pub stack_view: Vec<u8>,
pub memory_view: Vec<u8>,
pub display_view: Vec<u8>,
pub error: Option<String>,
pub persistent: PersistentState,
}
impl Default for State {
fn default() -> Self {
Self {
reg_file: RegFile::default(),
running: Running::Paused,
instructions: 0,
stack_view: vec![],
memory_view: vec![],
display_view: vec![],
persistent: PersistentState::default(),
error: None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct PersistentState {
pub history: Vec<(u32, Instruction)>,
}
impl PersistentState {
pub fn update(&mut self, new_state: &Self) {
self.history.extend(new_state.history.clone());
if self.history.len() > 1024 {
let len = self.history.len() - 1024;
self.history.drain(..len);
}
_ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)),
})
}
}
+137 -116
View File
@@ -5,12 +5,10 @@ use std::{
use crate::emulator::system::{
memory::MemoryUnit,
model::{IODevice, RegFile},
model::{IODevice, ProcessorError, RegFile},
};
use common::instructions::{
Instruction, Interrupt, Register, errors::InstructionDecodeError,
};
use common::instructions::{Instruction, Interrupt, Register};
pub struct Processor {
pub memory: Box<dyn MemoryUnit>,
@@ -18,14 +16,13 @@ pub struct Processor {
pub halted: bool,
pub io_devices: Vec<Arc<dyn IODevice>>,
pub dustbin: u32,
pub void: u32,
}
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
#[allow(clippy::needless_pass_by_ref_mut)]
impl Processor {
#[must_use]
pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self {
@@ -34,7 +31,7 @@ impl Processor {
registers: RegFile::default(),
halted: false,
io_devices,
dustbin: 0,
void: 0,
}
}
@@ -48,48 +45,45 @@ impl Processor {
self.memory.reset();
}
pub fn cycle(&mut self) -> Result<(u32, Instruction), InstructionDecodeError> {
pub fn cycle(&mut self) -> Result<(u32, Instruction), ProcessorError> {
self.halted = false;
// Get value from PCX.
let addr = self.fetch();
let addr = self.fetch()?;
// Increment PCX.
self.advance();
// Set MAR to the previous value of PCX.
*self.reg(Register::Mar) = addr;
let val = self.memory.read_word(addr);
*self.reg(Register::Mar)? = addr;
let val = self.memory.read_word(addr)?;
// Set CIR to the value of RAM[MAR].
*self.reg(Register::Mar) = val;
*self.reg(Register::Mar)? = val;
// Decode and execute the instruction.
let instruction = Instruction::decode(val)?;
log(&instruction.to_string());
instruction.execute(self);
let instruction = Instruction::decode(val)
.map_err(|_| ProcessorError::InvalidInstruction(val))?;
instruction.execute(self)?;
Ok((addr, instruction))
}
fn fetch(&self) -> u32 {
const fn fetch(&self) -> Result<u32, ProcessorError> {
self.get(Register::Pcx)
}
#[must_use]
pub fn get(&self, reg: Register) -> u32 {
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
self.registers.get(reg)
}
pub fn reg(&mut self, reg: Register) -> &mut u32 {
pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
match reg {
Register::Zero => &mut self.dustbin,
Register::Zero => Ok(&mut self.void),
_ => self.registers.reg(reg),
}
}
pub fn display(&mut self) -> Vec<u8> {
pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
self.memory.read_range(0x20000, 2000)
}
@@ -99,53 +93,74 @@ impl Processor {
self.set_flag(Flag::LessThan, a < b);
}
// stack operations
pub fn push(&mut self, value: u32) {
let stack_ptr = self.get(Register::Spr);
*self.reg(Register::Spr) += 4;
self.memory.write_word(stack_ptr, value);
}
pub fn pop(&mut self) -> u32 {
*self.reg(Register::Spr) -= 4;
self.memory.read_word(self.get(Register::Spr))
}
// functions to set new state
fn set_flag(&mut self, flag: Flag, value: bool) {
if value {
*self.reg(Register::Sts) |= flag as u32;
*self
.reg(Register::Sts)
.expect("STS should never be invalid") |= flag as u32;
} else {
*self.reg(Register::Sts) &= !(flag as u32);
*self
.reg(Register::Sts)
.expect("STS should never be invalid") &= !(flag as u32);
}
}
fn get_flag(&self, flag: Flag) -> bool {
self.get(Register::Sts) & (flag as u32) != 0
fn get_flag(&self, flag: Flag) -> Result<bool, ProcessorError> {
Ok(self.get(Register::Sts)? & (flag as u32) != 0)
}
fn advance(&mut self) {
fn advance(&mut self) -> Result<(), ProcessorError> {
// increment PCX
*self.reg(Register::Pcx) += 4;
*self.reg(Register::Pcx)? += 4;
Ok(())
}
fn jump(&mut self, reg: Register, offset: u16) {
*self.reg(Register::Pcx) = self.get(reg) + u32::from(offset);
fn jump(&mut self, reg: Register, offset: u16) -> Result<(), ProcessorError> {
*self.reg(Register::Pcx)? = self.get(reg)? + u32::from(offset);
Ok(())
}
fn begin_interrupt(&mut self, _int: Interrupt) {
// first we get the address of the interrupt descriptor table.
todo!();
pub fn begin_interrupt(
&mut self,
interrupt: Interrupt,
) -> Result<(), ProcessorError> {
let idt = self.get(Register::Idr)?;
let addr = self
.memory
.read_word(idt + u32::from(interrupt.as_u8()) * 4)?;
println!("INFO: Interrupt {interrupt:?} addr: {addr}");
self.push(self.get(Register::Pcx)?)?;
*self.reg(Register::Pcx)? = addr;
Ok(())
}
fn end_interrupt(&mut self) {
todo!();
fn push(&mut self, val: u32) -> Result<(), ProcessorError> {
*self.reg(Register::Spr)? -= 4;
let reg = *self.reg(Register::Spr)?;
self.memory.write_word(reg, val)
}
pub fn get_stack(&mut self, n: u32) -> Vec<u8> {
let addr = self.get(Register::Spr);
fn pop(&mut self) -> Result<u32, ProcessorError> {
let reg = *self.reg(Register::Spr)?;
let val = self.memory.read_word(reg)?;
*self.reg(Register::Spr)? += 4;
Ok(val)
}
// TODO: remove this once implemented
#[allow(clippy::needless_pass_by_ref_mut)]
fn end_interrupt(&mut self) -> Result<(), ProcessorError> {
let ret = self.pop()?;
*self.reg(Register::Ret)? = ret;
*self.reg(Register::Pcx)? = ret;
Ok(())
}
pub fn get_stack(&mut self, n: u32) -> Result<Vec<u8>, ProcessorError> {
let addr = self.get(Register::Spr)?;
let size = n * 4;
// returns the stack
self.memory.read_range(
@@ -170,38 +185,40 @@ enum Flag {
}
trait Executable {
fn execute(self, cpu: &mut Processor);
fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError>;
}
impl Executable for Instruction {
#[allow(clippy::too_many_lines)]
fn execute(self, cpu: &mut Processor) {
fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError> {
match self {
// No operation - a blank line.
// Copies from SrcReg to a.drReg.
Self::Mov(a) => {
*cpu.reg(a.dr) = cpu.get(a.sr1);
*cpu.reg(a.dr)? = cpu.get(a.sr1)?;
}
// Copies from SrcReg to a.drReg, sign extending the value to take up a full
// word.
Self::MovSigned(a) => {
*cpu.reg(a.dr) = sign_extend(cpu.get(a.sr1));
*cpu.reg(a.dr)? = sign_extend(cpu.get(a.sr1)?);
}
// Loads a byte from memory address (base + offset) into a.drReg. The
// effective address must be byte-aligned.
Self::LoadByte(a) => {
*cpu.reg(a.r2) = u32::from(
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)),
*cpu.reg(a.r2)? = u32::from(
cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?,
);
}
// Loads a sign-extended byte from memory address (base + offset) into
// a.drReg. The effective address must be byte-aligned.
Self::LoadByteSigned(a) => {
*cpu.reg(a.r2) = sign_extend(u32::from(
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)),
*cpu.reg(a.r2)? = sign_extend(u32::from(
cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?,
));
}
@@ -210,181 +227,184 @@ impl Executable for Instruction {
Self::LoadHalfword(a) => {
// we read an entire word, then right shift so we only get the first half
// of the word
*cpu.reg(a.r2) =
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16;
*cpu.reg(a.r2)? = cpu
.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
>> 16;
}
// Loads a sign-extended half-word from memory address (base + offset) into
// a.drReg. The effective address must be 2-byte-aligned.
Self::LoadHalfwordSigned(a) => {
*cpu.reg(a.r2) = sign_extend(
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16,
*cpu.reg(a.r2)? = sign_extend(
cpu.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
>> 16,
);
}
// Loads a word from memory address (base + offset) into a.drReg. The
// effective address must be 4-byte-aligned.
Self::LoadWord(a) => {
*cpu.reg(a.r2) =
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate));
*cpu.reg(a.r2)? = cpu
.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?;
}
// Stores a byte from SrcReg in memory address (base + offset) The effective
// address must be byte-aligned.
Self::StoreByte(a) => {
cpu.memory.write_byte(
cpu.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1) as u8,
);
cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)? as u8,
)?;
}
// Stores a half-word from SrcReg in memory address (base + offset) The
// effective address must be 2-byte-aligned.
Self::StoreHalfword(a) => {
// split the value into bytes and then write two bytes
let bytes = (cpu.get(a.r1) as u16).to_le_bytes();
let bytes = (cpu.get(a.r1)? as u16).to_le_bytes();
cpu.memory
.write_byte(cpu.get(a.r2) + u32::from(a.immediate), bytes[0]);
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0])?;
cpu.memory
.write_byte(cpu.get(a.r2) + u32::from(a.immediate) + 1, bytes[1]);
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1])?;
}
// Stores a word from SrcReg in memory address (base + offset) The effective
// address must be 4-byte-aligned.
Self::StoreWord(a) => {
cpu.memory
.write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1));
cpu.memory.write_word(
cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)?,
)?;
}
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
// word. To populate the upper 16 bits, see LUI.
Self::LoadLowerImmediate(a) => {
*cpu.reg(a.r1) = u32::from(a.immediate);
*cpu.reg(a.r1)? = u32::from(a.immediate);
}
// Loads a 16-bit literal value into reg, setting the top 16 bits of the word.
// To populate the lower 16 bits, see LLI.
Self::LoadUpperImmediate(a) => {
*cpu.reg(a.r1) =
(cpu.get(a.r1) & 0x0000_FFFF) | (u32::from(a.immediate) << 16);
*cpu.reg(a.r1)? =
(cpu.get(a.r1)? & 0x0000_FFFF) | (u32::from(a.immediate) << 16);
}
// Unconditionally jumps to the calculated address or direct address
Self::Jump(a) => cpu.jump(a.r1, a.immediate),
Self::Jump(a) => cpu.jump(a.r1, a.immediate)?,
// Jumps to the calculated address or direct address if equal flag set.
Self::JumpEq(a) => {
if cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
if cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
}
}
// Jumps to the calculated address or direct address if equal flag not set.
Self::JumpNeq(a) => {
if !cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
if !cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
}
}
// Jumps to the calculated address or direct address if greater than flag set.
Self::JumpGt(a) => {
if cpu.get_flag(Flag::GreaterThan) {
cpu.jump(a.r1, a.immediate);
if cpu.get_flag(Flag::GreaterThan)? {
cpu.jump(a.r1, a.immediate)?;
}
}
// Jumps to the calculated address or direct address if greater than flag or
// equal flag set.
Self::JumpGe(a) => {
if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
if cpu.get_flag(Flag::GreaterThan)? || cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
}
}
// Jumps to the calculated address or direct address if less than flag set.
Self::JumpLt(a) => {
if cpu.get_flag(Flag::LessThan) {
cpu.jump(a.r1, a.immediate);
if cpu.get_flag(Flag::LessThan)? {
cpu.jump(a.r1, a.immediate)?;
}
}
// Jumps to the calculated address or direct address if less than flag or
// equal flag set.
Self::JumpLe(a) => {
if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
if cpu.get_flag(Flag::LessThan)? || cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
}
}
// Increments the value in the given register
Self::Increment(a) => *cpu.reg(a.sr1) = inc(cpu.get(a.sr1)),
Self::Increment(a) => *cpu.reg(a.sr1)? = inc(cpu.get(a.sr1)?),
// Decrements the value in the given register
Self::Decrement(a) => *cpu.reg(a.sr1) = dec(cpu.get(a.sr1)),
Self::Decrement(a) => *cpu.reg(a.sr1)? = dec(cpu.get(a.sr1)?),
// Left shifts the value in Reg by the given amount (either a register, or a
// literal value)
Self::ShiftLeft(a) => {
let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1);
*cpu.reg(a.sr1) =
shl(val, if regval != 0 { regval as u8 } else { a.shamt });
let reg = cpu.get(a.sr1)?;
let val = a.shamt;
*cpu.reg(a.sr1)? = shl(reg, val);
}
// Right shifts the value in Reg by the given amount (either a register, or a
// literal value).
Self::ShiftRight(a) => {
let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1);
*cpu.reg(a.sr1) =
shr(val, if regval != 0 { regval as u8 } else { a.shamt });
let regval = cpu.get(a.sr1)?;
let val = a.shamt;
*cpu.reg(a.sr1)? = shr(regval, val);
}
// Adds the value of Src2 to Src1 and writes the result to a.dr
Self::Add(a) => {
*cpu.reg(a.dr) = add(cpu.get(a.sr1), cpu.get(a.sr2));
*cpu.reg(a.dr)? = add(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
}
// Subtracts the value of Src2 from Src1 and writes the result to a.dr
Self::Sub(a) => {
*cpu.reg(a.dr) = sub(cpu.get(a.sr1), cpu.get(a.sr2));
*cpu.reg(a.dr)? = sub(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
}
Self::AddImmediate(a) => {
*cpu.reg(a.r2) = add(cpu.get(a.r1), u32::from(a.immediate));
*cpu.reg(a.r2)? = add(cpu.get(a.r1)?, u32::from(a.immediate));
}
Self::SubImmediate(a) => {
*cpu.reg(a.r2) = sub(cpu.get(a.r1), u32::from(a.immediate));
*cpu.reg(a.r2)? = sub(cpu.get(a.r1)?, u32::from(a.immediate));
}
// Performs bitwise AND on Src1 and Src2 storing the result in a.dr
Self::And(a) => *cpu.reg(a.dr) = and(cpu.get(a.sr1), cpu.get(a.sr2)),
Self::And(a) => *cpu.reg(a.dr)? = and(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
// Performs bitwise OR on Src1 and Src2 storing the result in a.dr
Self::Or(a) => *cpu.reg(a.dr) = or(cpu.get(a.sr1), cpu.get(a.sr2)),
Self::Or(a) => *cpu.reg(a.dr)? = or(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
// Performs bitwise NOT on Src storing the result in a.dr
Self::Not(a) => *cpu.reg(a.dr) = not(cpu.get(a.sr1)),
Self::Not(a) => *cpu.reg(a.dr)? = not(cpu.get(a.sr1)?),
// Performs bitwise XOR on Src1 and Src2 storing the result in a.dr
Self::Xor(a) => *cpu.reg(a.dr) = xor(cpu.get(a.sr1), cpu.get(a.sr2)),
Self::Xor(a) => *cpu.reg(a.dr)? = xor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
// Performs bitwise NAND on Src1 and Src2 storing the result in a.dr
Self::Nand(a) => *cpu.reg(a.dr) = nand(cpu.get(a.sr1), cpu.get(a.sr2)),
Self::Nand(a) => *cpu.reg(a.dr)? = nand(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
// Performs bitwise NOR on Src1 and Src2 storing the result in a.dr
Self::Nor(a) => *cpu.reg(a.dr) = nor(cpu.get(a.sr1), cpu.get(a.sr2)),
Self::Nor(a) => *cpu.reg(a.dr)? = nor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
// Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr
Self::Xnor(a) => *cpu.reg(a.dr) = xnor(cpu.get(a.sr1), cpu.get(a.sr2)),
Self::Xnor(a) => *cpu.reg(a.dr)? = xnor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
// Compares the value of Reg1 to the value in Reg2. The results of the
// comparisons are set in the Status register.
Self::Compare(a) => {
cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2));
cpu.cmp(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
}
// Initiates an interrupt with the given 8 bit interrupt code.
@@ -392,12 +412,12 @@ impl Executable for Instruction {
// - The return address is saved to the RET register.
// - The stack base ptr is set to the kernel stack.
Self::Interrupt(interrupt_code) => {
cpu.begin_interrupt(interrupt_code);
cpu.begin_interrupt(interrupt_code)?;
}
// Returns from an interrupt,
Self::IntReturn => {
cpu.end_interrupt();
cpu.end_interrupt()?;
}
// Halts the processor.
@@ -411,6 +431,7 @@ impl Executable for Instruction {
todo!()
}
}
Ok(())
}
}
+304 -132
View File
@@ -13,22 +13,32 @@ fn test_nop_instruction() {
let mut cpu = create_test_processor();
let initial_state = cpu.registers;
Instruction::Nop.execute(&mut cpu);
Instruction::Nop.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.registers.get(Register::Rg0),
initial_state.get(Register::Rg0)
cpu.registers
.get(Register::Rg0)
.expect("Failed to get register Rg0"),
initial_state
.get(Register::Rg0)
.expect("Failed to get register Rg0")
);
assert_eq!(
cpu.registers.get(Register::Acc),
initial_state.get(Register::Acc)
cpu.registers
.get(Register::Acc)
.expect("Failed to get register Acc"),
initial_state
.get(Register::Acc)
.expect("Failed to get register Acc")
);
}
#[test]
fn test_mov_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x1234_5678;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1234_5678;
let mov_instr = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg1),
@@ -37,14 +47,19 @@ fn test_mov_instruction() {
None,
));
mov_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
mov_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
}
#[test]
fn test_mov_signed_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x0000_00FF;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_00FF;
let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new(
Some(Register::Rg1),
@@ -53,16 +68,23 @@ fn test_mov_signed_instruction() {
None,
));
mov_signed_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
mov_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
}
#[test]
fn test_load_byte_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_byte(addr, 0xAB);
*cpu.reg(Register::Rg1) = addr - 4;
cpu.memory
.write_byte(addr, 0xAB)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
4,
@@ -70,16 +92,23 @@ fn test_load_byte_instruction() {
Some(Register::Rg2),
));
load_byte_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB);
load_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_00AB
);
}
#[test]
fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_byte(addr, 0xFF);
*cpu.reg(Register::Rg1) = addr;
cpu.memory
.write_byte(addr, 0xFF)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
0,
@@ -87,16 +116,23 @@ fn test_load_byte_signed_instruction() {
Some(Register::Rg2),
));
load_byte_signed_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
load_byte_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
}
#[test]
fn test_load_halfword_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_word(addr, 0x1234_5678);
*cpu.reg(Register::Rg1) = addr;
cpu.memory
.write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
0,
@@ -104,16 +140,23 @@ fn test_load_halfword_instruction() {
Some(Register::Rg2),
));
load_halfword_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x0000_1234);
load_halfword_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_1234
);
}
#[test]
fn test_load_word_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_word(addr, 0x1234_5678);
*cpu.reg(Register::Rg1) = addr;
cpu.memory
.write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
0,
@@ -121,16 +164,21 @@ fn test_load_word_instruction() {
Some(Register::Rg2),
));
load_word_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
load_word_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
}
#[test]
fn test_store_byte_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
*cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2) = 0xAB;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0xAB;
let store_byte_instr = Instruction::StoreByte(ITypeArgs::new(
0,
@@ -138,16 +186,18 @@ fn test_store_byte_instruction() {
Some(Register::Rg1),
));
store_byte_instr.execute(&mut cpu);
assert_eq!(cpu.memory.read_byte(addr), 0xAB);
store_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB);
}
#[test]
fn test_store_word_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
*cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2) = 0x1234_5678;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0x1234_5678;
let store_word_instr = Instruction::StoreWord(ITypeArgs::new(
0,
@@ -155,15 +205,17 @@ fn test_store_word_instruction() {
Some(Register::Rg1),
));
store_word_instr.execute(&mut cpu);
assert_eq!(cpu.memory.read_word(addr), 0x1234_5678);
store_word_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.memory.read_word(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0x1234_5678);
}
#[test]
fn test_add_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 15;
*cpu.reg(Register::Rg2) = 25;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 15;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 25;
let add_instr = Instruction::Add(RTypeArgs::new(
Some(Register::Rg1),
@@ -172,15 +224,20 @@ fn test_add_instruction() {
None,
));
add_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 40);
add_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
40
);
}
#[test]
fn test_sub_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2) = 20;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 20;
let sub_instr = Instruction::Sub(RTypeArgs::new(
Some(Register::Rg1),
@@ -189,15 +246,20 @@ fn test_sub_instruction() {
None,
));
sub_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 30);
sub_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
30
);
}
#[test]
fn test_and_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let and_instr = Instruction::And(RTypeArgs::new(
Some(Register::Rg1),
@@ -206,15 +268,20 @@ fn test_and_instruction() {
None,
));
and_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 0b1000);
and_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1000
);
}
#[test]
fn test_or_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let or_instr = Instruction::Or(RTypeArgs::new(
Some(Register::Rg1),
@@ -223,15 +290,20 @@ fn test_or_instruction() {
None,
));
or_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 0b1110);
or_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1110
);
}
#[test]
fn test_xor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let xor_instr = Instruction::Xor(RTypeArgs::new(
Some(Register::Rg1),
@@ -240,14 +312,19 @@ fn test_xor_instruction() {
None,
));
xor_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 0b0110);
xor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b0110
);
}
#[test]
fn test_not_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x0F0F_0F0F;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0F0F_0F0F;
let not_instr = Instruction::Not(RTypeArgs::new(
Some(Register::Rg1),
@@ -256,15 +333,20 @@ fn test_not_instruction() {
None,
));
not_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0);
not_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xF0F0_F0F0
);
}
#[test]
fn test_compare_equal() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 42;
*cpu.reg(Register::Rg2) = 42;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 42;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
@@ -273,18 +355,26 @@ fn test_compare_equal() {
None,
));
cmp_instr.execute(&mut cpu);
cmp_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(cpu.get_flag(Flag::Equal));
assert!(!cpu.get_flag(Flag::GreaterThan));
assert!(!cpu.get_flag(Flag::LessThan));
assert!(cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(
!cpu.get_flag(Flag::GreaterThan)
.expect("Failed to get flag GreaterThan")
);
assert!(
!cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
}
#[test]
fn test_compare_greater_than() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2) = 30;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
@@ -293,18 +383,26 @@ fn test_compare_greater_than() {
None,
));
cmp_instr.execute(&mut cpu);
cmp_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(!cpu.get_flag(Flag::Equal));
assert!(cpu.get_flag(Flag::GreaterThan));
assert!(!cpu.get_flag(Flag::LessThan));
assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(
cpu.get_flag(Flag::GreaterThan)
.expect("Failed to get flag GreaterThan")
);
assert!(
!cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
}
#[test]
fn test_compare_less_than() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 20;
*cpu.reg(Register::Rg2) = 30;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 20;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
@@ -313,41 +411,59 @@ fn test_compare_less_than() {
None,
));
cmp_instr.execute(&mut cpu);
cmp_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(!cpu.get_flag(Flag::Equal));
assert!(!cpu.get_flag(Flag::GreaterThan));
assert!(cpu.get_flag(Flag::LessThan));
assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(
!cpu.get_flag(Flag::GreaterThan)
.expect("Failed to get flag GreaterThan")
);
assert!(
cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
}
#[test]
fn test_increment_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 42;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
let inc_instr =
Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None));
inc_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 43);
inc_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
43
);
}
#[test]
fn test_decrement_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 42;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
let dec_instr =
Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None));
dec_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 41);
dec_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
41
);
}
#[test]
fn test_shift_left_with_shamt() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
@@ -356,14 +472,19 @@ fn test_shift_left_with_shamt() {
Some(2),
));
shl_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0b10_1000);
shl_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b10_1000
);
}
#[test]
fn test_shift_right_with_shamt() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b10_1000;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b10_1000;
let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
Some(Register::Rg1),
@@ -372,26 +493,32 @@ fn test_shift_right_with_shamt() {
Some(2),
));
shr_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0b1010);
shr_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b1010
);
}
#[test]
fn test_shift_left_with_register() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1010;
*cpu.reg(Register::Rg2) = 3;
// #[test]
// fn test_shift_left_with_register() {
// let mut cpu = create_test_processor();
// *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
None,
None,
));
// let shl_instr =
// Instruction::ShiftLeft(RTypeArgs::new(Some(Register::Rg1), None, None,
// Some(3)));
shl_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0b101_0000);
}
// shl_instr.execute(&mut cpu).expect(
// "Emulator was slain by losing the game while attempting to execute
// instruction", );
// assert_eq!(
// cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
// 0b101_0000
// );
// }
#[test]
fn test_load_lower_immediate() {
@@ -403,14 +530,19 @@ fn test_load_lower_immediate() {
None,
));
lli_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0x0000_1234);
lli_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x0000_1234
);
}
#[test]
fn test_load_upper_immediate() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x0000_5678;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_5678;
let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new(
0x1234,
@@ -418,48 +550,71 @@ fn test_load_upper_immediate() {
None,
));
lui_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0x1234_5678);
lui_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x1234_5678
);
}
#[test]
fn test_jump_unconditional() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx");
let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Pcx), 0x1100);
assert_ne!(cpu.get(Register::Pcx), initial_pc);
jump_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
assert_ne!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
}
#[test]
fn test_jump_equal_when_flag_set() {
let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, true);
*cpu.reg(Register::Rg1) = 0x1000;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let jump_eq_instr =
Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_eq_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Pcx), 0x1100);
jump_eq_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
}
#[test]
fn test_jump_equal_when_flag_not_set() {
let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, false);
*cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx");
let jump_eq_instr =
Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_eq_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Pcx), initial_pc);
jump_eq_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
}
#[test]
@@ -467,15 +622,17 @@ fn test_halt_instruction() {
let mut cpu = create_test_processor();
assert!(!cpu.halted);
Instruction::Halt.execute(&mut cpu);
Instruction::Halt.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(cpu.halted);
}
#[test]
fn test_nand_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let nand_instr = Instruction::Nand(RTypeArgs::new(
Some(Register::Rg1),
@@ -484,15 +641,20 @@ fn test_nand_instruction() {
None,
));
nand_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), !0b1000);
nand_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1000
);
}
#[test]
fn test_nor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let nor_instr = Instruction::Nor(RTypeArgs::new(
Some(Register::Rg1),
@@ -501,15 +663,20 @@ fn test_nor_instruction() {
None,
));
nor_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), !0b1110);
nor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1110
);
}
#[test]
fn test_xnor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let xnor_instr = Instruction::Xnor(RTypeArgs::new(
Some(Register::Rg1),
@@ -518,6 +685,11 @@ fn test_xnor_instruction() {
None,
));
xnor_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), !0b0110);
xnor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b0110
);
}
+72 -28
View File
@@ -1,5 +1,3 @@
use std::sync::mpsc::Sender;
use crate::emulator::{
system::model::{Command, Running, State},
ui::interface::Component,
@@ -9,19 +7,27 @@ use common::{instructions::Register, prelude::Instruction};
pub struct ControlPanel {
visible: bool,
sender: Sender<Command>,
step_amount_input: String,
step_amount: usize,
}
impl ControlPanel {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
#[allow(clippy::must_use_candidate)]
pub fn new() -> Self {
Self {
visible: false,
sender,
step_amount_input: String::from("1"),
step_amount: 1,
}
}
}
impl Default for ControlPanel {
fn default() -> Self {
Self::new()
}
}
impl Component for ControlPanel {
fn category(&self) -> super::interface::Category {
super::interface::Category::Control
@@ -47,46 +53,76 @@ impl Component for ControlPanel {
.clicked()
{
if state.running == Running::Running {
self.sender.send(Command::Stop).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
state.cmd_sender.send(Command::Stop).unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
} else {
self.sender.send(Command::Start).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
state.cmd_sender.send(Command::Start).unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
}
// Step
if ui.button("Step").clicked() {
self.sender.send(Command::Step).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.cmd_sender
.send(Command::Step(self.step_amount))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
// Resets the emulator and all attached devices
if ui.button("Reset All").clicked() {
self.sender.send(Command::Reset(0)).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.cmd_sender
.send(Command::Reset(0))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
// Resets the emulator and all attached devices
if ui.button("Clear Registers").clicked() {
self.sender.send(Command::Reset(1)).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.cmd_sender
.send(Command::Reset(1))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
// Resets the emulator and all attached devices
if ui.button("Clear RAM").clicked() {
self.sender.send(Command::Reset(2)).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.cmd_sender
.send(Command::Reset(2))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
ui.separator();
state.send(Command::RegisterRequest);
state.send(Command::RunningRequest);
state.send(Command::InstructionCountRequest);
if ui
.text_edit_singleline(&mut self.step_amount_input)
.changed()
{
self.step_amount = if let Ok(amount) = self.step_amount_input.parse() {
amount
} else {
state
.error_log
.push("Unable to parse step amount".to_string());
1
}
}
// Status info
ui.label(format!(
"Status: {}",
@@ -97,17 +133,25 @@ impl Component for ControlPanel {
}
));
let pcx = state.reg_file.get(Register::Pcx);
let pcx = state
.reg_file
.get(Register::Pcx)
.expect("PCX should never be invalid");
let instructions = state.instructions;
ui.label(format!("Instructions: {instructions}"));
ui.label(format!("PC: 0x{pcx:08X}"));
let instruction = Instruction::decode(state.reg_file.get(Register::Cir))
.map_or_else(
|_| "Invalid Instruction".to_string(),
|instruction| instruction.to_string(),
);
let instruction = Instruction::decode(
state
.reg_file
.get(Register::Cir)
.expect("CIR should never be invalid"),
)
.map_or_else(
|_| "Invalid Instruction".to_string(),
|instruction| instruction.to_string(),
);
ui.label(format!("Instruction: {instruction}"));
});
+3 -1
View File
@@ -1,5 +1,5 @@
use crate::emulator::{
system::model::State,
system::model::{Command, State},
ui::interface::{Category, Component},
};
@@ -40,6 +40,8 @@ impl Component for Display {
}
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
state.send(Command::DisplayRequest);
let display: Vec<u8> = state.display_view.clone();
let font_id = FontId::monospace(12.0);
+100 -137
View File
@@ -3,7 +3,6 @@ use std::{
ffi::OsStr,
fs,
path::{Path, PathBuf},
sync::mpsc::Sender,
};
use common::prelude::Instruction;
@@ -19,6 +18,7 @@ use crate::emulator::{
use assembler::prelude::*;
#[derive(Default)]
pub struct Editor {
// editor state
path: Option<PathBuf>,
@@ -41,7 +41,6 @@ pub struct Editor {
// other
visible: bool,
sender: Sender<Command>,
error: Option<String>,
}
@@ -94,14 +93,13 @@ impl Component for Editor {
impl Editor {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
pub const fn new() -> Self {
Self {
path: None,
text: String::new(),
buffer: String::new(),
output: Vec::new(),
unsaved: true,
sender,
cursor_col: 1,
cursor_line: 1,
visible: false,
@@ -199,38 +197,6 @@ impl Editor {
)
});
// if let Some(path) = FileDialog::new()
// .add_filter("Assembly Files or Binaries", &["dsa", "dsb"])
// .add_filter("all", &["*"])
// .set_directory(&work_dir)
// .pick_file()
// {
// match path.extension().and_then(|ext| ext.to_str()) {
// Some("dsb") => {
// let contents = match std::fs::read(&path) {
// Ok(contents) => contents,
// Err(why) => {
// self.error = Some(format!("Failed to read file: {why}"));
// return;
// }
// };
// self.path = Some(path.clone());
// self.output = contents;
// self.unsaved = false;
// self.text = String::from("Loaded Binary File!");
// self.buffer = self.text.clone();
// self.unsaved = false;
// }
// _ => {
// if let Ok(contents) = std::fs::read_to_string(&path) {
// self.path = Some(path.clone());
// self.text.clone_from(&contents);
// self.buffer = contents;
// self.unsaved = false;
// }
// }
// }
if self.save_file_dialog.is_some() {
// TODO: Flash an error stating you can only have one menu open at once.
self.save_file_dialog = None;
@@ -252,119 +218,92 @@ impl Editor {
fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
// Handle open dialog
if let Some(dialog) = &mut self.open_file_dialog {
if dialog.show(ctx).selected() {
if let Some(file) = dialog.path() {
// check if the file is a binary file
if file.extension().is_some_and(|ext| ext == "dsb") {
match std::fs::read(file) {
Ok(content) => {
let mut res = String::new();
for (i, b) in content.iter().enumerate() {
_ = write!(res, "{b:02x}");
if i % 4 == 3 {
res.push('\n');
}
}
self.text = res.clone();
self.buffer = res;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
} else {
match std::fs::read_to_string(file) {
Ok(content) => {
self.text = content.clone();
self.buffer = content;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
}
}
self.open_file_dialog = None;
}
}
// Handle save dialog
if let Some(dialog) = &mut self.save_file_dialog {
if dialog.show(ctx).selected() {
if let Some(file) = dialog.path() {
self.buffer = self.text.clone();
let content = if file.extension().is_some_and(|ext| ext == "dsb") {
let mut res = Vec::new();
for line in self.text.lines() {
for line in line.split_whitespace() {
match u32::from_str_radix(line, 16) {
Ok(num) => res.push(num),
Err(e) => {
self.error =
Some(format!("Failed to parse file: {e}"));
return;
}
if let Some(dialog) = &mut self.open_file_dialog
&& dialog.show(ctx).selected()
{
if let Some(file) = dialog.path() {
// check if the file is a binary file
if file.extension().is_some_and(|ext| ext == "dsb") {
match std::fs::read(file) {
Ok(content) => {
let mut res = String::new();
for (i, b) in content.iter().enumerate() {
_ = write!(res, "{b:02x}");
if i % 4 == 3 {
res.push('\n');
}
}
}
res.into_iter()
.flat_map(u32::to_be_bytes)
.collect::<Vec<u8>>()
} else {
self.text.clone().as_bytes().to_vec()
};
match std::fs::write(file, content) {
Ok(()) => {
self.text = res.clone();
self.buffer = res;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to save file: {e}"));
self.error = Some(format!("Failed to read file: {e}"));
}
}
} else {
match std::fs::read_to_string(file) {
Ok(content) => {
self.text = content.clone();
self.buffer = content;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
}
self.save_file_dialog = None;
}
self.open_file_dialog = None;
}
// Handle save dialog
if let Some(dialog) = &mut self.save_file_dialog
&& dialog.show(ctx).selected()
{
if let Some(file) = dialog.path() {
self.buffer = self.text.clone();
let content = if file.extension().is_some_and(|ext| ext == "dsb") {
let mut res = Vec::new();
for line in self.text.lines() {
for line in line.split_whitespace() {
match u32::from_str_radix(line, 16) {
Ok(num) => res.push(num),
Err(e) => {
self.error =
Some(format!("Failed to parse file: {e}"));
return;
}
}
}
}
res.into_iter()
.flat_map(u32::to_be_bytes)
.collect::<Vec<u8>>()
} else {
self.text.clone().as_bytes().to_vec()
};
match std::fs::write(file, content) {
Ok(()) => {
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to save file: {e}"));
}
}
}
self.save_file_dialog = None;
}
}
// fn open(&mut self) {
// let work_dir = std::env::current_dir().unwrap_or_else(|_| {
// dirs::home_dir().expect(
// "Couldn't get your current working directory or your home directory.",
// )
// });
// if let Some(path) = FileDialog::new()
// .add_filter("Assembly Files or Binaries", &["dsa", "dsb"])
// .add_filter("all", &["*"])
// .set_directory(&work_dir)
// .pick_file()
// {
// if let Ok(contents) = std::fs::read_to_string(&path) {
// self.path = Some(path.clone());
// self.text.clone_from(&contents);
// self.buffer = contents;
// self.unsaved = false;
// }
// std::env::set_current_dir(
// path.parent().expect("A file should be in a directory!"),
// )
// .expect("ERROR: Failed to set current working directory.");
// }
// }
fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
@@ -512,6 +451,29 @@ impl Editor {
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
}
Some("dsc") => {
let output_path = Path::new(path).with_extension("dsa");
if let Err(e) = compiler::compile_file(path, &output_path) {
self.error = Some(format!("Compiler error: {}", e));
}
let mut compiler = CompilerEngine::new();
compiler.start_compilation(&output_path);
// Or block until done
let instructions = match compiler.wait_for_result() {
Ok(instructions) => instructions,
Err(e) => {
self.error = Some(format!("Assembler error: {}", e));
return;
}
};
self.output = instructions
.iter()
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
}
Some("dsb") => {
if let Ok(bytes) = fs::read(path) {
self.output = bytes;
@@ -526,7 +488,7 @@ impl Editor {
}
}
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, ctx: &Context) {
fn render_toolbar(&mut self, state: &State, ui: &mut Ui, ctx: &Context) {
self.handle_file_dialogs(ctx);
ui.horizontal(|ui| {
@@ -567,7 +529,8 @@ impl Editor {
Some("Can't load program at invalid offset!".to_string());
}
self.sender
state
.cmd_sender
.send(Command::Write(self.load_offset, self.output.clone()))
.unwrap_or_else(|_| {
self.error = Some("Failed to send command".to_string());
+8 -3
View File
@@ -1,6 +1,9 @@
use egui::{Context, Ui};
use crate::emulator::{system::model::State, ui::interface::Component};
use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
pub struct History {
visible: bool,
@@ -20,11 +23,13 @@ impl Component for History {
}
fn render(&mut self, state: &mut State, ui: &mut Ui, _ctx: &Context) {
state.send(Command::HistoryRequest);
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(400.0)
.show(ui, |ui| {
if state.persistent.history.is_empty() {
if state.instruction_history.is_empty() {
ui.label(
egui::RichText::new("No output data")
.font(egui::FontId::monospace(12.0))
@@ -40,7 +45,7 @@ impl Component for History {
.show(ui, |ui| {
// Process bytes in chunks of 4
for (idx, instruction) in
state.persistent.history.iter().enumerate()
state.instruction_history.iter().enumerate()
{
ui.label(format!("{idx}: "));
+6 -18
View File
@@ -1,4 +1,4 @@
use crate::emulator::system::model::{Command, PersistentState, Running, State};
use crate::emulator::system::model::{Command, Running, State, StateUpdate};
use std::sync::mpsc::{Receiver, Sender};
pub trait Component {
@@ -34,21 +34,15 @@ impl Category {
}
pub struct EmulatorUI {
pub sender: Sender<Command>,
pub receiver: Receiver<State>,
pub state: State,
pub persistent: PersistentState,
pub components: Vec<Box<dyn Component>>,
}
impl EmulatorUI {
#[must_use]
pub fn new(sender: Sender<Command>, receiver: Receiver<State>) -> Self {
pub fn new(sender: Sender<Command>, receiver: Receiver<StateUpdate>) -> Self {
Self {
sender,
receiver,
state: State::default(),
persistent: PersistentState::default(),
state: State::new(sender, receiver),
components: vec![],
}
}
@@ -56,19 +50,13 @@ impl EmulatorUI {
pub fn add_component(&mut self, component: Box<dyn Component>) {
self.components.push(component);
}
fn update_state(&mut self) {
while let Ok(state) = self.receiver.try_recv() {
self.state = state;
self.persistent.update(&self.state.persistent);
self.state.persistent = self.persistent.clone();
}
}
}
impl eframe::App for EmulatorUI {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.update_state();
if let Err(e) = self.state.update() {
self.state.error_log.push(e.to_string());
}
if self.state.running == Running::Running {
ctx.request_repaint();
+294
View File
@@ -0,0 +1,294 @@
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use common::prelude::Instruction;
use egui::{Context, Ui};
use egui_file::FileDialog;
use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
#[derive(Default)]
pub struct Loader {
path: Option<PathBuf>,
output: Vec<u8>,
load_offset: u32,
offset_str: String,
// file dialogs
open_file_dialog: Option<FileDialog>,
// other
visible: bool,
error: Option<String>,
}
impl Component for Loader {
fn name(&self) -> &'static str {
"Loader"
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn category(&self) -> super::interface::Category {
super::interface::Category::Programming
}
fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) {
ui.vertical(|ui| {
self.render_toolbar(state, ui, ctx);
ui.add_space(4.0); // Add some spacing instead of just a separator
ui.separator();
egui::ScrollArea::vertical()
.auto_shrink([false; 2])
.max_height(ui.available_height() - 100.0)
.show(ui, |ui| {
self.render_output(state, ui, ctx);
});
self.render_bottom_bar(state, ui, ctx);
});
}
}
impl Loader {
#[must_use]
pub const fn new() -> Self {
Self {
path: None,
output: Vec::new(),
visible: false,
load_offset: 0,
offset_str: String::new(),
error: None,
open_file_dialog: None,
}
}
fn filename(&self) -> &str {
if let Some(path) = &self.path {
return path
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
}
"Unnamed!"
}
fn open(&mut self) {
let work_dir = std::env::current_dir().unwrap_or_else(|_| {
dirs::home_dir().expect(
"Couldn't get your current working directory or your home directory.",
)
});
if self.open_file_dialog.is_some() {
// TODO: Flash an error stating you can only have one menu open at once.
self.open_file_dialog = None;
}
if self.open_file_dialog.is_none() {
if let Some(p) = &self.path {
let path = p.parent().map(Path::to_path_buf);
let mut dialog = FileDialog::open_file(path);
dialog.open();
self.open_file_dialog = Some(dialog);
} else {
let mut dialog = FileDialog::open_file(Some(work_dir));
dialog.open();
self.open_file_dialog = Some(dialog);
}
}
}
fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
// Handle open dialog
if let Some(dialog) = &mut self.open_file_dialog
&& dialog.show(ctx).selected()
{
if let Some(file) = dialog.path() {
// check if the file is a binary file
if file.extension().is_some_and(|ext| ext == "dsb") {
match std::fs::read(file) {
Ok(content) => {
self.output = content;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
}
}
self.open_file_dialog = None;
}
}
fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(400.0)
.show(ui, |ui| {
if self.output.is_empty() {
ui.label(
egui::RichText::new("No output data")
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::GRAY),
);
return;
}
egui::Grid::new("output_grid")
.spacing([5.0, 2.0]) // Horizontal and vertical spacing
.num_columns(4)
.striped(false)
.show(ui, |ui| {
// Process bytes in chunks of 4
for (line_num, chunk) in self.output.chunks(4).enumerate() {
let address = line_num * 4;
// Convert chunk to u32 (little-endian)
let mut bytes = [0u8; 4];
for (i, &byte) in chunk.iter().enumerate() {
if i < 4 {
bytes[i] = byte;
}
}
let value = u32::from_be_bytes(bytes);
// Address column
ui.with_layout(
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
ui.set_min_width(80.0);
let style = ui.style_mut();
style.visuals.widgets.inactive.bg_fill =
egui::Color32::from_gray(30);
ui.label(
egui::RichText::new(format!("0x{address:04X}"))
.font(egui::FontId::monospace(12.0)),
);
},
);
// Individual bytes column
let byte_str = chunk
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ");
ui.label(
egui::RichText::new(format!("{byte_str:<11}"))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 200, 255)),
);
// Hex column
ui.label(
egui::RichText::new(format!("0x{value:08X}"))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(255, 200, 200)),
);
// Instruction column
let instruction = Instruction::decode(value).map_or_else(
|_| format!("{value:10}"),
|instruction| instruction.to_string(),
);
ui.label(
egui::RichText::new(instruction)
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 255, 200)),
);
ui.end_row();
}
});
});
}
fn render_bottom_bar(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
ui.horizontal(|ui| {
// error display
ui.label(
egui::RichText::new(self.error.clone().unwrap_or_default())
.color(egui::Color32::RED),
);
});
}
fn render_toolbar(&mut self, state: &State, ui: &mut Ui, ctx: &Context) {
self.handle_file_dialogs(ctx);
ui.horizontal(|ui| {
ui.label(format!("Filename: {}", self.filename()));
});
ui.horizontal(|ui| {
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
ui.spacing_mut().item_spacing.x = 6.0;
// Opens a file
if ui.button("Open").clicked() {
self.open();
}
// Loads the generated binary into the assembler at the provided offset
if ui.button("Load").clicked() {
if self.error.is_some() {
self.error =
Some("Can't load program at invalid offset!".to_string());
}
state
.cmd_sender
.send(Command::Write(self.load_offset, self.output.clone()))
.unwrap_or_else(|_| {
self.error = Some("Failed to send command".to_string());
});
}
// Entry widget to enter a load offset
if ui.text_edit_singleline(&mut self.offset_str).changed() {
if let Some(offset) = parse_address(&self.offset_str) {
self.load_offset = offset;
self.error = None;
} else {
self.error = Some("Invalid offset".to_string());
}
}
});
}
}
fn parse_address(address: &str) -> Option<u32> {
address.strip_prefix("0x").map_or_else(
|| {
address.strip_prefix("0b").map_or_else(
|| {
address.strip_prefix("0o").map_or_else(
|| address.parse::<u32>().ok(),
|oct| u32::from_str_radix(oct, 8).ok(),
)
},
|bin| u32::from_str_radix(bin, 2).ok(),
)
},
|hex| u32::from_str_radix(hex, 16).ok(),
)
}
+24 -20
View File
@@ -1,4 +1,4 @@
use std::{num::ParseIntError, sync::mpsc::Sender};
use std::num::ParseIntError;
use common::prelude::Instruction;
@@ -7,23 +7,22 @@ use crate::emulator::{
ui::interface::Component,
};
#[derive(Default)]
pub struct MemoryInspector {
view_size: u32,
view_addr: u32,
visible: bool,
addr_input: String,
sender: Sender<Command>,
}
impl MemoryInspector {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
pub const fn new() -> Self {
Self {
view_size: 256,
view_addr: 0,
visible: false,
addr_input: String::new(),
sender,
}
}
}
@@ -63,28 +62,26 @@ impl Component for MemoryInspector {
let search_clicked = ui.button("🔍 Search").clicked();
// Handle Enter key in text field
let enter_pressed =
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
let enter_pressed = address_response.lost_focus()
&& ctx.input(|i| i.key_pressed(egui::Key::Enter));
if search_clicked || enter_pressed {
if let Ok(new) = parse_address(&self.addr_input) {
self.view_addr = new;
if let Err(why) = self.sender.send(Command::Read(new, self.view_size)) {
panic!(
"Error sending message across threads -- cannot be recovered: {why}"
)
}
} else {
state.error = Some("Invalid address".to_string());
state.error_log.push("Invalid address".to_string());
}
}
let _ = state
.cmd_sender
.send(Command::MemRequest(self.view_addr, self.view_size));
ui.label("(hex or decimal)");
});
// Show input error if any
if let Some(error) = &state.error {
if let Some(error) = state.error_log.last() {
ui.colored_label(egui::Color32::RED, format!("Error: {error}"));
}
@@ -113,9 +110,12 @@ impl Component for MemoryInspector {
ui.end_row();
// Memory data (8 bytes per row)
for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4)) {
for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4))
{
let row_address = self.view_addr + (row * 4);
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
ui.monospace(format!(
"0x{row_address:08X} ({row_address})"
));
for &byte in chunk {
ui.monospace(format!("{byte:02X}"));
}
@@ -126,12 +126,16 @@ impl Component for MemoryInspector {
}
// combine all 4 bytes in the chunk into a u32
let combined = chunk
.iter()
.fold(0u32, |acc, &byte| (acc << 8) | u32::from(byte));
let combined = chunk.iter().fold(0u32, |acc, &byte| {
(acc << 8) | u32::from(byte)
});
ui.monospace(format!("{combined}"));
ui.monospace(format!("{}", Instruction::decode(combined).unwrap_or(Instruction::Nop)));
ui.monospace(format!(
"{}",
Instruction::decode(combined)
.unwrap_or(Instruction::Nop)
));
ui.end_row();
}
+1
View File
@@ -3,6 +3,7 @@ pub mod display;
pub mod editor;
pub mod history;
pub mod interface;
pub mod loader;
pub mod memory_inspector;
pub mod menu;
pub mod stack_inspector;
+9 -5
View File
@@ -1,4 +1,7 @@
use crate::emulator::{system::model::State, ui::interface::Component};
use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
use common::instructions::Register;
@@ -33,6 +36,8 @@ impl Component for StackInspector {
}
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
state.send(Command::StackRequest);
ui.vertical(|ui| {
ui.heading("Stack Inspector");
egui::ScrollArea::vertical()
@@ -46,7 +51,6 @@ impl Component for StackInspector {
ui.label("Address");
ui.label("Value");
ui.end_row();
for (i, value) in
state.stack_view.chunks(4).take(32).enumerate()
{
@@ -54,9 +58,9 @@ impl Component for StackInspector {
"Could not read 4 byte instruction or data! Something is wrong.",
));
ui.label(format!(
"{} [{}]",
i,
state.reg_file.get(Register::Spr) - i as u32 * 4
"+{} [{}]",
i*4,
state.reg_file.get(Register::Spr).expect("SPR should never be invalid") + i as u32 * 4
));
ui.label(format!("0x{value:08X} ({value})"));
ui.end_row();
+10 -7
View File
@@ -30,7 +30,7 @@ use crate::emulator::{
system::{
emulator::run_emulator,
memory::MainStore,
model::{Command, State},
model::{Command, StateUpdate},
processor::Processor,
},
ui::{
@@ -86,7 +86,7 @@ pub fn android_main(app: AndroidApp) -> Result<(), Box<dyn std::error::Error>> {
pub fn setup_emulator(
cmd_receiver: Receiver<Command>,
state_sender: Sender<State>,
state_sender: Sender<StateUpdate>,
rpc_client: Option<Arc<RpcClient>>,
) {
let main_store = MainStore::new();
@@ -101,22 +101,22 @@ pub fn setup_emulator(
#[must_use]
pub fn setup_ui(
cmd_sender: Sender<Command>,
state_reciever: Receiver<State>,
state_reciever: Receiver<StateUpdate>,
) -> EmulatorUI {
let mut ui = EmulatorUI::new(cmd_sender.clone(), state_reciever);
let mut ui = EmulatorUI::new(cmd_sender, state_reciever);
// Create UI modules.
let control_unit = ControlPanel::new(cmd_sender.clone());
let control_unit = ControlPanel::new();
ui.add_component(Box::new(control_unit));
let mem_inspector = MemoryInspector::new(cmd_sender.clone());
let mem_inspector = MemoryInspector::new();
ui.add_component(Box::new(mem_inspector));
let stack_inspector = StackInspector::new();
ui.add_component(Box::new(stack_inspector));
let editor = Editor::new(cmd_sender);
let editor = Editor::new();
ui.add_component(Box::new(editor));
let display = Display::new();
@@ -125,5 +125,8 @@ pub fn setup_ui(
let history = emulator::ui::history::History::new();
ui.add_component(Box::new(history));
let loader = emulator::ui::loader::Loader::new();
ui.add_component(Box::new(loader));
ui
}
-279
View File
@@ -1,279 +0,0 @@
```rust
// src/assembler/source.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourcePosition {
pub line: u32,
pub column: u32,
pub offset: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceSpan {
pub start: SourcePosition,
pub end: SourcePosition,
pub file_id: u64, // Hash of the file path
}
impl SourceSpan {
pub fn new(start: SourcePosition, end: SourcePosition, file_id: u64) -> Self {
Self { start, end, file_id }
}
pub fn single_char(pos: SourcePosition, file_id: u64) -> Self {
Self {
start: pos,
end: pos,
file_id,
}
}
}
2. Enhanced Token with Source Information
Update the Token type to include source positions:
```rust
// src/assembler/model.rs
pub struct Token {
pub kind: TokenKind,
pub span: SourceSpan,
pub raw: String, // Original source text
}
pub enum TokenKind {
// ... existing variants ...
}
3. Enhanced CodeModule Structure
Enhance the
CodeModule
struct to track source information:
```rust
// src/assembler/mod.rs
pub struct CodeModule {
pub path: PathBuf,
pub hash: u64,
pub source: String,
pub lines: Vec<usize>, // Line start offsets for quick lookup
pub tokens: Vec<Token>,
pub nodes: Vec<Node>,
pub dependencies: Vec<CodeModule>,
}
impl CodeModule {
pub fn new(path: PathBuf, source: String) -> Self {
let hash = quick_hash(&path);
let lines = source.lines()
.scan(0, |offset, line| {
let start = *offset;
*offset += line.len() + 1; // +1 for newline
Some(start)
})
.collect();
Self {
path,
hash,
source,
lines,
tokens: Vec::new(),
nodes: Vec::new(),
dependencies: Vec::new(),
}
}
pub fn position_from_offset(&self, offset: usize) -> (u32, u32) {
match self.lines.binary_search(&offset) {
Ok(line) => (line as u32 + 1, 1),
Err(0) => (1, offset as u32 + 1),
Err(line) => {
let line_start = self.lines[line - 1];
(line as u32, (offset - line_start + 1) as u32)
}
}
}
}
4. Enhanced Lexer with Source Positions
Update the lexer to track source positions:
```rust
// src/assembler/lexer.rs
pub fn lex(module: &mut CodeModule) -> Result<(), AssembleError> {
let source = &module.source;
let mut tokens = Vec::new();
let mut pos = 0;
let mut line_start = 0;
let mut line = 1;
while pos < source.len() {
let c = source[pos..].chars().next().unwrap();
if c == '\n' {
line += 1;
line_start = pos + 1;
pos += 1;
continue;
}
if c.is_whitespace() {
pos += 1;
continue;
}
let token_start = pos;
// ... existing token parsing logic ...
// When creating a token:
let start_pos = SourcePosition {
line,
column: (token_start - line_start + 1) as u32,
offset: token_start,
};
// Update pos based on token length
let token_length = /* calculate token length */;
pos += token_length;
let end_pos = SourcePosition {
line,
column: (pos - line_start + 1) as u32,
offset: pos,
};
tokens.push(Token {
kind: token_kind,
span: SourceSpan::new(start_pos, end_pos, module.hash),
raw: source[token_start..pos].to_string(),
});
}
module.tokens = tokens;
Ok(())
}
5. Enhanced Error Reporting
Create a structured error type with source context:
```rust
// src/assembler/error.rs
#[derive(Debug)]
pub struct AssemblerError {
pub kind: ErrorKind,
pub span: SourceSpan,
pub message: String,
pub context: Vec<String>,
}
impl AssemblerError {
pub fn new(kind: ErrorKind, span: SourceSpan, message: impl Into<String>) -> Self {
Self {
kind,
span,
message: message.into(),
context: Vec::new(),
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context.push(context.into());
self
}
pub fn format(&self, module: &CodeModule) -> String {
let (line, col) = module.position_from_offset(self.span.start.offset);
let line_content = module.source.lines().nth(line as usize - 1).unwrap_or("");
let mut output = format!(
"{}:{}:{}: {}\n",
module.path.display(),
line,
col,
self.message
);
// Add source line with caret
output.push_str(&format!("{}\n", line_content));
output.push_str(&" ".repeat(col as usize - 1));
output.push_str("^\n");
// Add context if any
for ctx in &self.context {
output.push_str(&format!(" = note: {}\n", ctx));
}
output
}
}
6. Integration with Compilation Pipeline
Update the compilation pipeline to use the enhanced types:
```rust
// src/assembler/mod.rs
pub fn assemble(src: &Path) -> Result<Vec<Instruction>, AssemblerError> {
let source = std::fs::read_to_string(src)
.map_err(|e| AssemblerError::io_error(src, e))?;
let mut module = CodeModule::new(src.to_path_buf(), source);
// Lexing
lexer::lex(&mut module)?;
// Parsing
parser::parse(&mut module)?;
// Resolution
resolver::resolve(&mut module)?;
// Code generation
codegen::generate(&module)
}
7. Logging Integration
Enhance the logging system to include source context:
```rust
// src/util/logging.rs
pub trait Loggable {
fn log(&self, level: LogLevel, message: impl std::fmt::Display);
fn log_with_span(&self, level: LogLevel, span: &SourceSpan, message: impl std::fmt::Display);
}
impl Loggable for CodeModule {
fn log_with_span(&self, level: LogLevel, span: &SourceSpan, message: impl std::fmt::Display) {
if span.file_id != self.hash {
if let Some(dep) = self.find_dependency(span.file_id) {
return dep.log_with_span(level, span, message);
}
}
let (line, col) = self.position_from_offset(span.start.offset);
let line_content = self.source.lines().nth(line as usize - 1).unwrap_or("");
log::log!(
level,
"{}:{}:{}: {}\n {}\n {}{}",
self.path.display(),
line,
col,
message,
line_content,
" ".repeat(col as usize - 1),
"^"
);
}
}
8. Usage Example
Here's how you'd use this in practice:
```rust
// In your parser or code that needs to report errors
fn parse_token(&mut self, module: &CodeModule) -> Result<Token, AssemblerError> {
// ...
if !is_valid_token(&token) {
return Err(AssemblerError::new(
ErrorKind::SyntaxError,
token.span,
"Invalid token"
).with_context("Expected a valid instruction or directive"));
}
// ...
}
```
+36 -18
View File
@@ -2,10 +2,38 @@
// a simple brainf##k interpreter,
// because I already wrote a compiler lol.
include print "./lib/print.dsa"
include print "./lib/io/print.dsa"
// "print hello world"
db program: "++++++[>++++++++++++<-]>.>++++++++++[>++++++++++<-]>+.+++++++..+++.>++++[>+++++++++++<-]>.<+++[>----<-]>.<<<<<+++[>+++++<-]>.>>.+++.------.--------.>>+."
db program: "++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++
>++++++++++++++++
>
>+
<<
[
>>
>
>++++++++++
<<
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]
>[<+>-]
>[-]
>>
>++++++++++
<
[->-[>+>>]>[+[-<+>]>+>>]<<<<<]
>[-]
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<.>.
>>[>>+<<-]
>[>+<<+>-]
>[<+>-]
<<<-
]
<<++..."
db error: "Invalid Instruction!"
dw stack: 0x10000
@@ -20,6 +48,7 @@ _init_stack:
start:
// load the start of the program into rg0
lwi program, rg0
lwi data, rg1
// rg0 is our instruction pointer
// rg1 is our data pointer
@@ -40,13 +69,6 @@ loop_start:
// load the current instruction into rg3
ldb rg0, rg3
// pusha 2
// push rg3
// call print::print_byte
// pop zero
// popa 2
// switch on the instruction
// all cases will return to either loop_start or loop_end
cmp rg3, rg8
@@ -68,19 +90,15 @@ loop_start:
cmp rg3, zero
jeq end
// if we get here, we don't know what the instruction is
lwi error, rg0
push rg0
call print::print
pop zero
end:
lwi error, rg2
// if we get here, we don't know what the instruction is
lwi error, rg2
pusha 2
push rg2
call print::print
pop zero
popa 2
end:
hlt
loop_end:
@@ -110,7 +128,7 @@ inc_ptr:
// ------------------------------------------
// decrement the pointer
dec_ptr:
stw rg1, rg2
stw rg2, rg1
subi rg1, 4
ldw rg1, rg2
jmp loop_end
Binary file not shown.
+121
View File
@@ -0,0 +1,121 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-04 01:55:11
// Imports
include arena: "./lib/memory/arena_alloc.dsa"
include print: "./lib/io/print.dsa"
// Globals & Reserved Memory
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 0, rg0
push rg0 // bpr-4: x
subi bpr 4 rg1
lli 512, rg0
push rg1 // bpr-8: y
push rg0 // push arg 0
call arena::new
pop rg2
lli 32, rg0
push rg2 // bpr-12: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena::alloc
pop rg3
pop zero
lli 32, rg0
subi bpr 12 rg2
ldw rg2, rg2 // bpr-20: alloc
push rg2 // bpr-16: alloc
push rg3 // bpr-20: ptr1
push rg0 // push arg 1
push rg2 // push arg 0
call arena::alloc
pop rg4
pop zero
subi bpr 16 rg0
ldw rg0, rg0 // bpr-24: alloc
push rg0 // bpr-24: alloc
push rg4 // bpr-28: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 20 rg0
ldw rg0, rg0 // bpr-28: ptr1
push rg0 // bpr-32: ptr1
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 28 rg0
ldw rg0, rg0 // bpr-36: ptr2
push rg0 // bpr-36: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 36 rg0
ldw rg0, rg0 // bpr-44: ptr2
ldw rg0, rg2
push rg0 // bpr-40: ptr2
push rg2 // push arg 0
call print::print_num
pop zero
call print::print_newline
lli 42, rg2
subi bpr 40 rg5
ldw rg5, rg5 // bpr-48: ptr2
stw rg2, rg5
push rg5 // bpr-44: ptr2
push rg5 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 44 rg2
ldw rg2, rg2 // bpr-52: ptr2
ldw rg2, rg5
push rg2 // bpr-48: ptr2
push rg5 // push arg 0
call print::print_num
pop zero
call print::print_newline
db str_12: "end"
lwi str_12, rg5
push rg5 // push arg 0
call print::println
pop zero
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
+32
View File
@@ -0,0 +1,32 @@
include print: "./lib/io/print.dsa";
include arena: "./lib/memory/arena_alloc.dsa";
fn main() -> u32 {
let x: u32 = 0;
let y: u32 = &x;
let alloc: u32 = arena::new(512);
let ptr1: u32 = arena::alloc(alloc, 32);
let ptr2: u32 = arena::alloc(alloc, 32);
print::print_hex_word(alloc);
print::print_newline();
print::print_hex_word(ptr1);
print::print_newline();
print::print_hex_word(ptr2);
print::print_newline();
print::print_num(*ptr2);
print::print_newline();
*ptr2 = 42;
print::print_hex_word(ptr2);
print::print_newline();
print::print_num(*ptr2);
print::print_newline();
print::println("end");
return 0;
}
+34
View File
@@ -0,0 +1,34 @@
include print "../io/print.dsa"
dw idt: 0xFFFF0000
setup_idt:
push bpr
mov spr, bpr
// load the IDT into the IDR
ldw idt, idr
mov bpr, spr
pop bpr
return
setup_hard_fault_handler:
push bpr
mov spr, bpr
lwi handle_hard_fault, rg0
stw rg0, idr, 4
mov bpr, spr
pop bpr
return
dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!"
handle_hard_fault:
call print::reset
lwi hard_fault_err, rg0
push rg0
call print::print
pop zero
hlt
-18
View File
@@ -1,18 +0,0 @@
fib_n:
pop ret
pop rg0 // n
lli 0, rg1
lli 1, rg2
start:
add rg1, rg2, acc
push rg1
mov rg2, rg1
mov acc, rg2
cmp rg0, zero
dec rg0
jgt start
jmp 4, ret
+331
View File
@@ -0,0 +1,331 @@
// lib:
// print.dsa
// usage:
//
// include print "<relative path>""
//
// usage for print:
// push (register containing address of string)
// push pcx
// jmp print::print
//
// usage for reset:
// push pcx
// jmp print::reset
//
// usage for clear:
// push pcx
// jmp print::clear
//
// usage for print_byte:
// push (register containing byte)
// push pcx
// jmp print::print_byte
//
// usage for print_word:
// push (register containing word)
// push pcx
// jmp print::print_word
//
// usage for print_num:
// push (register containing number to print in decimal)
// push pcx
// jmp print::print_num
//
include maths "../maths/core.dsa"
dw display: 0x20000
dw current: 0x20000
// ------------------------------------------
// prints the string at addr(arg[0]) to the screen. (no trailing whitespace unless explicitly provided)
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_print_loop:
ldb rg0, acc
cmp acc, zero
jeq _end
stb acc, rg1
addi rg0, 1
addi rg1, 1
jmp _print_loop
// ------------------------------------------
println:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_println_loop:
ldb rg0, acc
cmp acc, zero
jeq _println_end
stb acc, rg1
addi rg0, 1
addi rg1, 1
jmp _println_loop
_println_end:
call print_newline
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
// initialise
push bpr
mov spr, bpr
// load byte into acc
ldw bpr, rg0, 8
ldw current, rg1
addi rg1, 3
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
addi rg1, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen in hex.
print_hex_word:
push bpr
mov spr, bpr
ldw current, rg1
ldb bpr, rg0, 8
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 9
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 10
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 11
push rg0
call _print_hex_byte
addi spr, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen in hex.
print_hex_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
call _print_hex_byte
jmp _end
// function body
_print_hex_byte:
// mask to get lower nibble
lli 0xF, rg2
// save rg0 state
push rg0
shr rg0, 4
and rg0, rg2, rg0
call _print_hex_nibble
pop rg0
and rg0, rg2, rg0
call _print_hex_nibble
return
// print a hex digit
_print_hex_nibble:
lli 10, rg3
cmp rg0, rg3
jlt _print_hex_nibble_number
addi rg0, 0x37, rg0
stb rg0, rg1
addi rg1, 1
return
// helper function.
_print_hex_nibble_number:
addi rg0, 0x30, rg0
stb rg0, rg1
addi rg1, 1
return
// ------------------------------------------
// print whitespace
print_whitespace:
push bpr
mov spr, bpr
ldw current, rg1
lli 0x20, rg0
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// print newline
print_newline:
push bpr
mov spr, bpr
// load variables into registers
ldw display, rg0
ldw current, rg1
// get the offset from the display base
sub rg1, rg0, rg0
lwi 80, rg2
pusha 3
push rg0
push rg2
call maths::divmod
pop zero // result
pop rg3 // remainder
popa 3
sub rg1, rg3, rg2
addi rg2, 80, rg1
// _end saves the display state
jmp _end
// ------------------------------------------
// prints arg[0] as a decimal number to the screen.
print_num:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load number to print
lli 0, rg5 // rg5 = digit counter
// check if number is zero
cmp rg0, zero
jne _print_num_extract_digits
// special case: print '0' for zero
lli 0x30, rg6
push rg6 // push digit to stack buffer
lli 1, rg5 // we have 1 digit
jmp _print_num_output
_print_num_extract_digits:
// divide by 10 repeatedly to get digits
cmp rg0, zero
jeq _print_num_output
// call divmod(rg0, 10)
push rg0 // dividend
lli 10, rg1
push rg1 // divisor (10)
call maths::divmod
pop rg0 // quotient (continue dividing this)
pop rg1 // remainder (the digit)
// convert digit to ASCII and push to stack buffer
addi rg1, 0x30, rg6 // convert to ASCII
push rg6 // push digit to stack
inc rg5 // increment digit counter
jmp _print_num_extract_digits
_print_num_output:
// now print digits (pop them off in reverse order)
ldw current, rg1 // get display pointer
_print_num_output_loop:
// check if we've printed all digits
cmp rg5, zero
jeq _print_num_done
// pop digit and print it
pop rg6
stb rg6, rg1
addi rg1, 1
dec rg5
jmp _print_num_output_loop
_print_num_done:
jmp _end
// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
push bpr
mov spr, bpr
ldw display, rg1
jmp _end
// ------------------------------------------
// clears the screen
clear:
push bpr
mov spr, bpr
// display size = 2000 bytes / 500 words
lli 500 rg0
ldw display, rg1
_clear_loop:
dec rg0
stw zero, rg1
addi rg1, 4
cmp rg0, zero
jgt _clear_loop
jmp _end
// ------------------------------------------
// return
_end:
stw rg1, current
mov bpr, spr
pop bpr
return
+104
View File
@@ -0,0 +1,104 @@
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 2
ldw bpr, rg1, 12 // load op 1
lwi 0, rg2 // initialise rg2 to zero
_multiply_loop:
add rg2, rg0, rg2
dec rg1
cmp rg1, zero
jgt _multiply_loop
_multiply_end:
stw rg2, bpr, 8
mov bpr, spr
pop bpr
return
divmod:
push bpr
mov spr, bpr
ldw bpr, rg1, 8 // load op 2
ldw bpr, rg0, 12 // load op 1
lli 0, rg3
_divmod_loop:
cmp rg0, rg1
jlt _divmod_end
sub rg0, rg1, rg0
inc rg3
jmp _divmod_loop
_divmod_end:
// store div in first arg
// store mod in second arg
stw rg3, bpr, 8
stw rg0, bpr, 12
mov bpr, spr
pop bpr
return
// multiply.dsa - improved version
// Multiplies two 32-bit numbers using shift-and-add
//
// Usage:
// push operand2 (multiplier)
// push operand1 (multiplicand)
// call multiply::multiply
// pop result
// pop zero (discard second argument)
new_multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // rg0 = multiplicand
ldw bpr, rg1, 12 // rg1 = multiplier
lli 0, rg2 // rg2 = result (accumulator)
lli 32, rg3 // rg3 = bit counter
mult_loop:
// Check if lowest bit of multiplier is 1
lli 1, acc
and rg1, acc, acc // acc = rg1 & 1
cmp acc, zero
jeq skip_add // if (rg1 & 1) == 0, skip addition
// Add multiplicand to result
add rg2, rg0, rg2
skip_add:
shl rg0, 1 // shift multiplicand left
shr rg1, 1 // shift multiplier right
dec rg3
cmp rg3, zero
jgt mult_loop
stw rg2, bpr, 8 // store result
mov bpr, spr
pop bpr
return
+24
View File
@@ -0,0 +1,24 @@
include print "../io/print.dsa"
fib_n:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load arg
lwi 0, rg1
lwi 1, rg2
_start:
add rg1, rg2, rg3
mov rg2, rg1
mov rg3, rg2
dec rg0
cmp rg0, zero
jgt _start
stw rg3, bpr, 8
mov bpr, spr
pop bpr
return
+100
View File
@@ -0,0 +1,100 @@
dw heap_start: 196608
dw heap_end: 262144
dw heap_current: 196608
new:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 12, rg1
add rg0, rg1, rg2
ldw heap_current, rg1
add rg1, rg2, rg3
ldw heap_end, rg4
cmp rg3, rg4
lli 0, rg5
jle _cmp_end_2
lli 1, rg5
_cmp_end_2:
cmp rg5, zero
jeq _else_4
_then_3:
lli 0, rg4
stw rg4, bpr, 8
jmp _ret
jmp _end_5
_else_4:
nop
_end_5:
lli 12, rg4
add rg1, rg4, rg5
add rg1, rg2, rg4
stw rg5, rg1
lli 4, rg6
add rg1, rg6, rg7
stw rg5, rg7
lli 8, rg6
add rg1, rg6, rg7
stw rg4, rg7
stw rg3, heap_current
stw rg1, bpr, 8
jmp _ret
alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw bpr, rg1, 12
lli 4, rg2
add rg0, rg2, rg3
ldw rg3, rg2
lli 8, rg3
add rg0, rg3, rg4
ldw rg4, rg3
add rg2, rg1, rg4
cmp rg4, rg3
lli 0, rg5
jle _cmp_end_6
lli 1, rg5
_cmp_end_6:
cmp rg5, zero
jeq _else_8
_then_7:
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
jmp _end_9
_else_8:
nop
_end_9:
lli 4, rg5
add rg0, rg5, rg6
stw rg4, rg6
stw rg2, bpr, 8
jmp _ret
destroy:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 0, rg1
stw rg1, bpr, 8
jmp _ret
reset_all:
push bpr
mov spr, bpr
ldw heap_start, rg0
stw rg0, heap_current
lli 0, rg0
stw rg0, bpr, 8
jmp _ret
_ret:
mov bpr, spr
pop bpr
return
+77
View File
@@ -0,0 +1,77 @@
// Arena Allocator
// Supports multiple arenas that can be destroyed independently
// Much more practical than a simple bump allocator
// Global heap management
static heap_start: u32 = 0x30000;
static heap_end: u32 = 0x40000;
static heap_current: u32 = 0x30000;
// Arena structure (stored at the start of each arena):
// [0-3]: start_address (u32)
// [4-7]: current_position (u32)
// [8-11]: end_address (u32)
// Total header size: 12 bytes
// Create a new arena with given size
// Returns pointer to arena handle (or 0 if failed)
fn arena_create(size: u32) -> u32 {
let total_size: u32 = size + 12;
let arena_ptr: u32 = heap_current;
let new_current: u32 = arena_ptr + total_size;
// Check if we have space
if new_current > heap_end {
return 0;
}
// Calculate arena data region
let data_start: u32 = arena_ptr + 12;
let data_end: u32 = arena_ptr + total_size;
// Initialize arena header
// Note: In real implementation, you'd use pointer writes here
// For now, using placeholder comments:
*arena_ptr = data_start; // start_address
*(arena_ptr + 4) = data_start; // current_position
*(arena_ptr + 8) = data_end; // end_address
heap_current = new_current;
return arena_ptr;
}
// Allocate from an arena
// Returns pointer to allocated memory (or 0 if failed)
fn arena_alloc(arena: u32, size: u32) -> u32 {
// Read current position from arena
let current: u32 = *(arena + 4);
let end: u32 = *(arena + 8);
let new_current: u32 = current + size;
// Check if arena has space
if new_current > end {
return 0;
}
// Update current position in arena
*(arena + 4) = new_current;
return current;
}
// Destroy an arena (in bump allocator, this is a no-op)
// In a real allocator, you'd mark the memory as free
fn arena_destroy(arena: u32) {
// In a true allocator, mark memory as reusable
// For bump allocator, we can't reclaim memory
// unless we destroy ALL arenas and reset
return 0;
}
// Reset entire heap (destroys ALL arenas)
fn reset_all() {
heap_current = heap_start;
return 0;
}
-30
View File
@@ -1,30 +0,0 @@
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 1
ldw bpr, rg1, 12 // load op 2
start:
add acc, rg0, acc
dec rg1
cmp rg1, zero
jgt start
end:
mov bpr, spr
pop bpr
return
-115
View File
@@ -1,115 +0,0 @@
// lib:
// print.dsa
// usage:
//
// include print "<relative path>""
//
// usage for print:
// push (register containing address of string)
// push pcx
// jmp print::print
//
// usage for reset:
// push pcx
// jmp print::reset
//
// usage for clear:
// push pcx
// jmp print::clear
//
// usage for print_byte:
// push (register containing byte)
// push pcx
// jmp print::print_byte
//
// usage for print_word:
// push (register containing word)
// push pcx
// jmp print::print_word
//
dw display: 0x20000
dw current: 0x20000
// ------------------------------------------
// prints the string at addr(arg[0]) to the screen.
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_print_loop:
ldb rg0, acc
stb acc, rg1
addi rg0, 1
addi rg1, 1
cmp acc, zero
jne _print_loop
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
// initialise
push bpr
mov spr, bpr
// load byte into acc
ldw bpr, rg0, 8
ldw current, rg1
stw rg0, rg1
addi rg1, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
push bpr
mov spr, bpr
ldw display, rg1
jmp _end
// ------------------------------------------
// clears the screen
clear:
push bpr
mov spr, bpr
// display size = 2000 bytes / 500 words
lli 500 rg0
ldw display, rg1
_clear_loop:
dec rg0
stw zero, rg1
addi rg1, 4
cmp rg0, zero
jgt _clear_loop
jmp _end
// ------------------------------------------
// return
_end:
stw rg1, current
mov bpr, spr
pop bpr
return
+51
View File
@@ -0,0 +1,51 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-04 01:44:06
// Imports
include print: "./lib/io/print.dsa"
include fib: "./lib/maths/fib.dsa"
// Globals & Reserved Memory
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 6, rg0
push rg0 // bpr-4: x
push rg0 // push arg 0
call fib::fib_n
pop rg1
push rg1 // bpr-8: y
push rg1 // push arg 0
call print::print_num
pop zero
jmp _ret
+9
View File
@@ -0,0 +1,9 @@
include print: "./lib/io/print.dsa";
include fib: "./lib/maths/fib.dsa";
fn main() -> u32 {
let x: u32 = 6;
let y: u32 = fib::fib_n(x);
print::print_num(y);
}
+214
View File
@@ -0,0 +1,214 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-03 23:37:16
// Imports
include print: "./lib/io/print.dsa"
// Globals & Reserved Memory
dw heap_start: 196608
dw heap_end: 262144
dw heap_current: 196608
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 0, rg0
push rg0 // bpr-4: x
subi bpr 4 rg1
lli 512, rg0
push rg1 // bpr-8: y
push rg0 // push arg 0
call arena_create
pop rg2
lli 32, rg0
push rg2 // bpr-12: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena_alloc
pop rg3
pop zero
lli 32, rg0
subi bpr 12 rg2
ldw rg2, rg2 // bpr-20: alloc
push rg3 // bpr-16: ptr1
push rg2 // bpr-20: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena_alloc
pop rg4
pop zero
subi bpr 20 rg0
ldw rg0, rg0 // bpr-28: alloc
push rg4 // bpr-24: ptr2
push rg0 // bpr-28: alloc
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 16 rg0
ldw rg0, rg0 // bpr-24: ptr1
push rg0 // bpr-32: ptr1
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 24 rg0
ldw rg0, rg0 // bpr-32: ptr2
push rg0 // bpr-36: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 36 rg0
ldw rg0, rg0 // bpr-44: ptr2
ldw rg0, rg2
push rg0 // bpr-40: ptr2
push rg2 // push arg 0
call print::print_num
pop zero
call print::print_newline
lli 42, rg2
subi bpr 40 rg5
ldw rg5, rg5 // bpr-48: ptr2
stw rg2, rg5
push rg5 // bpr-44: ptr2
push rg5 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 44 rg2
ldw rg2, rg2 // bpr-52: ptr2
ldw rg2, rg5
push rg2 // bpr-48: ptr2
push rg5 // push arg 0
call print::print_num
pop zero
call print::print_newline
db str_1: "end"
lwi str_1, rg5
push rg5 // push arg 0
call print::println
pop zero
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
arena_create:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 12, rg1
add rg0, rg1, rg2
ldw heap_current, rg1
add rg1, rg2, rg3
ldw heap_end, rg4
cmp rg3, rg4
lli 0, rg5
jle _cmp_end_2
lli 1, rg5
_cmp_end_2:
cmp rg5, zero
jeq _else_4
_then_3:
lli 0, rg4
stw rg4, bpr, 8
jmp _ret
jmp _end_5
_else_4:
nop
_end_5:
lli 12, rg4
add rg1, rg4, rg5
add rg1, rg2, rg4
stw rg5, rg1
lli 4, rg6
add rg1, rg6, rg7
stw rg5, rg7
lli 8, rg6
add rg1, rg6, rg7
stw rg4, rg7
stw rg3, heap_current
stw rg1, bpr, 8
jmp _ret
arena_alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw bpr, rg1, 12
lli 4, rg2
add rg0, rg2, rg3
ldw rg3, rg2
lli 8, rg3
add rg0, rg3, rg4
ldw rg4, rg3
add rg2, rg1, rg4
cmp rg4, rg3
lli 0, rg5
jle _cmp_end_6
lli 1, rg5
_cmp_end_6:
cmp rg5, zero
jeq _else_8
_then_7:
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
jmp _end_9
_else_8:
nop
_end_9:
lli 4, rg5
add rg0, rg5, rg6
stw rg4, rg6
stw rg2, bpr, 8
jmp _ret
arena_destroy:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 0, rg1
stw rg1, bpr, 8
jmp _ret
reset_all:
push bpr
mov spr, bpr
ldw heap_start, rg0
stw rg0, heap_current
lli 0, rg0
stw rg0, bpr, 8
jmp _ret
-18
View File
@@ -1,18 +0,0 @@
include print "./lib/print.dsa"
dw stack: 0x10000
db string: "Hello world"
init:
// set up a stack.
ldw stack, bpr
mov bpr, spr
start:
lwi string, rg1
push rg1
call print::print
pop rg1
hlt
+80
View File
@@ -0,0 +1,80 @@
include print "./lib/io/print.dsa"
dw idt: 0xFFFF0000
dw stack: 0x10000
init:
// setup interrupt handlers
ldw idt, idr
lwi handle_hard_fault, rg0
stw rg0, idr, 4
// set up a stack.
ldw stack, bpr
mov bpr, spr
db string: "I won, the game!"
db hexbyte: 0xab
dw hexword: 0x1234abcd
db replace: "I lost"
start:
// test print string
lwi string, rg0
push rg0
call print::print
pop zero
// test print hex byte.
ldb hexbyte, rg0
push rg0
call print::print_hex_byte
pop zero
// test print hex word.
ldw hexword, rg0
push rg0
call print::print_hex_word
pop zero
// test print char
lli 0x40, rg0 // print @
push rg0
call print::print_byte
pop zero
// test newline
call print::print_newline
lwi string rg0
push rg0
call print::print
// test print word
lwi 0x31323334, rg0 // print 1234
push rg0
call print::print_word
pop zero
// test reset cursor pos
call print::reset
// test print string at reset pos
lwi replace, rg0
push rg0
call print::print
pop zero
hlt
// fault handler in case we fail DSA.
dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!"
handle_hard_fault:
call print::clear
call print::reset
lwi hard_fault_err, rg0
push rg0
call print::print
pop zero
hlt
Binary file not shown.
+875
View File
@@ -0,0 +1,875 @@
# DSA Project Roadmap & Task Breakdown
> **Damn Simple Architecture** — Full ecosystem development plan including emulator, assembler, compiler, debugger, and tooling infrastructure.
---
## Table of Contents
1. [Phase 1: Foundation & Core Infrastructure](#phase-1-foundation--core-infrastructure)
- [1.1 Binary Format & Linking System](#11-binary-format--linking-system)
- [1.2 Assembler Rewrite](#12-assembler-rewrite)
- [1.3 Documentation Updates](#13-documentation-updates)
2. [Phase 2: Compiler Development](#phase-2-compiler-development)
- [2.1 Language Design & Implementation](#21-language-design--implementation)
- [2.2 Standard Library](#22-standard-library)
3. [Phase 3: Build System & Package Management](#phase-3-build-system--package-management)
- [3.1 Build System](#31-build-system)
- [3.2 Package Management System](#32-package-management-system)
4. [Phase 4: Debugger & Development Tools](#phase-4-debugger--development-tools)
- [4.1 Debug Symbol System](#41-debug-symbol-system)
- [4.2 Debugger Implementation](#42-debugger-implementation)
- [4.3 Enhanced Editor Integration](#43-enhanced-editor-integration)
5. [Phase 5: Integration & Polish](#phase-5-integration--polish)
6. [Phase 6: Future Enhancements (NTH)](#phase-6-future-enhancements-nth)
7. [Summary Timeline](#summary-timeline)
8. [Critical Path](#critical-path)
9. [Recommended Work Order](#recommended-work-order)
---
## Phase 1: Foundation & Core Infrastructure
**Estimated Duration: 34 weeks**
---
### 1.1 Binary Format & Linking System
> **Priority: CRITICAL** — Everything depends on this.
> **Total Estimate: 1.5 weeks**
---
#### 1.1.1 Design New Binary Format Specification
**Estimate: 2 days**
**Dependencies:** None
**Deliverable:** `docs/binary-format-spec.md`
- [ ] Research existing object file formats (ELF, COFF, Mach-O) for inspiration
- [ ] Design `.dsb` object file format specification
- [ ] Symbol table structure
- [ ] Relocation table format
- [ ] Section definitions (code, data, rodata, bss)
- [ ] Debug information structure
- [ ] Metadata headers
- [ ] Design `.dse` executable format specification
- [ ] Entry point definition
- [ ] Memory layout requirements
- [ ] Linking metadata
- [ ] Document format specifications in markdown
- [ ] Create format version strategy for future compatibility
---
#### 1.1.2 Implement DSB Object File Writer
**Estimate: 3 days**
**Dependencies:** 1.1.1
**Deliverable:** `dsa-binary-format` crate v0.1.0
- [ ] Create new crate: `dsa-binary-format`
- [ ] Implement object file structures
- [ ] Header structure
- [ ] Symbol table builder
- [ ] Section manager
- [ ] Relocation entry creator
- [ ] Write serialization logic
- [ ] Add validation and error handling
- [ ] Write unit tests for each structure
- [ ] Integration tests for complete object files
---
#### 1.1.3 Build Linker Program
**Estimate: 4 days**
**Dependencies:** 1.1.2
**Deliverable:** `dsa-link` executable
- [ ] Create new crate: `dsa-linker`
- [ ] Implement symbol resolution
- [ ] Global symbol table
- [ ] Symbol conflict detection
- [ ] Weak symbol handling
- [ ] Implement relocation processing
- [ ] Address calculation
- [ ] Patch generation
- [ ] Cross-section references
- [ ] Build executable generator
- [ ] Combine sections
- [ ] Generate final memory layout
- [ ] Write `.dse` output
- [ ] Add linker script support (basic)
- [ ] Comprehensive error messages
- [ ] Test suite with complex linking scenarios
---
### 1.2 Assembler Rewrite
> **Priority: HIGH** — Required for all compiled code.
> **Total Estimate: 1.5 weeks**
---
#### 1.2.1 Assembler Architecture Design
**Estimate: 1 day**
**Dependencies:** 1.1.1
**Deliverable:** `docs/assembler-architecture.md`
- [ ] Design multi-pass architecture
- [ ] Pass 1: Symbol collection
- [ ] Pass 2: Macro expansion
- [ ] Pass 3: Code generation
- [ ] Pass 4: Relocation generation
- [ ] Plan error handling strategy
- [ ] Design threading model for parallel file processing
- [ ] Define module/import resolution system
- [ ] Plan integration points with DSC compiler
---
#### 1.2.2 Implement Core Assembler
**Estimate: 5 days**
**Dependencies:** 1.1.2, 1.2.1
**Deliverable:** `dsa-asm` executable v2.0.0
- [ ] Create new crate: `dsa-assembler-ng` (next-gen)
- [ ] Implement lexer with better error recovery
- [ ] Build parser with detailed error messages
- [ ] Instruction parsing
- [ ] Directive handling
- [ ] Macro system
- [ ] Include resolution
- [ ] Symbol table management
- [ ] Code generator outputting to DSB format
- [ ] Multi-threading for file parsing
- [ ] Comprehensive test suite
- [ ] Error message testing
---
#### 1.2.3 Import System & DSC Integration
**Estimate: 2 days**
**Dependencies:** 1.2.2, 2.1.2
**Deliverable:** Working import system
- [ ] Design import protocol between DSC and assembler
- [ ] Implement symbol table merging
- [ ] Handle pre-compiled object imports
- [ ] Test DSC → Assembly → Object pipeline
- [ ] Document integration process
---
### 1.3 Documentation Updates
> **Priority: MEDIUM** — Can be done alongside development.
> **Total Estimate: 3 days (distributed)**
---
#### 1.3.1 Update Assembly Documentation
**Estimate: 1 day**
**Dependencies:** 1.2.2
**Deliverable:** Updated `docs/dsa-assembly-reference.md`
- [ ] Review all instruction documentation
- [ ] Document new pseudo-instructions
- [ ] Update calling convention docs
- [ ] Add examples for new features
- [ ] Document assembler directives
- [ ] Macro system documentation
---
#### 1.3.2 Architecture Documentation
**Estimate: 1 day**
**Dependencies:** None (can start anytime)
**Deliverable:** `docs/dsa-architecture.md`
- [ ] Document ISA specification
- [ ] Memory model documentation
- [ ] Interrupt handling
- [ ] Hardware peripheral specs
- [ ] Timing/performance characteristics
---
#### 1.3.3 Build Tools Documentation
**Estimate: 1 day**
**Dependencies:** 1.2.2, 1.1.3, 3.1.2
**Deliverable:** `docs/build-tools-guide.md`
- [ ] Assembler usage guide
- [ ] Linker usage guide
- [ ] Build system guide
- [ ] Tutorial: Building a simple program
- [ ] Tutorial: Multi-file projects
---
## Phase 2: Compiler Development
**Estimated Duration: 34 weeks**
---
### 2.1 Language Design & Implementation
> **Priority: HIGH** — Core functionality.
> **Total Estimate: 2.5 weeks**
---
#### 2.1.1 Language Syntax Design
**Estimate: 2 days**
**Dependencies:** None
**Deliverable:** `docs/language-spec.md`
- [x] Define syntax goals (simplicity, systems programming)
- [ ] Design type system
- [x] Primitive types
- [x] Pointers/references
- [ ] Structs
- [ ] Arrays
- [x] Function types
- [x] Control flow syntax
- [x] Function declaration syntax
- [x] Module/import system
- [x] Operator precedence
- [ ] Write EBNF grammar
- [x] Create example programs
---
#### 2.1.2 Lexer & Parser Implementation
**Estimate: 4 days**
**Dependencies:** 2.1.1
**Deliverable:** Parser in `dsc-compiler` crate
- [x] Adapt existing C lexer to new syntax
- [ ] Implement new parser for designed syntax
- [ ] Array syntax
- [ ] Struct syntax
- [x] Pointer syntax
- [ ] Namespaced call syntax
- [x] AST node definitions
- [ ] Error recovery mechanisms
- [ ] Comprehensive parser tests
- [ ] Syntax error message quality testing
- [ ] Implement C frontend by moving lexer/parser from `c_compiler` to the new `compiler` project structure
- [ ] Evaluate possible memory management strategies (e.g., keep all variables on the stack vs spill only when calling functions)
---
#### 2.1.3 Code Generation Improvements
**Estimate: 5 days**
**Dependencies:** 2.1.2, 1.2.2
**Deliverable:** Working code generator
- [x] Review and fix existing codegen issues
- [ ] Implement missing language features
- [ ] Structs
- [ ] Arrays
- [x] Pointers/memory operations
- [ ] For loops
- [ ] Switch statements
- [ ] Break/continue
- [ ] Optimize register allocation further
- [x] Implement proper function calling conventions
- [ ] Add constant folding optimization
- [ ] Dead code elimination
- [ ] Test each feature thoroughly
---
#### 2.1.4 Type Checking & Semantic Analysis
**Estimate: 3 days**
**Dependencies:** 2.1.2
**Deliverable:** Type checker integrated in compiler
- [ ] Implement type checker
- [ ] Symbol table for scoping
- [ ] Type inference where applicable
- [ ] Const checking
- [ ] Definite assignment analysis
- [ ] Comprehensive semantic error messages
- [ ] Test suite for type errors
---
### 2.2 Standard Library
> **Priority: MEDIUM** — Needed for useful programs.
> **Total Estimate: 1 week**
---
#### 2.2.1 Core Runtime Library (in Assembly)
**Estimate: 3 days**
**Dependencies:** 1.2.2
**Deliverable:** `lib/runtime/` directory
- [ ] Memory allocation (malloc/free)
- [ ] String operations
- [ ] Math functions
- [x] Multiply
- [ ] Divide (fix as very slow and broken)
- [ ] I/O functions (improved print, read)
- [x] Print number
- [x] Print hex value
- [x] Print word
- [x] Print byte
- [x] Print from string ptr
- [x] Print whitespace and newline
- [x] Reset display
- [x] Reset cursor
- [ ] System call interface
- [ ] Tests for each function
---
#### 2.2.2 Standard Library (in DSC)
**Estimate: 2 days**
**Dependencies:** 2.1.3, 2.2.1
**Deliverable:** `lib/std/` directory
- [ ] String module
- [ ] Collections (array utilities, maybe simple list)
- [ ] File I/O module
- [ ] Math utilities
- [ ] Tests and examples
---
## Phase 3: Build System & Package Management
**Estimated Duration: 23 weeks**
---
### 3.1 Build System
> **Priority: HIGH** — Required for complex projects.
> **Total Estimate: 1.5 weeks**
---
#### 3.1.1 Build System Design
**Estimate: 1 day**
**Dependencies:** None
**Deliverable:** `docs/build-system-design.md`
- [ ] Define project structure conventions
- [ ] Design build manifest format (`dsa-project.toml` or similar)
- [ ] Dependency resolution strategy
- [ ] Build cache design
- [ ] Incremental build strategy
- [ ] Multi-target support
---
#### 3.1.2 Build Tool Implementation
**Estimate: 5 days**
**Dependencies:** 3.1.1, 1.2.2, 1.1.3, 2.1.3
**Deliverable:** `dsa-build` executable
- [ ] Create crate: `dsa-build`
- [ ] Manifest parser
- [ ] Dependency graph builder
- [ ] Task orchestrator
- [ ] Compilation tasks
- [ ] Assembly tasks
- [ ] Linking tasks
- [ ] Build cache implementation
- [ ] Parallel build support
- [ ] Clean, rebuild commands
- [ ] Watch mode for development
- [ ] Comprehensive tests
---
#### 3.1.3 Project Management Commands
**Estimate: 2 days**
**Dependencies:** 3.1.2
**Deliverable:** Enhanced `dsa-build` with project management
- [ ] `dsa new <project>` — Create new project
- [ ] `dsa init` — Initialize in existing directory
- [ ] `dsa add <dependency>` — Add dependency
- [ ] Binary vs library project types
- [ ] Template system for project scaffolding
- [ ] Documentation for each command
---
### 3.2 Package Management System
> **Priority: MEDIUM** — Enables code sharing.
> **Total Estimate: 1.5 weeks**
---
#### 3.2.1 Package Registry Design
**Estimate: 2 days**
**Dependencies:** 3.1.1
**Deliverable:** `docs/package-registry-design.md`
- [ ] Decide: Git monorepo vs custom hosting
- [ ] Design package naming conventions
- [ ] Version resolution strategy (semver)
- [ ] Package manifest format
- [ ] Security considerations
- [ ] Package storage format (source/binary/both)
- [ ] API design for registry server
---
#### 3.2.2 Local Package Manager Tool
**Estimate: 4 days**
**Dependencies:** 3.2.1, 3.1.2
**Deliverable:** `dsa-pkg` tool integrated with `dsa-build`
- [ ] Create crate: `dsa-pkg`
- [ ] Package index synchronization
- [ ] Dependency resolver
- [ ] Package download/cache system
- [ ] Integration with build system
- [ ] Commands:
- [ ] `dsa install <package>`
- [ ] `dsa publish`
- [ ] `dsa search <query>`
- [ ] `dsa update`
- [ ] Lock file generation
- [ ] Test with mock registry
---
#### 3.2.3 Package Registry Implementation
**Estimate: 3 days**
**Dependencies:** 3.2.1
**Deliverable:** Package registry (URL or repo)
- [ ] If **Git monorepo** approach:
- [ ] Set up repository structure
- [ ] CI/CD for validation
- [ ] Submission process
- [ ] Package browser website
- [ ] If **custom hosting**:
- [ ] Simple web server (Rust + Axum/Actix)
- [ ] Package upload API
- [ ] Package search API
- [ ] Basic web UI
- [ ] Database for metadata
- [ ] Documentation for publishing
---
## Phase 4: Debugger & Development Tools
**Estimated Duration: 34 weeks**
---
### 4.1 Debug Symbol System
> **Priority: HIGH** — Foundation for debugging.
> **Total Estimate: 1 week**
---
#### 4.1.1 Debug Symbol Format Design
**Estimate: 1 day**
**Dependencies:** 1.1.1
**Deliverable:** `docs/debug-symbol-format.md`
- [ ] Design symbol table format
- [ ] Function addresses → names
- [ ] Line number → address mapping
- [ ] Variable location information
- [ ] Type information
- [ ] Define symbol table file format
- [ ] Plan for embedding in DSE/DSB files
---
#### 4.1.2 Symbol Generation in Tools
**Estimate: 3 days**
**Dependencies:** 4.1.1, 1.2.2, 2.1.3
**Deliverable:** Debug symbols in build output
- [ ] Modify assembler to emit debug symbols
- [ ] Modify compiler to emit debug symbols
- [ ] Source file/line tracking
- [ ] Variable scope tracking
- [ ] Linker merges debug symbols
- [ ] Test symbol generation pipeline
---
#### 4.1.3 Symbol Table Loader in Emulator
### Pre-Debugger Editor Integration Tasks
- **Integrate compiler into editor**
- Add a build command that invokes the full compiler pipeline (lexer → parser → codegen).
- Show compilation output and errors in the console panel.
- **DSC language support**
- Enable syntax highlighting and autocompletion for DSC files within the editor.
- Provide a dedicated “Build DSC” command that uses the integrated compiler.
- **Editor diagnostics**
- Wire compiler error messages to the editors gutter so users can click to jump to source lines.
**Estimate: 2 days**
**Dependencies:** 4.1.2
**Deliverable:** Symbol loading in emulator crate
- [ ] Implement symbol table parser
- [ ] Build address → symbol lookup (HashMap)
- [ ] Build symbol → address lookup
- [ ] Memory efficient storage
- [ ] Tests for symbol resolution
---
### 4.2 Debugger Implementation
> **Priority: HIGH** — Major productivity boost.
> **Total Estimate: 2 weeks**
---
#### 4.2.1 Core Debugger Features
**Estimate: 5 days**
**Dependencies:** 4.1.3
**Deliverable:** Debugger backend
- [ ] Execution control
- [ ] Step instruction
- [ ] Step over function calls
- [ ] Continue to breakpoint
- [ ] Run to cursor
- [ ] Breakpoint system
- [ ] Address breakpoints
- [ ] Conditional breakpoints
- [ ] Watchpoints (memory access)
- [ ] Register inspection
- [ ] Memory inspection
- [ ] Stack trace generation
- [ ] Test debugger commands
---
#### 4.2.2 Disassembler with Symbol Resolution
**Estimate: 3 days**
**Dependencies:** 4.1.3
**Deliverable:** Enhanced disassembler
- [ ] Instruction decoder
- [ ] Format with labels instead of addresses
- [ ] Show function names at call sites
- [ ] Inline comments with variable names
- [ ] Color coding for instruction types
- [ ] Tests for disassembly output
---
#### 4.2.3 Pseudo-Instruction Decompiler
> ⚠️ **COMPLEX TASK** — Separate pass to decompile assembly into readable pseudo-instructions.
**Estimate: 4 days**
**Dependencies:** 4.2.2
**Deliverable:** Pseudo-instruction view mode
- [ ] Pattern recognition for common sequences
- [ ] Function prologue/epilogue
- [ ] Multiplication using shifts/adds
- [ ] Division
- [ ] Conditional moves
- [ ] Control flow reconstruction
- [ ] If/else detection
- [ ] Loop detection
- [ ] Switch statement detection
- [ ] Expression reconstruction
- [ ] Format as higher-level pseudo-code
- [ ] Extensive pattern testing
---
#### 4.2.4 Execution History Tracking
**Estimate: 2 days**
**Dependencies:** 4.2.1
**Deliverable:** Execution trace feature
- [ ] Circular buffer for instruction history
- [ ] Register state snapshots over time
- [ ] Configurable history depth
- [ ] Efficient memory usage
- [ ] Playback/reverse debugging (basic)
- [ ] Export trace to file
---
### 4.3 Enhanced Editor Integration
> **Priority: MEDIUM** — UX improvement.
> **Total Estimate: 1 week**
---
#### 4.3.1 Tiling Window System
**Estimate: 2 days**
**Dependencies:** None (UI work)
**Deliverable:** Panel system in emulator
- [ ] Research Rust tiling libraries (`egui_tiles`, or custom)
- [ ] Design panel layout system
- [ ] Code editor panel
- [ ] Disassembly panel
- [ ] Register panel
- [ ] Memory panel
- [ ] Console panel
- [ ] Implement drag-and-drop panel management
- [ ] Save/load layouts
---
#### 4.3.2 Assembly Editor Improvements
**Estimate: 2 days**
**Dependencies:** 4.3.1
**Deliverable:** Enhanced assembly editor
- [ ] Syntax highlighting for DSA assembly
- [ ] Auto-completion for instructions
- [ ] Label/symbol auto-completion
- [ ] Error highlighting
- [ ] Inline documentation tooltips
- [ ] Jump-to-definition for labels
---
#### 4.3.3 High-Level Language Editor
**Estimate: 2 days**
**Dependencies:** 4.3.1, 2.1.4
**Deliverable:** DSC language editor
- [ ] Syntax highlighting for DSC language
- [ ] Basic auto-completion
- [ ] Bracket matching
- [ ] Error highlighting from compiler
- [ ] Go-to-definition (using debug symbols)
- [ ] Inline type hints
---
#### 4.3.4 Integrate Build Tools and Compiler into Editor
Estimate: 1 day
Dependencies: 4.3.1, 3.1.2, 2.1.2
Deliverable: Integrated build experience with compiler support
- [ ] Build button/command in UI that invokes the full compiler pipeline
- [ ] Show build output and compilation errors in console panel
- [ ] Error navigation (click to jump to source)
- [ ] Hot reload on successful build
- [ ] Build status indicator
- [ ] Hook DSC language support into editor for syntax highlighting and autocompletion
- [ ] Provide dedicated DSC build command that uses the new compiler integration
---
## Phase 5: Integration & Polish
**Estimated Duration: 12 weeks**
---
### 5.1 Tool Integration
> **Priority: HIGH** — Everything works together.
> **Total Estimate: 1 week**
---
#### 5.1.1 Unified Toolchain
**Estimate: 3 days**
**Dependencies:** All previous phases
**Deliverable:** `dsa` unified command-line tool
- [ ] Create meta-crate: `dsa-tools`
- [ ] Unified CLI with subcommands
- [ ] `dsa build`
- [ ] `dsa run`
- [ ] `dsa debug`
- [ ] `dsa test`
- [ ] `dsa pkg`
- [ ] Shared configuration system
- [ ] Tool interop testing
- [ ] Documentation for workflow
---
#### 5.1.2 Emulator Integration
**Estimate: 2 days**
**Dependencies:** 5.1.1, 4.3.4
**Deliverable:** Fully integrated development environment
- [ ] Add build tools as emulator dependencies
- [ ] In-editor build triggered from emulator
- [ ] Debugger uses build output directly
- [ ] Source-level debugging with line mapping
- [ ] Test full edit → build → debug cycle
---
#### 5.1.3 Documentation & Tutorials
**Estimate: 2 days**
**Dependencies:** 5.1.2
**Deliverable:** Complete documentation suite
- [ ] Getting started guide
- [ ] Full tutorial: Building a simple game
- [ ] Debugger usage guide
- [ ] Best practices document
- [ ] Troubleshooting guide
---
## Phase 6: Future Enhancements (NTH)
> **Priority: LOW** — Nice to have, long-term goal.
> **Estimated Duration: 4+ weeks**
---
### 6.1 Command-Line Emulator
> ⚠️ **COMPLEX LONG-TERM GOAL** — Requires significant design and UX consideration.
---
#### 6.1.1 Design Phase
**Estimate: 1 week**
**Dependencies:** None
**Deliverable:** `docs/cli-emulator-design.md`
- [ ] UX research for terminal-based debuggers
- [ ] Design TUI layout (using `ratatui` or similar)
- [ ] Command syntax design
- [ ] Scripting support design
- [ ] Accessibility considerations
---
#### 6.1.2 Implementation
**Estimate: 3+ weeks**
**Dependencies:** 6.1.1, Phase 4 complete
**Deliverable:** `dsa-emu-cli` executable
- [ ] TUI framework setup
- [ ] Core emulator integration
- [ ] Command parser
- [ ] Panel rendering (code, registers, memory, etc.)
- [ ] Keyboard shortcuts
- [ ] Mouse support
- [ ] Configuration system
- [ ] Extensive usability testing
---
## Summary Timeline
| Phase | Duration | Key Dependencies |
| ----------------------------- | --------- | ------------------- |
| Phase 1: Foundation | 34 weeks | None |
| Phase 2: Compiler | 34 weeks | Phase 1 complete |
| Phase 3: Build System | 23 weeks | Phases 12 complete |
| Phase 4: Debugger | 34 weeks | Phases 13 complete |
| Phase 5: Integration | 12 weeks | Phases 14 complete |
| Phase 6: CLI Emulator _(NTH)_ | 4+ weeks | Phase 4 complete |
**Total Estimated Time: 1217 weeks (34 months) for Phases 15**
---
## Critical Path
The following tasks are on the critical path and will block other work if delayed:
```
1.1.1 Binary format design
└── 1.1.2 Object file writer
└── 1.1.3 Linker
└── 1.2.2 Assembler rewrite
└── 2.1.3 Compiler codegen
└── 3.1.2 Build system
└── 4.1.2 Debug symbols
└── 4.2.1 Debugger
```
---
## Recommended Work Order
| Weeks | Focus | Tasks |
| ----- | ------------------------------------- | ------------------------------------------------- |
| 12 | Binary Format & Linker | 1.1.1 → 1.1.2 → 1.1.3 |
| 34 | Assembler Rewrite | 1.2.1 → 1.2.2 |
| 56 | Compiler Syntax & Parser | 2.1.1 → 2.1.2 _(start 1.3 docs in parallel)_ |
| 79 | Compiler Codegen & Types | 2.1.3 → 2.1.4 _(start 2.2.1 runtime in parallel)_ |
| 1011 | Build System | 3.1.1 → 3.1.2 → 3.1.3 |
| 1213 | Package Management _(if desired now)_ | 3.2.1 → 3.2.2 → 3.2.3 |
| 1415 | Debug Symbols | 4.1.1 → 4.1.2 → 4.1.3 |
| 1618 | Core Debugger | 4.2.1 → 4.2.2 → 4.2.4 |
| 1920 | Editor Enhancements | 4.3.1 → 4.3.2 → 4.3.3 → 4.3.4 |
| 2122 | Integration & Polish | 5.1.1 → 5.1.2 → 5.1.3 |
---
## Notes
- Time estimates assume ~68 productive hours per day.
- Add **2030% buffer** for unexpected issues.
- Testing time is included in each estimate.
- Documentation is distributed throughout rather than batched at the end.
- Package management (3.2) can be deferred if time-constrained.
- Pseudo-instruction decompiler (4.2.3) can be a stretch goal.
- CLI emulator (Phase 6) is explicitly a "nice to have" and should not block other work.
Binary file not shown.
Binary file not shown.
+427
View File
@@ -0,0 +1,427 @@
# DSA Assembly Language Instruction Reference
## Overview
This document provides a comprehensive reference for the DSA (Damn Simple Architecture) assembly language, including all hardware instructions and pseudo-instructions with their syntax variations and usage examples.
## Calling Convention
| Step | Responsibility | Action | Description |
|------|----------------|--------|-------------|
| 1 | **Caller** | Push arguments | Push exactly n arguments to the stack (in order, last argument pushed first) |
| 2 | **Caller** | Call function | Execute `call namespace::function` - this automatically pushes the return address (pcx) and jumps to the function |
| 3 | **Function** | Set up stack frame | Execute `push bpr; mov spr, bpr` to establish new stack frame |
| 4 | **Function** | Access arguments | Read arguments starting at `spr+8` (first 3 args at offsets 8, 12, 16) |
| 5 | **Function** | Execute function | Perform the function's operations using the arguments |
| 6 | **Function** | Store return value | Write return value (if any) to `spr+8` |
| 7 | **Function** | Restore stack frame | Execute `mov bpr, spr; pop bpr` to restore previous stack frame |
| 8 | **Function** | Return | Execute `return` pseudo-instruction to return to caller |
| 9 | **Caller** | Clean up stack | Pop exactly n arguments from the stack to clean up |
| 10 | **Caller** | Handle unused values | Use `pop zero` to discard any unused stack values if needed |
**Notes:**
- The namespace in step 2 is the name assigned in the `include` statement
- The `call` pseudo-instruction automatically handles return address management so long as the callee does not mess with the stack
- Arguments are accessed by the callee using offsets from the base pointer (bpr)
## Registers
| Register | Type | Description |
|----------|------|---------------------------------------------------------------------------------------------------|
| `rg0-rgf` | General Purpose | General-purpose registers. |
| `acc` | Special | Accumulator for calculations and temporary storage - don't use this for variables as pseudo instructions may overwrite this implicitly! |
| `spr` | Special | Stack pointer |
| `bpr` | Special | Base pointer for stack frames |
| `ret` | Special | Return address register |
| `idr` | Privileged | Interrupt descriptor table address<br/>**on-read/write: protection fault (unless in kernel mode)** |
| `mmr` | Privileged | Hardware memory map table address<br/>**on-read/write: protection fault (unless in kernel mode)** |
| `zero` | Read-only | Always contains zero<br/>**on-read: always returns zero**<br/>**on-write: value is voided** |
| `pcx` | Read-only | Program counter<br/>**on-write: protection fault** |
| `noreg` | Placeholder | Indicates absence of register argument<br/>**on-read/write: illegal instruction fault** |
## Hardware Instructions
### Data Movement Instructions
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **MOV** | `src_reg, dest_reg` | Copy value from source to destination register |
| **MOVS** | `src_reg, dest_reg` | Copy with sign extension |
**Examples:**
```asm
mov rg0, rg1 ; Copy rg0 to rg1
movs rg0, rg1 ; Copy rg0 to rg1 with sign extension
```
### Memory Access Instructions
#### Load Instructions
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **LDB** | `base_reg, dest_reg [, offset]`<br>`label, dest_reg [, offset]` | Load byte from memory |
| **LDBS** | `base_reg, dest_reg [, offset]`<br>`label, dest_reg [, offset]` | Load byte with sign extension |
| **LDH** | `base_reg, dest_reg [, offset]`<br>`label, dest_reg [, offset]` | Load half-word (16-bit) |
| **LDHS** | `base_reg, dest_reg [, offset]`<br>`label, dest_reg [, offset]` | Load half-word with sign extension |
| **LDW** | `base_reg, dest_reg [, offset]`<br>`label, dest_reg [, offset]` | Load word (32-bit) |
**Examples:**
```asm
; Direct register addressing
ldb rg0, rg1 ; Load byte from address in rg0
ldw rg0, rg1, 8 ; Load word from (rg0 + 8)
; Label addressing
ldb buffer, rg2 ; Load byte from label 'buffer'
ldw stack, bpr ; Load stack address into base pointer
```
**Label Expansions:**
```asm
; ldb buffer, rg2 expands to:
lli buffer, rg2 ; Load lower 16 bits of buffer address
lui buffer, rg2 ; Load upper 16 bits of buffer address
ldb rg2, rg2 ; Load byte from address in rg2
; ldw stack, bpr expands to:
lli stack, bpr ; Load lower 16 bits of stack address
lui stack, bpr ; Load upper 16 bits of stack address
ldw bpr, bpr ; Load word from address in bpr
```
#### Store Instructions
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **STB** | `src_reg, base_reg [, offset]`<br>`src_reg, label [, offset]` | Store byte to memory |
| **STH** | `src_reg, base_reg [, offset]`<br>`src_reg, label [, offset]` | Store half-word to memory |
| **STW** | `src_reg, base_reg [, offset]`<br>`src_reg, label [, offset]` | Store word to memory |
**Examples:**
```asm
; Direct register addressing
stb rg0, rg1 ; Store byte from rg0 to address in rg1
stw rg0, rg1, 12 ; Store word to (rg1 + 12)
; Label addressing
stb acc, buffer ; Store byte from accumulator to 'buffer'
stw rg1, current ; Store word to 'current' variable
```
**Label Expansions:**
```asm
; stb acc, buffer expands to:
lli buffer, rgf ; Load lower 16 bits of buffer address
lui buffer, rgf ; Load upper 16 bits of buffer address
stb acc, rgf ; Store byte from acc to address in rgf
; stw rg1, current expands to:
lli current, rgf ; Load lower 16 bits of current address
lui current, rgf ; Load upper 16 bits of current address
stw rg1, rgf ; Store word from rg1 to address in rgf
```
### Immediate Load Instructions
| Mnemonic | Operands | Description |
|----------|----------|------------------------------------------------------------------------|
| **LLI** | `imm, dest_reg` | Load 16-bit immediate into lower 16 bits<br/>**Clears upper 16 bits!** |
| **LUI** | `imm, dest_reg` | Load 16-bit immediate into upper 16 bits |
**Usage**
ensure that you always run **Lli** before **Lui** as **Lli** clears the upper 16 bits.
**Examples:**
```asm
lli 0x1234, rg0 ; Load 0x1234 into lower 16 bits of rg0
lui 0xABCD, rg0 ; Load 0xABCD into upper 16 bits of rg0
```
### Jump Instructions
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **JMP** | `addr [, offset_reg]`<br>`imm, offset_reg` | Unconditional jump |
| **JEQ** | `addr [, offset_reg]` | Jump if equal flag set |
| **JNE** | `addr [, offset_reg]` | Jump if not equal flag set |
| **JGT** | `addr [, offset_reg]` | Jump if greater than flag set |
| **JGE** | `addr [, offset_reg]` | Jump if greater or equal flags set |
| **JLT** | `addr [, offset_reg]` | Jump if less than flag set |
| **JLE** | `addr [, offset_reg]` | Jump if less or equal flags set |
**Examples:**
```asm
jmp start ; Jump to label 'start'
jmp 4, ret ; Jump to address (4 + ret register)
jeq end ; Jump to 'end' if equal flag set
jgt loop ; Jump to 'loop' if greater than flag set
```
### Arithmetic Instructions
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **ADD** | `src1_reg, src2_reg, dest_reg` | Addition |
| **SUB** | `src1_reg, src2_reg, dest_reg` | Subtraction |
| **IADD** | `src_reg, imm [, dest_reg]` | Immediate addition |
| **ISUB** | `src_reg, imm [, dest_reg]` | Immediate subtraction |
| **INC** | `reg` | Increment register by 1 |
| **DEC** | `reg` | Decrement register by 1 |
**Examples:**
```asm
add rg0, rg1, rg2 ; rg2 = rg0 + rg1
sub rg0, rg1, rg2 ; rg2 = rg0 - rg1
iadd rg0, 10 ; rg0 = rg0 + 10
// or using alternate syntax
addi rg0, 1 ; rg0 = rg0 + 1
inc rg0 ; rg0 = rg0 + 1
```
### Bitwise Operations
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **AND** | `src1_reg, src2_reg, dest_reg` | Bitwise AND |
| **OR** | `src1_reg, src2_reg, dest_reg` | Bitwise OR |
| **XOR** | `src1_reg, src2_reg, dest_reg` | Bitwise XOR |
| **NOT** | `src_reg, dest_reg` | Bitwise NOT |
| **NAND** | `src1_reg, src2_reg, dest_reg` | Bitwise NAND |
| **NOR** | `src1_reg, src2_reg, dest_reg` | Bitwise NOR |
| **XNOR** | `src1_reg, src2_reg, dest_reg` | Bitwise XNOR |
**Examples:**
```asm
and rg0, rg1, rg2 ; rg2 = rg0 & rg1
not rg0, rg1 ; rg1 = ~rg0
```
### Shift Operations
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **SHL** | `reg, shift_amount` | Shift left |
| **SHR** | `reg, shift_amount` | Shift right |
**Examples:**
```asm
shl rg0, 2 ; Shift rg0 left by 2 bits
shr rg0, 3 ; Shift rg0 right by 3 bits
```
### Comparison and Control
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **CMP** | `reg1, reg2` | Compare registers and set flags |
**Examples:**
```asm
cmp rg0, zero ; Compare rg0 with zero register
cmp rg1, rg2 ; Compare rg1 with rg2
```
### System Instructions
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **HLT** | - | Halt processor execution |
| **NOP** | - | No operation |
| **INT** | `interrupt_code` | Trigger interrupt |
| **IRT** | - | Return from interrupt |
**Examples:**
```asm
hlt ; Stop processor execution
int 0x21 ; Trigger interrupt 0x21
```
## Pseudo-Instructions
### Data Definition
| Mnemonic | Syntax | Description |
|----------|--------|-------------|
| **DB** | `name: value1 [, value2, ...]` | Define bytes |
| **DH** | `name: value1 [, value2, ...]` | Define half-words |
| **DW** | `name: value1 [, value2, ...]` | Define words |
**Examples:**
```asm
db message: "Hello World", 0
dh numbers: 1000, 2000, 3000
dw stack: 0x10000
```
### Memory Reservation
| Mnemonic | Syntax | Description |
|----------|--------|-------------|
| **RESB** | `name: size` | Reserve bytes |
| **RESH** | `name: size` | Reserve half-words |
| **RESW** | `name: size` | Reserve words |
**Examples:**
```asm
resb buffer: 256 ; Reserve 256 bytes
resh array: 100 ; Reserve space for 100 half-words
resw heap: 1024 ; Reserve space for 1024 words
```
### Stack Operations
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **PUSH** | `reg` | Push register value onto stack |
| **POP** | `reg` | Pop stack value into register |
**Examples:**
```asm
push rg0 ; Push rg0 value onto stack
pop ret ; Pop return address
```
### Memory Access Shortcuts
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **LWI** | `name, reg` | Load address into register |
**Examples:**
```asm
lwi string, rg1 ; Load address of 'string' into rg1
```
### Function Control
| Mnemonic | Operands | Description |
|----------|----------|-------------|
| **CALL** | `namespace::function` | Call a function with automatic return address management |
| **RETURN** | - | Return from a function to the caller |
**Examples:**
```asm
call print::print ; Call the print function from the print namespace
return ; Return from the current function
```
### Module System
| Mnemonic | Syntax | Description |
|----------|--------|-------------|
| **INCLUDE** | `module_name "path"` | Include module |
**Examples:**
```asm
include print "print.dsa"
include fib "fib.dsa"
```
## Library Examples
### Multiplication Library (multiply.dsa)
```asm
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 1
ldw bpr, rg1, 12 // load op 2
lli 0, acc // initialize accumulator
start:
add acc, rg0, acc
dec rg1
cmp rg1, zero
jgt start
end:
stw acc, bpr, 8 // store result for caller
mov bpr, spr
pop bpr
return
```
### Print Library (print.dsa)
```asm
// print.dsa
// usage:
//
// include print "<relative path>"
//
// usage for print:
// push (register containing address of string)
// call print::print
// pop zero
//
// usage for reset:
// call print::reset
dw display: 0x20000
dw current: 0x20000
// prints the given text to the screen.
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // get string address argument
ldw current, rg1 // get current display position
print_loop:
ldb rg0, acc
stb acc, rg1
iadd rg0, 1
iadd rg1, 1
cmp acc, zero
jne print_loop
jmp end
// return
end:
stw rg1, current
mov bpr, spr
pop bpr
return
// resets the cursor position on the screen
reset:
push bpr
mov spr, bpr
ldw display, rg1
stw rg1, current
mov bpr, spr
pop bpr
return
```
### Example Program (main.dsa)
```asm
include print "./print.dsa"
dw stack: 0x10000
db string: "'To confuse your enemy, you must first confuse yourself' - Probably Sun Tzu."
init:
// set up a stack.
ldw stack, bpr
mov bpr, spr
start:
lwi string, rg1
// push string address argument
push rg1
// call print function
call print::print
// clean up stack
pop rg1
hlt
```
+10
View File
@@ -0,0 +1,10 @@
# DSA File formatting specification.
First, a clarification on what formats this document references.
- .dsb: DSA Binary object, similar to a .o object file
- .dse: DSA Executable file, similar to a .exe/ELF binary
## Format Specification
### DSB binary format