reoganised project

This commit is contained in:
2026-02-23 20:32:45 +00:00
parent 42bc666c11
commit 71b36dc6b5
96 changed files with 662 additions and 46 deletions
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "dsx_common"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
compiler = { path = "../../core/compiler" }
assembler = { path = "../../core/assembler" }
common = { path = "../../core/dsa_common" }
chrono = "0.4.44"
serde = { version = "1.0.228", features = ["derive"] }
toml = "1.0.3"
walkdir = "2.5.0"
+160
View File
@@ -0,0 +1,160 @@
use std::{
env, fs, io,
path::{Path, PathBuf},
process::Command,
};
use crate::config::DsxConfig;
use assembler::prelude::Assembler;
use common::build::{BuildError, Builder};
use compiler::Compiler;
// ---------- build ----------------------------------------------------------
pub fn build_project(cwd: &Path) -> Result<(), BuildError> {
let config: DsxConfig = toml::from_str(&fs::read_to_string(cwd.join("Dsx.toml"))?)
.map_err(|deser_err| {
io::Error::new(io::ErrorKind::InvalidData, deser_err.to_string())
})?;
let src_dir = cwd.join("src");
if !src_dir.exists() {
return Err(BuildError::Generic(String::from(
"Source Directory does not exist",
)));
}
// make sure there's a main file to assemble later.
if !main_exists(&src_dir)? {
return Err(BuildError::Generic(String::from(
"No main.dsa or main.dsc file found in top level of src directory.",
)));
}
// detect src.
let (has_dsa, has_dsc) = detect_source_language(&src_dir);
// create a build dir and copy all files across
let build_dir = cwd.join("build");
fs::create_dir_all(&build_dir)?;
env::set_current_dir(&build_dir)?;
copy_recursively(&src_dir, &build_dir)?;
if has_dsc {
build_all_dsc(&build_dir)?;
}
// Replace .dsc with .dsa only in include statements, recursively for each file.
let mut sed_cmd = Command::new("bash");
sed_cmd.args([
"-c",
&format!(
"find \"{}\" -type f -name '*.dsa' -exec sed -i '/^include/ s/\\.dsc/.dsa/g' {{}} +",
build_dir.display()
),
]);
run(&mut sed_cmd);
// assemble result
{
fs::create_dir_all(cwd.join("artifacts"))?;
let mut asm = Assembler::new("./main.dsa");
asm.start();
asm.write_result("../artifacts/out.dsb")?;
}
println!("Build finished. Binary at {}/main.dsb", build_dir.display());
Ok(())
}
// ----- Helpers -------------------------------
struct BuildStep;
impl BuildStep {
pub fn compiling(path: &Path) {
println!("Compiling {}", path.display());
}
pub fn assembling(path: &Path) {
println!("Assembling {}", path.display());
}
}
/// Checks what source languages are used in the project.
fn detect_source_language(src_dir: &Path) -> (bool, bool) {
let mut contains_dsc = false;
let mut contains_dsa = false;
for entry in walkdir::WalkDir::new(src_dir).into_iter().flatten() {
match entry.path().extension().and_then(|s| s.to_str()) {
Some("dsc") => contains_dsc = true,
Some("dsa") => contains_dsa = true,
_ => {}
}
}
(contains_dsa, contains_dsc)
}
// Checks if either main.dsa or main.dsc exist in the source directory
fn main_exists(src_dir: &Path) -> Result<bool, std::io::Error> {
for entry in fs::read_dir(src_dir).into_iter().flatten() {
match entry?.path().file_name().and_then(|s| s.to_str()) {
Some("main.dsc") => return Ok(true),
Some("main.dsa") => return Ok(true),
_ => {}
}
}
Ok(false)
}
// Copy contents of one directory to another
fn copy_recursively(src: &Path, dst: &Path) -> Result<(), std::io::Error> {
if src.is_file() {
fs::create_dir_all(dst.parent().unwrap())?;
fs::copy(src, dst)?;
return Ok(());
}
if src.is_dir() {
for entry in fs::read_dir(src)? {
let entry = entry?;
let child_src = entry.path();
let child_dst = dst.join(entry.file_name());
copy_recursively(&child_src, &child_dst)?;
}
}
Ok(())
}
fn build_all_dsc(path: &Path) -> Result<(), BuildError> {
if path.is_dir() {
for entry in fs::read_dir(path)? {
let entry = entry?;
build_all_dsc(&entry.path())?;
}
return Ok(());
}
if path.extension().and_then(|s| s.to_str()) == Some("dsc") {
let input_path = path;
let output_path = path.with_extension("dsa");
let mut compiler = Compiler::new(input_path);
compiler.start();
compiler.write_result(output_path.clone())?;
}
Ok(())
}
/// Run a command and exit on failure.
fn run(cmd: &mut Command) {
let status = cmd.status().expect("failed to execute command");
if !status.success() {
std::process::exit(1);
}
}
+589
View File
@@ -0,0 +1,589 @@
pub trait Template {
fn lib(project: &str) -> String;
fn bin(project: &str) -> String;
fn create(project: &str, lib: bool) -> String {
if lib {
Self::lib(project)
} else {
Self::bin(project)
}
}
}
pub struct Dsa;
pub struct Dsc;
impl Template for Dsa {
fn lib(project: &str) -> String {
format!(
r#"//
lib.dsa
// usage:
//
// include {project} "<relative path>"
//
// usage for {project}_main:
// push (arg1)
// push (arg0)
// call {project}::{project}_main
// pop (arg0)
// pop (arg1)
// Example data declarations
// dw example_data: 0x0000
// Main function template
{project}_main:
// the correct way to start a function as defined by the calling convention
push bpr
mov spr, bpr
// explanation of how to access args
ldw bpr, rg0, 8 // arg 0
ldw bpr, rg0, 12 // arg 1
// your code goes here
// Example: load example_data into rg1
// ldw example_data, rg1
// the correct way to end a function as defined by the calling convention
mov bpr, spr
pop bpr
return
"#,
)
}
fn bin(project: &str) -> String {
format!(
r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}
// Imports
include print: "./lib/print.dsa"
// Globals & Reserved Memory
dw stack: 0x10000
db message: "Process Exited with code:"
// Entry Point
_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
main:
push bpr
mov spr, bpr
// Your code goes here
// Return zero
stw zero, bpr, 8
mov bpr, spr
pop bpr
return"#,
timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
)
}
}
impl Template for Dsc {
fn lib(project: &str) -> String {
format!(
r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}
// Imports
include print: "./lib/print.dsa";
// Main Function
fn {project}_main() -> u32 {{
return 0;
}}"#,
timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
)
}
fn bin(project: &str) -> String {
format!(
r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}
// Imports
include print: "./lib/print.dsa";
// Main Function
fn main() -> u32 {{
return 0;
}}"#,
timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
)
}
}
pub fn create_print_lib() -> String {
String::from(
r#"
// 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.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
"#,
)
}
pub fn create_maths_lib() -> String {
String::from(
r#"
// 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
"#,
)
}
+44
View File
@@ -0,0 +1,44 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DsxConfig {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub remote_url: Option<String>,
#[serde(default)]
pub binaries: Vec<Binary>,
// todo!
// #[serde(default)]
// pub libraries: Vec<Library>,
}
impl DsxConfig {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
description: None,
remote_url: None,
binaries: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Binary {
pub name: String,
pub path: String,
}
impl Binary {
pub fn new(name: &str, path: &str) -> Self {
Self {
name: name.to_string(),
path: path.to_string(),
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod builder;
pub mod config;