From 24424970fec1066db7475e181326fbb90857454a Mon Sep 17 00:00:00 2001 From: zxq5 Date: Thu, 20 Aug 2026 18:17:06 +0100 Subject: [PATCH] progress on DSSML compiler --- emulator/dsa_gui/Cargo.toml | 17 +- emulator/dsa_gui/src/ui/process_manager.rs | 30 ++ emulator/dsa_gui/src/ui/tiles/editor/mod.rs | 69 ++- libraries/zxq5_egui/Cargo.toml | 2 +- resources/main.dsa | 138 ++++++ .../src/backend/intermediate_rep.rs | 102 ++++ .../src/backend/ir_lowering_codegen.rs | 445 ++++++++++++++++++ tooling/dscs_compiler/src/backend/mod.rs | 2 + tooling/dscs_compiler/src/model.rs | 6 + 9 files changed, 776 insertions(+), 35 deletions(-) create mode 100644 resources/main.dsa create mode 100644 tooling/dscs_compiler/src/backend/intermediate_rep.rs create mode 100644 tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs diff --git a/emulator/dsa_gui/Cargo.toml b/emulator/dsa_gui/Cargo.toml index 15eed05..532efc3 100644 --- a/emulator/dsa_gui/Cargo.toml +++ b/emulator/dsa_gui/Cargo.toml @@ -19,18 +19,19 @@ dsa_tasks = { path = "../../libraries/dsa_tasks" } zxq5_egui = { path = "../../libraries/zxq5_egui" } assembler = { path = "../../tooling/assembler" } +dscs_compiler = { path = "../../tooling/dscs_compiler"} dsfl_compiler = { path = "../../tooling/dsfl_compiler" } dsa_emulator = { version = "0.3.0", path = "../dsa_emulator" } disassembler = { version = "0.3.0", path = "../../tooling/disassembler" } clap = { version = "4.5.60", features = ["derive"] } ron = "0.12.0" -egui = "0.35.0" -eframe = "0.35.0" -egui_tiles = "0.16.0" -egui_code_editor = "0.3.7" -egui_file = "0.27.0" -egui_extras = "0.35.0" +egui = "0.36.1" +eframe = "0.36.1" +egui_tiles = "0.17.1" +egui_code_editor = "0.4.1" +egui_file = "0.28.0" +egui_extras = "0.36.1" -egui-file-dialog = "0.14.1" -egui_commonmark = { version = "0.24.0", features = ["better_syntax_highlighting", "macros"] } \ No newline at end of file +egui-file-dialog = "0.15.0" +egui_commonmark = { version = "0.25.0", features = ["better_syntax_highlighting", "macros"] } \ No newline at end of file diff --git a/emulator/dsa_gui/src/ui/process_manager.rs b/emulator/dsa_gui/src/ui/process_manager.rs index a6a09c7..9b7b642 100644 --- a/emulator/dsa_gui/src/ui/process_manager.rs +++ b/emulator/dsa_gui/src/ui/process_manager.rs @@ -29,6 +29,36 @@ impl ProcessSpawner { } } +pub struct ProcessJob +where + Proc: TraceableProcess, + OnComplete: FnOnce(Result) -> Action + Send + 'static, +{ + input: Proc::Input, + proc: Proc, + on_complete: OnComplete, +} + +impl ProcessJob +where + Proc: TraceableProcess, + OnComplete: FnOnce(Result) -> Action + Send + 'static, +{ + pub fn new(input: Proc::Input, proc: Proc, on_complete: OnComplete) -> Self { + Self { + input, + proc, + on_complete, + } + } + + pub fn into_action(self) -> Action { + Action::SpawnProcess(ProcessSpawner::new(move |pm| { + pm.spawn(self.proc.label(), self.proc, self.input, self.on_complete) + })) + } +} + pub struct ProcessHandle { id: usize, name: String, diff --git a/emulator/dsa_gui/src/ui/tiles/editor/mod.rs b/emulator/dsa_gui/src/ui/tiles/editor/mod.rs index 2542cd0..4482374 100644 --- a/emulator/dsa_gui/src/ui/tiles/editor/mod.rs +++ b/emulator/dsa_gui/src/ui/tiles/editor/mod.rs @@ -1,6 +1,6 @@ use crate::state::Action; use crate::state::DataSource::Data; -use crate::ui::process_manager::ProcessSpawner; +use crate::ui::process_manager::{ProcessJob, ProcessSpawner}; use crate::ui::tiles::TilePane; use crate::ui::tiles::editor::syntax::SyntaxFrom; use assembler::prelude::Assembler; @@ -12,6 +12,7 @@ use egui_tiles::TileId; use std::any::Any; use std::fs::File; use std::path::{Path, PathBuf}; +use dscs_compiler::DSCSCompiler; use theme::THEME; use zxq5_egui::buttons::PaddedWidget; @@ -67,32 +68,48 @@ impl Editor { &mut self.buffer } - // pub fn get_build_command(file_path: PathBuf) -> impl TraceableProcess { - // let filename = file_path.file_name().unwrap().to_str().unwrap(); - // match file_path.extension().unwrap().to_str().unwrap() { - // "dsa" => Assembler::new(filename), - // "dsfl" => DsflCompiler::new(filename), - // } - // } pub fn create_build_action( &self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static, - ) -> Action { - ProcessSpawner::new(move |pm| { - let builder = Assembler::new(filename.file_name().unwrap().to_str().unwrap()); - pm.spawn( - builder.label(), - builder, - filename, - move |result| match result { - Ok(bin) => on_complete(bin), - Err(e) => Action::ShowError(e.to_string()), - }, - ); - }) - .into_action() + ) -> Option { + let Some(ext) = filename.extension() else { + return None; + }; + + match ext.to_str().unwrap() { + "dsa" => { + let builder = Assembler::new(filename.file_name().unwrap().to_str().unwrap()); + Some(ProcessJob::new( + filename, builder, + move |result| match result { + Ok(bin) => on_complete(bin), + Err(e) => Action::ShowError(e.to_string()), + } + ).into_action()) + }, + "dsc" => { + let builder = DSCSCompiler::new(filename.display().to_string(), false); + Some(ProcessJob::new( + filename.clone(), builder, + move |result| match result { + Ok(bin) => { + let output = filename.with_extension("dsa"); + std::fs::write(&output, &bin).expect("Failed to write output"); + Action::OpenFile(output) + }, + Err(e) => Action::ShowError(e.to_string()), + } + ).into_action()) + + } + _ => { + None + } + } + + } } @@ -115,9 +132,9 @@ impl TilePane for Editor { return; }; - action = Some(self.create_build_action(filename, |res| { + action = self.create_build_action(filename, |res| { Action::LoadBinary(Data(res.to_memory_image())) - })); + }); } if PaddedWidget::button("Build").ui(ui).clicked() { @@ -126,12 +143,12 @@ impl TilePane for Editor { return; }; - action = Some(self.create_build_action(filename.clone(), move |res| { + action = self.create_build_action(filename.clone(), move |res| { let filename = filename.with_extension("dsb"); let mut writer = File::create(&filename).unwrap(); res.write(&mut writer).unwrap(); Action::OpenFile(filename) - })); + }); } PaddedWidget::label( diff --git a/libraries/zxq5_egui/Cargo.toml b/libraries/zxq5_egui/Cargo.toml index 6f3c53e..633f5f2 100644 --- a/libraries/zxq5_egui/Cargo.toml +++ b/libraries/zxq5_egui/Cargo.toml @@ -5,4 +5,4 @@ version.workspace = true authors.workspace = true [dependencies] -egui = "0.35.0" +egui = "0.36.1" diff --git a/resources/main.dsa b/resources/main.dsa new file mode 100644 index 0000000..36908c3 --- /dev/null +++ b/resources/main.dsa @@ -0,0 +1,138 @@ +// GENERATED BY DSC COMPILER +// Generated at 2026-08-20 11:13:23 + +// Imports +include alloc: "./lib/memory/block_alloc.dsa" +include print: "./lib/io/print.dsa" + +// Globals & Reserved Memory + +// Entry Point +dw stack: 0x010000 +db message: "Process Exited with code:" +_init: + ldw stack, bpr, 0 + 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 + ret + +db str_4: "successful free of ptr" +// fn main() -> u32 +main: + push bpr + mov spr, bpr + lli 32, rg0 + lli 64, rg1 + // push arg 1 + push rg0 + // push arg 0 + push rg1 + call alloc::init + pop rg2 + pop zero + push rg2 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + call print::print_newline + ldw spr, rg0, 0 + stw rg0, spr, 0 + // push arg 0 + push rg0 + call alloc::alloc + pop rg1 + push rg1 + // push arg 0 + push rg1 + call print::print_hex_word + pop zero + lli 200, rg0 + ldw spr, rg1, 0 + stw rg0, rg1, 0 + stw rg1, spr, 0 + call print::print_newline + ldw spr, rg0, 4 + stw rg0, spr, 4 + // push arg 0 + push rg0 + call alloc::alloc + pop rg2 + push rg2 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + call print::print_newline + ldw spr, rg0, 4 + ldw rg0, rg2, 0 + stw rg0, spr, 4 + // push arg 0 + push rg2 + call print::print_num + pop zero + ldw spr, rg2, 4 + stw rg2, spr, 4 + addi spr, 4, rg3 + ldw spr, rg2, 8 + stw rg2, spr, 8 + // push arg 1 + push rg3 + // push arg 0 + push rg2 + call alloc::free + pop zero + pop zero + ldw spr, rg2, 8 + stw rg2, spr, 8 + // push arg 0 + push rg2 + call alloc::alloc + pop rg3 + push rg3 + call print::print_newline + ldw spr, rg2, 0 + stw rg2, spr, 0 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + call print::print_newline + ldw spr, rg2, 8 + stw rg2, spr, 8 + // push arg 0 + push rg2 + call print::print_hex_word + pop zero + ldw spr, rg2, 8 + lli 0, rg4 + ieq rg2, rg4, rg5 + jez rg5, _else_2 +_then_1: + lwi str_4, rg4 + stw rg2, spr, 8 + // push arg 0 + push rg4 + call print::print + pop zero + jmp _end_3 +_else_2: + nop +_end_3: + lli 0, rg4 + stw rg4, bpr, 8 + jmp _ret \ No newline at end of file diff --git a/tooling/dscs_compiler/src/backend/intermediate_rep.rs b/tooling/dscs_compiler/src/backend/intermediate_rep.rs new file mode 100644 index 0000000..71645c9 --- /dev/null +++ b/tooling/dscs_compiler/src/backend/intermediate_rep.rs @@ -0,0 +1,102 @@ +use std::fmt; +use std::fmt::Formatter; +use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub enum Statement { + Const(VirtualReg, Literal), + BinaryOp { left: VirtualReg, op: PrimitiveOperation, + right: VirtualReg, result: VirtualReg }, + AddressOf { + value: VirtualReg, result: VirtualReg }, + Dereference { + address: VirtualReg, result: VirtualReg }, + GetStructField { + struct_addr: VirtualReg, result: VirtualReg, offset: usize }, + SetStructField { + struct_addr: VirtualReg, value: VirtualReg, offset: usize }, + Call { + return_value: VirtualReg, func: FunctionName, args: Vec }, +} + +pub struct IRProgram { + pub functions: Vec, +} + +pub struct Function { + pub name: FunctionName, + pub params: Vec, + pub blocks: Vec, +} + +pub struct BasicBlock { + pub label: Label, + pub statements: Vec, + pub terminator: Terminator, +} + +pub enum Terminator { + Jump(Label), + ConditionalJump { if_flag: VirtualReg, then_goto: Label, else_goto: Label }, + Return(VirtualReg), + ReturnVoid, +} + +pub type FunctionName = String; + + +#[derive(Clone)] +pub struct Label { + id: usize, + name: Rc +} + +impl Label { + pub fn new_with_id(name: impl Into, id: usize) -> Self { + Self { id, name: Rc::new(name.into()) } + } + + pub fn new(name: impl Into) -> Self { + Self { id: Self::get_new_id(), name: Rc::new(name.into()) } + } + + pub fn get_new_id() -> usize { + LABEL_ID.fetch_add(1, Ordering::Relaxed) + } +} + +impl fmt::Display for Label { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "__{}_{}", self.name, self.id) + } +} + +static LABEL_ID: AtomicUsize = AtomicUsize::new(0); + + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct VirtualReg(pub isize); + +impl VirtualReg { + pub fn void() -> Self { + Self(-1) + } +} + + +pub enum Literal { + UInt(usize), + Int(isize), + Char(char), +} + +pub enum PrimitiveOperation { + Add, + Sub, + And, + Or, + Xor, + Nor, + Nand +} + diff --git a/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs b/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs new file mode 100644 index 0000000..bf1103d --- /dev/null +++ b/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs @@ -0,0 +1,445 @@ +use std::collections::HashMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use clap::builder::Resettable::Value; +use dsa_tasks::ProcessAgent; +use crate::backend::scope::Allocator; +use crate::model::{Block, Call, CompilerError, Declaration, Expression, Program, Statement, TypeId, Variable}; + +use crate::backend::intermediate_rep as IR; +use crate::backend::intermediate_rep::{BasicBlock, Label, Terminator, VirtualReg}; + +pub struct IrLoweringCodeGenerator { + ast: Program, + + functions: Vec +} + +impl IrLoweringCodeGenerator { + pub fn new(ast: Program) -> Self { + Self { ast, functions: vec![] } + } + + pub fn run(&mut self, ctx: &ProcessAgent) -> Result { + for block in self.ast.clone().declarations { + match block { + Declaration::Variable { ..} => todo!(), + Declaration::Dependency(_) => todo!(), + Declaration::Function { name, ..} => todo!(), + Declaration::Struct { .. } => todo!(), + } + } + + for block in self.ast.clone().declarations { + self.generate_block(&block)?; + } + + self.output(ctx) + } + + pub fn output(&mut self, ctx: &ProcessAgent) -> Result { + todo!() + } + + pub fn generate_block(&mut self, decl: &Declaration) -> Result<(), CompilerError> { + match decl { + Declaration::Function { name, params, body, return_type } => { + let function = self.generate_function(name, params, body, return_type)?; + self.functions.push(function); + }, + Declaration::Struct { .. } => todo!("no codegen required for structs yet"), + Declaration::Dependency(_) => todo!("no codegen required for dependencies yet"), + Declaration::Variable { .. } => todo!("codegen for statics and constants"), + } + + Ok(()) + } + + pub fn generate_function( + &mut self, function_name: &str, + params: &[Variable], + body: &Block, + return_type: &TypeId + ) -> Result { + let mut context = FunctionContext::new(Label::new(function_name.to_string())); + + let params = (0..params.len()) + .map(|_| context.new_vreg()).collect(); + + self.collapse_block(body, &mut context)?; + + let blocks = context.terminate(IR::Terminator::ReturnVoid); + + Ok(IR::Function { + name: function_name.to_string(), params, blocks, + }) + } + + fn collapse_block( + &self, + block: &Block, + ctx: &mut FunctionContext, + ) -> Result<(), CompilerError> { + for statement in block { + self.collapse_statement(statement, ctx)?; + } + Ok(()) + } + + fn collapse_statement( + &self, + stmt: &Statement, + ctx: &mut FunctionContext, + ) -> Result<(), CompilerError> { + match stmt { + Statement::Block(block) => { + self.collapse_block(block, ctx) + } + + Statement::If { condition, then_stmt, else_stmt } => { + /* if statement expansion. produces the following layout + reg_cond = ... expansion of {condition} ... + if reg_cond goto then_{id} else goto else_{id} + then_{id}: + ... expansion of {then_stmt} ... + goto end_{id} + else_{id}: + ... expansion of {else_stmt} ... + goto end_{id} + end_{id}: + ... not our responsibility ... + */ + let id = Label::get_new_id(); + let then_label = Label::new_with_id("then", id); + let else_label = Label::new_with_id("else", id); + let end_label = Label::new_with_id("endif", id); + + let eval_result = self.collapse_expression(condition, ctx)?; + + ctx.next_block( + Terminator::ConditionalJump { + if_flag: eval_result, + then_goto: then_label.clone(), + else_goto: else_label.clone(), + }, + then_label.clone() + ); + + self.collapse_block(then_stmt, ctx)?; + + ctx.next_block( + Terminator::Jump(end_label.clone()), + else_label.clone() + ); + + self.collapse_block(else_stmt, ctx)?; + + ctx.next_block( + Terminator::Jump(end_label.clone()), + end_label.clone() + ); + + Ok(()) + } + + Statement::Defer(call) => { + // defers stmt. all currently in scope defers are called in FILO order on return + ctx.defer(call.clone()); + Ok(()) + }, + + Statement::Return(expr) => { + // return stmt. calls relevant defer methods and expands the body of the return statement. + + let result = if let Some(value) = expr { + self.collapse_expression(value, ctx)? + } else { VirtualReg::void() }; + + // run cleanup logic (defers) + for call in ctx.get_deferred_calls() { + self.collapse_call(&call, ctx)?; + } + + ctx.next_block(Terminator::Return(result), Label::new("unreachable")); + + Ok(()) + } + + Statement::Expression { expr } => { + // generate the code for the expression but discard the result + // let the optimiser remove it if it provably has no side effects. + + let _ = self.collapse_expression(expr, ctx)?; + Ok(()) + } + + Statement::Continue => { + // continue stmt. goes to the 'start' block of the loop + + let Some(scope) = ctx.current_scope() else { + return Err(CompilerError::generic("Invalid 'Continue' statement (not inside any loop)")); + }; + + ctx.next_block(Terminator::Jump(scope.start), Label::new("unreachable")); + Ok(()) + }, + + Statement::Break => { + // break stmt. jumps to the 'end' block of the loop + + let Some(scope) = ctx.current_scope() else { + return Err(CompilerError::generic("Invalid 'Continue' statement (not inside any loop)")); + }; + + ctx.next_block(Terminator::Jump(scope.end), Label::new("unreachable")); + Ok(()) + }, + + Statement::While { condition, body } => { + /* while loop. produces the following layout + goto while_check_{id} + while_check_{id}: + if {condition} goto while_body_{id} else goto while_end_{id} + while_body_{id}: + ... expansion of {body} ... + goto while_check_{id} + while_end_{id}: + ... not our responsibility ... + */ + + let id = Label::get_new_id(); + let check_label = Label::new_with_id("while_check", id); + let body_label = Label::new_with_id("while_body", id); + let end_label = Label::new_with_id("while_end", id); + + ctx.next_block(Terminator::Jump(check_label.clone()), check_label.clone()); + + let result = self.collapse_expression(condition, ctx)?; + + ctx.next_block( + Terminator::ConditionalJump { + if_flag: result, + then_goto: body_label.clone(), + else_goto: end_label.clone() + }, + body_label.clone(), + ); + + self.collapse_block(body, ctx)?; + + ctx.next_block( + Terminator::Jump(check_label.clone()), + end_label.clone() + ); + + Ok(()) + } + + Statement::Loop(block) => { + /* indefinite loop. produces the following layout + goto start_loop_{id} + start_loop_{id}: + goto start_loop_{id} + while_end_{id}: + ... not our responsibility ... + */ + + let id = Label::get_new_id(); + let start_label = Label::new_with_id("start_loop", id); + let end_label = Label::new_with_id("end_loop", id); + + ctx.next_block(Terminator::Jump(start_label.clone()), start_label.clone()); + + ctx.enter_scope(Scope::new(start_label.clone(), end_label.clone())); + + self.collapse_block(block, ctx)?; + + ctx.exit_scope(); + + ctx.next_block(Terminator::Jump(start_label.clone()), end_label); + + Ok(()) + }, + _ => todo!() + } + } + + fn collapse_call( + &self, + call: &Call, + ctx: &mut FunctionContext + ) -> Result { + let reg = ctx.new_vreg(); + + let args = call.args.iter() + .map(|arg| self.collapse_expression(&arg, ctx)) + .collect::, _>>()?; + + ctx.push_stmt(IR::Statement::Call { + return_value: reg, + func: call.name.to_string(), + args + }); + + Ok(reg) + } + + fn collapse_expression( + &self, + expr: &Expression, + ctx: &mut FunctionContext, + ) -> Result { + todo!() + } +} + +struct BasicBlockBuilder { + label: Label, + statements: Vec +} + +impl BasicBlockBuilder { + fn new(label: Label) -> Self { + Self { + label, + statements: vec![] + } + } + + pub fn push(&mut self, statement: IR::Statement) { + self.statements.push(statement); + } + + pub fn next_block(&mut self, terminator: Terminator, label: Label) -> BasicBlock { + BasicBlock { + label: std::mem::replace(&mut self.label, label), + statements: std::mem::take(&mut self.statements), + terminator, + } + } + + pub fn terminate_block(self, terminator: Terminator) -> BasicBlock { + BasicBlock { + label: self.label, + statements: self.statements, + terminator, + } + } +} + +pub struct Scope { + start: Label, + end: Label, + + defers: Vec, + locals: Vec<(VirtualReg, Variable)>, +} + +impl Scope { + pub fn new(start: Label, end: Label) -> Self { + Self { start, end, defers: vec![], locals: vec![] } + } +} + +struct FunctionContext { + entrypoint: Label, + scopes: Vec, + blocks: Vec, + + builder: BasicBlockBuilder, + + v_register_idx: usize, +} + +impl FunctionContext { + pub fn new(entrypoint: Label) -> Self { + Self { + entrypoint: entrypoint.clone(), + blocks: Vec::new(), + scopes: vec![Scope::new(entrypoint.clone(), Label::new("unreachable"))], + builder: BasicBlockBuilder::new(entrypoint), + v_register_idx: 0, + } + } + + pub fn new_vreg(&mut self) -> VirtualReg { + self.v_register_idx += 1; + VirtualReg(self.v_register_idx as isize - 1) + } + + pub fn defer(&mut self, call: Call) { + self.scopes.last_mut().unwrap().defers.push(call); + } + + pub fn new_local(&mut self, variable: Variable) -> VirtualReg { + let vreg = self.new_vreg(); + self.scopes.last_mut().unwrap().locals.push((vreg, variable)); + vreg + } + + pub fn enter_scope(&mut self, scope: Scope) { + self.scopes.push(scope); + } + + pub fn current_scope(&mut self) -> Option { + self.scopes.pop() + } + + pub fn get_deferred_calls(&self) -> Vec { + self.scopes + .iter() + .rev() + .flat_map(|s| + s.defers + .iter() + .cloned() + .rev() + ).collect() + } + + pub fn find_local(&self, var: &Variable) -> Option { + for scope in self.scopes.iter().rev() { + for local in &scope.locals { + if local.1 == *var { + return Some(local.0) + } + } + } + + None + } + + pub fn exit_scope(&mut self) { + self.scopes.pop().expect("(BUG) Cannot exit outermost scope"); + } + + pub fn next_block(&mut self, terminator: Terminator, label: Label) { + self.blocks.push(self.builder.next_block(terminator, label)); + } + + pub fn push_stmt(&mut self, stmt: IR::Statement) { + self.builder.push(stmt); + } + + pub fn terminate(mut self, terminator: Terminator) -> Vec { + self.blocks.push(self.builder.terminate_block(terminator)); + self.blocks + } + + pub fn take(&mut self) -> Vec { + std::mem::take(&mut self.blocks) + } +} + + + + + + + + + + + + + + diff --git a/tooling/dscs_compiler/src/backend/mod.rs b/tooling/dscs_compiler/src/backend/mod.rs index 248c0c2..d5ab46f 100644 --- a/tooling/dscs_compiler/src/backend/mod.rs +++ b/tooling/dscs_compiler/src/backend/mod.rs @@ -5,6 +5,8 @@ mod codegen; mod instruction; mod registers; mod scope; +pub mod intermediate_rep; +pub mod ir_lowering_codegen; pub fn generate_code(ast: &Program, ctx: &ProcessAgent) -> Result { let mut codegen = codegen::CodeGenerator::new(ast.clone()); diff --git a/tooling/dscs_compiler/src/model.rs b/tooling/dscs_compiler/src/model.rs index 7be8051..7deb7e9 100644 --- a/tooling/dscs_compiler/src/model.rs +++ b/tooling/dscs_compiler/src/model.rs @@ -22,6 +22,12 @@ impl fmt::Display for CompilerError { } } +impl CompilerError { + pub fn generic(msg: impl Into) -> Self { + Self::Generic(msg.into()) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct Name { pub name: String,