From 42c26d4184b6e993243e81f8ab37cc8ccac05158 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Fri, 20 Jun 2025 03:25:28 +0100 Subject: [PATCH] added a create-project system to assembler, and fixed a couple of parsing bugs --- assembler/src/assembler/codegen.rs | 10 +- assembler/src/assembler/expand.rs | 162 ++++++++++++++++++++--------- assembler/src/assembler/model.rs | 44 ++++++-- assembler/src/assembler/parser.rs | 37 ++++--- assembler/src/lib.rs | 10 +- assembler/src/main.rs | 6 ++ assembler/src/tooling/mod.rs | 1 + assembler/src/tooling/project.rs | 93 +++++++++++++++++ assembler/src/util/mod.rs | 9 ++ common/src/instructions.rs | 24 +++++ emulator/src/emulator/ui/editor.rs | 6 +- resources/dsa/multiply.dsa | 30 ++++++ resources/dsa/print.dsa | 48 ++++++--- resources/dsa/test.dsa | 19 +--- resources/dsb/test.dsb | Bin 696 -> 684 bytes 15 files changed, 392 insertions(+), 107 deletions(-) create mode 100644 assembler/src/tooling/mod.rs create mode 100644 assembler/src/tooling/project.rs create mode 100644 assembler/src/util/mod.rs create mode 100644 resources/dsa/multiply.dsa diff --git a/assembler/src/assembler/codegen.rs b/assembler/src/assembler/codegen.rs index a54eebc..f3f7a18 100644 --- a/assembler/src/assembler/codegen.rs +++ b/assembler/src/assembler/codegen.rs @@ -7,6 +7,7 @@ pub fn codegen(nodes: Vec) -> Result, AssembleError> { let mut instructions = vec![]; for node in nodes { + println!("node {node}"); instructions.push(build_instruction(node)?); } @@ -169,6 +170,9 @@ fn build_instruction(node: Node) -> Result { let immediate = expect_token!(args.first().unwrap(), Immediate)?; Ok(Instruction::Segment(immediate)) } + + // These pseudo-instructions should have been expanded! + // this case being activated suggests the wrong syntax was given, and indicates a bug in the parser Opcode::Db | Opcode::Dh | Opcode::Dw @@ -178,6 +182,10 @@ fn build_instruction(node: Node) -> Result { | Opcode::Push | Opcode::Pop | Opcode::Lwi - | Opcode::Include => todo!(), + | Opcode::Include + | Opcode::Call + | Opcode::Return + | Opcode::Pusha + | Opcode::Popa => Err(AssembleError::InvalidArg), } } diff --git a/assembler/src/assembler/expand.rs b/assembler/src/assembler/expand.rs index a929dd7..dbe81da 100644 --- a/assembler/src/assembler/expand.rs +++ b/assembler/src/assembler/expand.rs @@ -26,6 +26,10 @@ fn try_expand( match node.opcode() { Opcode::Push => expand_push(node.clone(), result)?, Opcode::Pop => expand_pop(node.clone(), result)?, + Opcode::Pusha => expand_pusha(node.clone(), result)?, + Opcode::Popa => expand_popa(node.clone(), result)?, + Opcode::Call => expand_call(node.clone(), result)?, + Opcode::Return => expand_return(node.clone(), result)?, Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => { expand_ldx(node.clone(), result)? } @@ -42,22 +46,105 @@ fn try_expand( fn expand_push(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> { let label = current.label(); let reg = expect_type!(current.arg(0).unwrap(), Register)?; + let spr = Token::Register(Register::Spr); nodes.extend(vec![ - node!( - label, - Opcode::SubI, - Token::Register(Register::Spr), - Token::Immediate(4), - Token::Register(Register::Spr) - ), - node!( - None, - Opcode::Stw, - reg, - Token::Register(Register::Spr), - Token::Immediate(0) - ), + node!(label, Opcode::SubI, spr, 4, spr), + node!(None, Opcode::Stw, reg, spr, 0), + ]); + + Ok(()) +} + +fn expand_pusha(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + let count = expect_token!(current.arg(0).unwrap(), Immediate)?; + let spr = Token::Register(Register::Spr); + let registers: Vec = Register::general(); + + nodes.push(node!( + label, + Opcode::SubI, + spr, + Token::Immediate(count * 4), + spr + )); + + nodes.extend( + (0..count) + .rev() + .map(|i| { + node!( + None, + Opcode::Stw, + Token::Register(registers[i as usize]), + spr, + Token::Immediate(i * 4) + ) + }) + .collect::>(), + ); + + Ok(()) +} + +fn expand_popa(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + let count = expect_token!(current.arg(0).unwrap(), Immediate)?; + let spr = Token::Register(Register::Spr); + let registers: Vec = Register::general(); + + nodes.extend( + (0..count) + .rev() + .map(|i| { + node!( + { if i == 0 { label.clone() } else { None } }, + Opcode::Ldw, + spr, + Token::Register(registers[i as usize]), + Token::Immediate(i * 4) + ) + }) + .collect::>(), + ); + + nodes.push(node!( + None, + Opcode::AddI, + spr, + Token::Immediate(count * 4), + spr + )); + + Ok(()) +} + +fn expand_call(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + let addr = expect_type!(current.arg(0).unwrap(), Symbol)?; + let spr = Token::Register(Register::Spr); + let pcx = Token::Register(Register::Pcx); + let zero = Token::Register(Register::Zero); + + nodes.extend(vec![ + node!(label, Opcode::SubI, spr, 4, spr), + node!(None, Opcode::Stw, pcx, spr, 0), + node!(None, Opcode::Jmp, addr, zero), + ]); + + Ok(()) +} + +fn expand_return(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + let spr = Token::Register(Register::Spr); + let ret = Token::Register(Register::Ret); + + nodes.extend(vec![ + node!(label, Opcode::Ldw, spr, ret, 0), + node!(None, Opcode::AddI, spr, 4, spr), + node!(None, Opcode::Jmp, 4, ret), ]); Ok(()) @@ -66,22 +153,11 @@ fn expand_push(current: Node, nodes: &mut Vec) -> Result<(), AssembleError fn expand_pop(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> { let label = current.label(); let reg = expect_type!(current.arg(0).unwrap(), Register)?; + let spr = Token::Register(Register::Spr); nodes.extend(vec![ - node!( - label, - Opcode::Ldw, - Token::Register(Register::Spr), - reg, - Token::Immediate(0) - ), - node!( - None, - Opcode::AddI, - Token::Register(Register::Spr), - Token::Immediate(4), - Token::Register(Register::Spr) - ), + node!(label, Opcode::Ldw, spr, reg, 0), + node!(None, Opcode::AddI, spr, 4, spr), ]); Ok(()) @@ -94,9 +170,9 @@ fn expand_ldx(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> let offset = expect_type!(current.arg(2).unwrap(), Immediate)?; nodes.extend(vec![ - node!(current.label(), Opcode::Lli, name.clone(), reg.clone()), - node!(None, Opcode::Lui, name.clone(), reg.clone()), - node!(None, opcode, reg.clone(), reg, offset), + node!(current.label(), Opcode::Lli, name, reg), + node!(None, Opcode::Lui, name, reg), + node!(None, opcode, reg, reg, offset), ]); Ok(()) @@ -107,20 +183,12 @@ fn expand_stx(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> let base = expect_type!(current.arg(0).unwrap(), Register)?; let dest = expect_type!(current.arg(1).unwrap(), Symbol)?; let offset = expect_type!(current.arg(2).unwrap(), Immediate)?; + let temp = Token::Register(Register::Rgf); + nodes.extend(vec![ - node!( - current.label(), - Opcode::Lli, - dest.clone(), - Token::Register(Register::Rgf) - ), - node!( - None, - Opcode::Lui, - dest.clone(), - Token::Register(Register::Rgf) - ), - node!(None, opcode, base, Token::Register(Register::Rgf), offset), + node!(current.label(), Opcode::Lli, dest, temp), + node!(None, Opcode::Lui, dest, temp), + node!(None, opcode, base, temp, offset), ]); Ok(()) @@ -131,8 +199,8 @@ fn expand_lwi(current: Node, nodes: &mut Vec) -> Result<(), AssembleError> let reg = expect_type!(current.arg(1).unwrap(), Register)?; nodes.extend(vec![ - node!(current.label(), Opcode::Lli, val.clone(), reg.clone()), - node!(None, Opcode::Lui, val.clone(), reg.clone()), + node!(current.label(), Opcode::Lli, val, reg), + node!(None, Opcode::Lui, val, reg), ]); Ok(()) @@ -153,7 +221,7 @@ fn expand_resx(current: Node, nodes: &mut Vec) -> Result<(), AssembleError // push the inital node with the label for _ in 0..size.div_ceil(units_per) { // push the rest of the nodes - buffer.push(node!(None, Opcode::Data, Token::Immediate(0))); + buffer.push(node!(None, Opcode::Data, 0)); } buffer[0].symbol = Some(region_label); nodes.extend(buffer); diff --git a/assembler/src/assembler/model.rs b/assembler/src/assembler/model.rs index 7e4592b..955c5ac 100644 --- a/assembler/src/assembler/model.rs +++ b/assembler/src/assembler/model.rs @@ -18,7 +18,19 @@ macro_rules! node { }; ($symbol: expr, $opcode: expr, $($tokens: expr),+) => { - Node::new($symbol.clone(), $opcode.clone(), vec![$($tokens.clone()),+]) + Node::new( + $symbol.clone(), + $opcode.clone(), + vec![$(node!(@convert_token $tokens)),+] + ) + }; + + (@convert_token $token: literal) => { + Token::Immediate($token) + }; + + (@convert_token $token: expr) => { + $token.clone() }; } @@ -36,7 +48,7 @@ impl Node { } pub fn opcode(&self) -> Opcode { - self.opcode.clone() + self.opcode } pub fn args(&self) -> Vec { @@ -134,11 +146,13 @@ impl fmt::Display for Opcode { Opcode::Push => write!(f, "push"), Opcode::Pop => write!(f, "pop"), Opcode::Lwi => write!(f, "lwi"), + Opcode::Call => write!(f, "call"), + Opcode::Return => write!(f, "return"), + Opcode::Pusha => write!(f, "pusha"), + Opcode::Popa => write!(f, "popa"), - // utility - removed at compile time + // meta instructions Opcode::Include => write!(f, "include"), - - // special - generated by assembler Opcode::Data => write!(f, "data"), Opcode::Segment => write!(f, "[SEGMENT]"), } @@ -200,7 +214,7 @@ impl TokenType { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Opcode { // Real instructions (0x00-0x26) Nop, @@ -242,6 +256,7 @@ pub enum Opcode { Hlt, AddI, SubI, + // Pseudo-instructions Db, Dh, @@ -251,10 +266,14 @@ pub enum Opcode { Resw, Push, Pop, + Pusha, + Popa, Lwi, - Include, + Call, + Return, - // fake instructions (these aren't present in the binary as instructions) + // meta instructions (these aren't present in the binary as instructions) + Include, Data, Segment, } @@ -328,6 +347,10 @@ impl FromStr for Opcode { "pop" => Ok(Self::Pop), "lwi" => Ok(Self::Lwi), "include" => Ok(Self::Include), + "call" => Ok(Self::Call), + "return" => Ok(Self::Return), + "pusha" => Ok(Self::Push), + "popa" => Ok(Self::Pop), _ => Err(OpcodeFromStrError::InvalidRegister("unknown opcode")), } } @@ -340,7 +363,9 @@ impl Opcode { "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc", "dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor", "xnor", "int", "irt", "hlt", "addi", "subi", // Pseudo-instructions - "db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "include", + "db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return", + "pusha", "popa", // meta instructions + "include", ]; pub fn to_opcode_value(&self) -> Option { @@ -385,6 +410,7 @@ impl Opcode { Self::AddI => Some(0x25), Self::SubI => Some(0x26), Self::Segment => Some(0x27), + // Pseudo-instructions don't have opcode values _ => None, } diff --git a/assembler/src/assembler/parser.rs b/assembler/src/assembler/parser.rs index 3f8ab49..1861b42 100644 --- a/assembler/src/assembler/parser.rs +++ b/assembler/src/assembler/parser.rs @@ -40,10 +40,12 @@ impl Default for Program { impl Parser { pub fn parse_nodes(tokens: Vec) -> Result, AssembleError> { let mut self_ = Parser { - tokens: tokens.into_iter().rev().collect(), + tokens: tokens.clone().into_iter().rev().collect(), nodes: vec![], }; + println!("{:#?}", tokens); + while !self_.tokens.is_empty() { let ins = self_.parse_instruction()?; self_.nodes.push(ins); @@ -68,8 +70,6 @@ impl Parser { unreachable!(); } - println!("{:?}", self.peek_next()?); - // check if the Node starts with a label let label = expect_token!(self.peek_next()?, Symbol).ok(); if label.is_some() { @@ -158,14 +158,24 @@ impl Parser { | Opcode::Jlt | Opcode::Jle => { let imm = expect_type!(self.next()?, Immediate, Symbol)?; - let offset = if expect_type!(self.peek_next()?, Register).is_ok() { - self.next()? - } else { - Token::Register(Register::Zero) + let offset = match self.peek_next() { + Ok(token) => { + if expect_type!(token, Register).is_ok() { + self.next()? + } else { + Token::Register(Register::Zero) + } + } + Err(_) => Token::Register(Register::Zero), }; args = vec![imm, offset]; } + Opcode::Call => { + let addr = expect_type!(self.next()?, Symbol)?; + args = vec![addr]; + } + // I-type instructions Opcode::Lui | Opcode::Lli | Opcode::Lwi => { let imm = expect_type!(self.next()?, Immediate, Symbol)?; @@ -193,18 +203,19 @@ impl Parser { } Opcode::Db | Opcode::Dh | Opcode::Dw => { - args = self.parse_data_definition(opcode.clone())?; + args = self.parse_data_definition(opcode)?; } // E-type pseudoinstructions (stack operations) - Opcode::Push => { + Opcode::Push | Opcode::Pop => { let reg = expect_type!(self.next()?, Register, Symbol)?; args = vec![reg]; } - Opcode::Pop => { - let reg = expect_type!(self.next()?, Register, Symbol)?; - args = vec![reg]; + Opcode::Pusha | Opcode::Popa => { + let count = + expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8)); + args = vec![count]; } // Special instructions @@ -214,7 +225,7 @@ impl Parser { } // Instructions with no arguments - Opcode::Hlt | Opcode::Nop | Opcode::Irt => { + Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => { args = vec![]; } diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index f456590..8f6ae20 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -1,14 +1,8 @@ use assembler::codegen::codegen; -use assembler::expand::expand_pseudo_ops; -use assembler::model::{Node, Opcode, Symbol, Token, TokenType}; -use assembler::parser::{Parser, Program}; -use assembler::resolver::{create_sections, resolve_dependencies, resolve_symbols}; -use common::prelude::*; -use core::fmt; pub mod assembler; - -use crate::assembler::lexer; +pub mod tooling; +mod util; pub mod prelude { pub use crate::assembler::assemble; diff --git a/assembler/src/main.rs b/assembler/src/main.rs index 6185a64..3ce7bcc 100644 --- a/assembler/src/main.rs +++ b/assembler/src/main.rs @@ -3,6 +3,12 @@ use std::{fs, io::Write, path::PathBuf}; fn main() { // parse args: let args: Vec = std::env::args().collect(); + + if args.len() == 2 && args[1] == "init" { + assembler::tooling::project::tool_libcreate(); + std::process::exit(0); + } + if args.len() != 5 || args[1] != "-i" || args[3] != "-o" { eprintln!("Usage: binary_name -i input_path -o output_path"); std::process::exit(1); diff --git a/assembler/src/tooling/mod.rs b/assembler/src/tooling/mod.rs new file mode 100644 index 0000000..36df406 --- /dev/null +++ b/assembler/src/tooling/mod.rs @@ -0,0 +1 @@ +pub mod project; diff --git a/assembler/src/tooling/project.rs b/assembler/src/tooling/project.rs new file mode 100644 index 0000000..9195872 --- /dev/null +++ b/assembler/src/tooling/project.rs @@ -0,0 +1,93 @@ +use crate::util::input; + +pub fn tool_libcreate() { + let mut ptype: String; + loop { + ptype = input("Enter project type (bin|lib)"); + if ptype == "bin" || ptype == "lib" { + break; + } + } + + let project_name = input("Enter project name"); + let project_path = input("Enter Directory to create project in"); + + println!("[ Creating new {ptype} project {project_name} in {project_path} ]"); + + let template = match ptype.as_str() { + "bin" => generate_bin_template(&project_name), + "lib" => generate_lib_template(&project_name), + _ => panic!("Invalid project type"), + }; + + let path = format!("{}/{}.dsa", project_path, project_name); + std::fs::write(path, template).expect("Unable to write file"); +} + +fn generate_lib_template(module_name: &str) -> String { + format!( + r#"// {module_name}.dsa +// usage: +// +// include {module_name} "" +// +// usage for {module_name}_main: +// push (arg1) +// push (arg0) +// call {module_name}::{module_name}_main +// pop (arg0) +// pop (arg1) + +// Example data declarations +// dw example_data: 0x0000 + +// Main function template +{module_name}_main: + // the correct way to start a function as defined by the calling convention + push bpr + mov spr, bpr + + // explanation of how to access args + ldw bpr, rg0, 8 // arg 0 + ldw bpr, rg0, 12 // arg 1 + + // your code goes here + // Example: load example_data into rg1 + // ldw example_data, rg1 + + // the correct way to end a function as defined by the calling convention + mov bpr, spr + pop bpr + return +"#, + ) +} + +fn generate_bin_template(project_name: &str) -> String { + format!( + r#"// {project_name}.dsa +// Binary executable project + +// Example Dependencies +// include math "libs/math/math.dsa" +include print "../resources/dsa/print.dsa" + +// Data declarations - It is best practice to include these before any code! +dw message: "Hello from {project_name}.dsa!" // strings are automatically null terminated! + +// Program entry point - execution starts at the first non-definition line +{project_name}: + // Getting started: Calling external functions + // Syntax: push (arg1), push (arg0), call namespace::function, pop (arg0), pop (arg1) + + // Example: Print a string (if print library is included) + ldw message, rg0 // load address of message + push rg0 // push argument + call print::print // call the print function + pop rg0 // clean up stack + + // Program must end with halt instruction + halt +"#, + ) +} diff --git a/assembler/src/util/mod.rs b/assembler/src/util/mod.rs new file mode 100644 index 0000000..5328c05 --- /dev/null +++ b/assembler/src/util/mod.rs @@ -0,0 +1,9 @@ +use std::io::Write; + +pub fn input(prompt: &str) -> String { + print!("{}\n > ", prompt); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + input.trim().to_string() +} diff --git a/common/src/instructions.rs b/common/src/instructions.rs index a30eb66..b27675d 100644 --- a/common/src/instructions.rs +++ b/common/src/instructions.rs @@ -71,6 +71,30 @@ pub enum Register { Pcx, } +impl Register { + #[must_use] + pub fn general() -> Vec { + vec![ + Self::Rg0, + Self::Rg1, + Self::Rg2, + Self::Rg3, + Self::Rg4, + Self::Rg5, + Self::Rg6, + Self::Rg7, + Self::Rg8, + Self::Rg9, + Self::Rga, + Self::Rgb, + Self::Rgc, + Self::Rgd, + Self::Rge, + Self::Rgf, + ] + } +} + impl Default for Register { fn default() -> Self { Self::NoReg diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index d06c758..b974d59 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -11,6 +11,8 @@ use crate::emulator::{ ui::interface::Component, }; +use assembler::prelude::*; + pub struct Editor { // editor state path: Option, @@ -344,10 +346,10 @@ impl Editor { // builds the current file if ui.button("Build").clicked() && !self.unsaved { if let Some(path) = &self.path { - let instructions = match assembler::assemble(path) { + let instructions = match assemble(path) { Ok(instructions) => instructions, Err(error) => { - self.error = Some(error.to_string()); + self.error = Some(format!("Failed to assemble: {error:?}")); return; } }; diff --git a/resources/dsa/multiply.dsa b/resources/dsa/multiply.dsa new file mode 100644 index 0000000..0e6cc40 --- /dev/null +++ b/resources/dsa/multiply.dsa @@ -0,0 +1,30 @@ +// multiply.dsa +// usage: +// +// include multiply "" +// +// 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 diff --git a/resources/dsa/print.dsa b/resources/dsa/print.dsa index 2cf59eb..b9a4654 100644 --- a/resources/dsa/print.dsa +++ b/resources/dsa/print.dsa @@ -17,18 +17,15 @@ dw display: 0x20000 dw current: 0x20000 -reset: - pop ret - ldw display, rg1 - stw rg1, current - jmp 4, ret - +// prints the given text to the screen. print: - pop ret // return address - pop rg0 // string + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 ldw current, rg1 -loop: +print_loop: ldb rg0, acc stb acc, rg1 @@ -36,10 +33,35 @@ loop: addi rg1, 1 cmp acc, zero - jne loop + jne print_loop + jmp end - // return +// return end: - // set current to stw rg1, current - jmp 4, ret \ No newline at end of file + + mov bpr, spr + pop bpr + return + +// resets the cursor position on the screen +reset: + push bpr + mov spr, bpr + ldw display, rg1 + jmp end + +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 \ No newline at end of file diff --git a/resources/dsa/test.dsa b/resources/dsa/test.dsa index e9e9caa..aa1400a 100644 --- a/resources/dsa/test.dsa +++ b/resources/dsa/test.dsa @@ -1,5 +1,4 @@ include print "./print.dsa" -include fib "./fib.dsa" dw stack: 0x10000 db string: "An idiot admires complexity, a genius admires simplicity, @@ -9,23 +8,15 @@ gonna think you're a god cause you made it so complicated nobody can understand That's how they write journals in Academics, they try to make it so complicated people think you're a genius" init: + // set up a stack. ldw stack, bpr mov bpr, spr start: lwi string, rg1 - // push variables - push rg1 // string address. - push pcx // return address. - - // call - jmp print::print - - - lli 25, rg0 - push rg0 - push pcx - jmp fib::fib_n - + push rg1 + call print::print + pop rg1 + hlt \ No newline at end of file diff --git a/resources/dsb/test.dsb b/resources/dsb/test.dsb index 853ca2129e324cd4deb123866c6f24a4814863dd..c284fe1214ec5b4fa47ec17f0960b1f8185bd4ee 100644 GIT binary patch delta 173 zcmdnNx`uVb8%7ldrt4-542p6r42IJfc6^_-l7Zn53xnY-28KW_h6(0u91IR}lUmyC^thkU4><|l>!gSpUqz3>gn