wrote new "ContextTree" system, emulator filetree is kind of broken for now. next commit will fix this.

This commit is contained in:
2026-08-03 01:45:30 +01:00
parent e6ae60a798
commit 8d28bf39f8
10 changed files with 376 additions and 204 deletions
+2 -4
View File
@@ -3,13 +3,12 @@ use egui_tiles::TileId;
use std::fmt::Debug;
use std::path::PathBuf;
/// the action enum forms the core of the application's state.
/// - Actions are used as the main way that Tiles inside the application pass state updates back to the parent UI,
/// specifically when those state updates involve other files. for example, pressing "build" in the editor would emit
/// a SpawnProcess action, running some task which on completion emits a LoadBinary action on the result
#[derive(Debug)]
pub enum Action {
None,
RequestCloseTab(TileId),
CloseTab(TileId),
Quit,
@@ -18,7 +17,7 @@ pub enum Action {
SaveFile(PathBuf, TileId),
RequestSaveAs(TileId),
ShowError(String),
SpawnProcess(ProcessSpawner)
SpawnProcess(ProcessSpawner),
}
#[derive(Debug, Clone)]
@@ -26,4 +25,3 @@ pub enum DataSource {
File(PathBuf),
Data(Vec<u8>),
}
+19 -16
View File
@@ -17,13 +17,12 @@ use egui_tiles::TileId;
use process_manager::ProcessManager;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::time::Duration;
use tiles::PanelFactory;
use tiles::documentation::Documentation;
use tiles::editor::Editor;
use tiles::io_devices::framebuffer::FrameBufferUi;
use zxq5_egui::file_tree::{FileTree, FileTreeEvent};
use zxq5_egui::context_tree::{ContextTree, NewFileTree};
pub mod appearance;
mod controller;
@@ -45,7 +44,7 @@ pub struct DamnSimpleUI {
show_process_manager: bool,
process_manager: ProcessManager,
file_tree: FileTree,
file_tree: ContextTree<Action, NewFileTree>,
}
impl eframe::App for DamnSimpleUI {
@@ -75,17 +74,13 @@ impl eframe::App for DamnSimpleUI {
});
Panel::left("left_panel").resizable(true).show(ui, |ui| {
let inner_res = self.file_tree.ui(ui, &mut |ui: &mut Ui, path: &Path| {
ui.button("Load Binary")
.clicked()
.then_some(Action::LoadBinary(DataSource::File(path.to_path_buf())))
});
let inner_res = self.file_tree.ui(ui);
if let Some(event) = inner_res.inner {
match event {
FileTreeEvent::Clicked(path) => self.perform_action(Action::OpenFile(path)),
FileTreeEvent::ContextAction(action) => self.perform_action(action),
}
// match event {
// ContextTreeEvent::Clicked(node) => self.perform_action(Action::OpenFile(node)),
// ContextTreeEvent::ContextAction(action, node) => self.perform_action(action),
// }
}
});
@@ -151,15 +146,23 @@ impl DamnSimpleUI {
emulator,
show_process_manager: true,
process_manager: ProcessManager::new(),
file_tree: FileTree::build(&std::env::current_dir().unwrap()),
file_tree: ContextTree::new(
NewFileTree::build(&std::env::current_dir().unwrap()),
|ui, node| match node {
NewFileTree::Leaf { filename } => ui
.button("Open Binary")
.clicked()
.then_some(Action::LoadBinary(DataSource::File(filename.clone()))),
NewFileTree::Branch { .. } => None,
},
),
}
}
pub fn perform_action(&mut self, action: Action) {
match action {
Action::None => {}
Action::SpawnProcess(spawner) => spawner(&mut self.process_manager),
Action::SpawnProcess(spawner) => spawner.run(&mut self.process_manager),
Action::RequestCloseTab(tile_id) => {
self.confirm_dialog.open(
"Close Tab",
+44 -9
View File
@@ -2,11 +2,32 @@ use crate::state::Action;
use crate::ui::ui_helpers::text::strip_ansi;
use dsa_tasks::{ProcessAgent, TraceableProcess};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::sync::mpsc;
use std::thread::JoinHandle;
use zxq5_egui::tabs::painted_tab;
pub type ProcessSpawner = Box<dyn FnOnce(&mut ProcessManager) + Send>;
pub struct ProcessSpawner(Box<dyn FnOnce(&mut ProcessManager) + Send>);
impl Debug for ProcessSpawner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ProcessSpawner")
}
}
impl ProcessSpawner {
pub(crate) fn run(self, process_manager: &mut ProcessManager) {
self.0(process_manager);
}
pub fn new(func: impl FnOnce(&mut ProcessManager) + Send + 'static) -> Self {
Self(Box::new(func))
}
pub fn into_action(self) -> Action {
Action::SpawnProcess(self)
}
}
pub struct ProcessHandle {
id: usize,
@@ -23,10 +44,10 @@ impl ProcessHandle {
id: usize,
name: impl Into<String>,
process: P,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) -> Self {
let (tx, rx) = mpsc::channel::<String>();
let agent = ProcessAgent::new( tx );
let agent = ProcessAgent::new(tx);
let join_handle = std::thread::spawn(move || {
let result = TraceableProcess::run(Box::new(process), agent);
@@ -34,7 +55,11 @@ impl ProcessHandle {
});
Self {
rx, id, name: name.into(), join_handle: Some(join_handle), logs: Vec::new()
rx,
id,
name: name.into(),
join_handle: Some(join_handle),
logs: Vec::new(),
}
}
}
@@ -63,7 +88,12 @@ impl ProcessManager {
self.terminated_actions.pop_front()
}
pub fn spawn<P: TraceableProcess>(&mut self, name: impl Into<String>, process: P, on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static) {
pub fn spawn<P: TraceableProcess>(
&mut self,
name: impl Into<String>,
process: P,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) {
let handle = ProcessHandle::spawn(self.new_id(), name, process, on_complete);
self.handles.push(handle);
}
@@ -79,7 +109,10 @@ impl ProcessManager {
/// Checks for finished processes and queues their resulting Actions.
fn poll_finished(&mut self) {
for handle in &mut self.handles {
let is_finished = handle.join_handle.as_ref().is_some_and(|jh| jh.is_finished());
let is_finished = handle
.join_handle
.as_ref()
.is_some_and(|jh| jh.is_finished());
if is_finished {
if let Some(jh) = handle.join_handle.take() {
if let Ok(action) = jh.join() {
@@ -102,11 +135,13 @@ impl ProcessManager {
}
// keep active_tab pointing at something valid
if !self.active_tab.map_or(false, |id| self.handles.iter().any(|h| h.id == id)) {
if !self
.active_tab
.map_or(false, |id| self.handles.iter().any(|h| h.id == id))
{
self.active_tab = self.handles.first().map(|h| h.id);
}
let mut to_close = None;
ui.horizontal(|ui| {
@@ -147,4 +182,4 @@ impl ProcessManager {
}
}
}
}
}
+43 -55
View File
@@ -1,5 +1,6 @@
use crate::state::Action;
use crate::state::DataSource::Data;
use crate::ui::process_manager::ProcessSpawner;
use crate::ui::tiles::TilePane;
use crate::ui::tiles::editor::syntax::SyntaxFrom;
use assembler::prelude::Assembler;
@@ -30,7 +31,10 @@ impl Default for Editor {
Self {
buffer: String::new(),
filename: None,
editor: CodeEditor::default().with_fontsize(12.0).with_theme(THEME).with_numlines(true),
editor: CodeEditor::default()
.with_fontsize(12.0)
.with_theme(THEME)
.with_numlines(true),
syntax: Syntax::default(),
completer: Completer::default().with_auto_indent().with_user_words(),
}
@@ -70,106 +74,90 @@ impl Editor {
}
}
pub fn create_build_action(&self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static) -> Action {
Action::SpawnProcess(Box::new(move |pm| {
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);
pm.spawn(
builder.label(),
builder,
move |result| match result {
Ok(bin) => on_complete(bin),
Err(e) => Action::ShowError(e.to_string()),
}
);
}))
pm.spawn(builder.label(), builder, move |result| match result {
Ok(bin) => on_complete(bin),
Err(e) => Action::ShowError(e.to_string()),
});
})
.into_action()
}
}
impl TilePane for Editor {
fn title(&self) -> egui::WidgetText {
self.filename.clone()
self.filename
.clone()
.map(|f| f.file_name().unwrap().to_str().unwrap().into())
.unwrap_or("New File*".into())
}
fn ui(&mut self, ui: &mut Ui, tile_id: TileId) -> Option<Action> {
let mut action = Action::None;
let mut action = None;
egui::Panel::top("panel").show(ui, |ui| {
ui.horizontal(|ui| {
if PaddedWidget::button("Build & Load").ui(ui).clicked() {
let Some(filename) = self.filename.clone() else {
action = Action::ShowError("Error - no filename set".to_string());
action = Some(Action::ShowError("Error - no filename set".to_string()));
return;
};
action = self.create_build_action(
filename, |res| {
Action::LoadBinary(Data(res.to_memory_image()))
}
);
action = Some(self.create_build_action(filename, |res| {
Action::LoadBinary(Data(res.to_memory_image()))
}));
}
if PaddedWidget::button("Build").ui(ui).clicked() {
let Some(filename) = self.filename.clone() else {
action = Action::ShowError("Error - no filename set".to_string());
action = Some(Action::ShowError("Error - no filename set".to_string()));
return;
};
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)
}
);
action = Some(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(
self.filename
.as_ref()
.map(|f| f.display().to_string())
.unwrap_or_else(|| String::from("New File"))
).color(Color32::DARK_GRAY).ui(ui);
.unwrap_or_else(|| String::from("New File")),
)
.color(Color32::DARK_GRAY)
.ui(ui);
});
});
self.editor.show_with_completer(ui, &mut self.buffer, &self.syntax, &mut self.completer);
self.editor
.show_with_completer(ui, &mut self.buffer, &self.syntax, &mut self.completer);
if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) {
action = if let Some(filename) = self.filename.clone() {
Action::SaveFile(filename, tile_id)
Some(Action::SaveFile(filename, tile_id))
} else {
Action::RequestSaveAs(tile_id)
Some(Action::RequestSaveAs(tile_id))
}
};
Some(action)
action
}
fn as_any_mut(&mut self) -> &mut dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool {
true
}
}
+42 -13
View File
@@ -1,16 +1,18 @@
pub mod documentation;
pub mod editor;
pub mod about;
pub mod disassembler;
pub mod documentation;
pub mod editor;
pub mod io_devices;
pub mod memory;
pub mod registers;
pub mod io_devices;
use crate::state::Action;
use crate::state::Action::RequestCloseTab;
use eframe::epaint::{Color32, CornerRadius};
use egui::{Id, Response, Stroke, Ui};
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
use egui_tiles::{
Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse,
};
use std::any::Any;
use zxq5_egui::tabs::painted_tab;
@@ -19,7 +21,6 @@ use zxq5_egui::tabs::painted_tab;
/// type after it's been handed off to the tree (e.g. to write into the
/// editor's buffer from an `Action` handler).
pub trait TilePane: Any {
/// the title that's shown on the tab in the UI.
fn title(&self) -> egui::WidgetText;
@@ -75,15 +76,23 @@ impl Behavior<Pane> for TileBehavior {
}
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
println!("on_tab_close_1");
let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else {
return false
println!("on_tab_close_ret1");
return false;
};
println!("on_tab_close_2");
if pane.close_requires_confirmation(tile_id) {
println!("on_tab_close_ret2");
self.pending_action = Some(RequestCloseTab(tile_id));
return false;
}
println!("on_tab_close_3");
tiles.remove(tile_id);
true
}
@@ -112,7 +121,6 @@ impl Behavior<Pane> for TileBehavior {
36.0 // taller tab row = more vertical padding around the label
}
fn simplification_options(&self) -> SimplificationOptions {
SimplificationOptions {
// Keep the tab bar around even with only one tab, and don't let
@@ -122,7 +130,13 @@ impl Behavior<Pane> for TileBehavior {
}
}
fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &Tiles<Box<dyn TilePane>>, _tile_id: TileId, tab_state: &TabState) -> egui::Color32 {
fn tab_bg_color(
&self,
visuals: &egui::Visuals,
_tiles: &Tiles<Box<dyn TilePane>>,
_tile_id: TileId,
tab_state: &TabState,
) -> egui::Color32 {
visuals.widgets.inactive.bg_fill
}
}
@@ -169,7 +183,8 @@ impl CentralTiles {
match self.tree.root {
Some(root) => {
if let Some(Tile::Container(Container::Tabs(tabs))) = self.tree.tiles.get_mut(root) {
if let Some(Tile::Container(Container::Tabs(tabs))) = self.tree.tiles.get_mut(root)
{
tabs.children.push(tile_id);
tabs.active = Some(tile_id);
} else {
@@ -205,9 +220,20 @@ impl CentralTiles {
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<Action> {
self.behavior.pending_action = None;
println!("centraltiles_ui_1");
// self.behavior.pending_action = None;
self.tree.ui(&mut self.behavior, ui);
self.behavior.pending_action.take()
println!("centraltiles_ui_2");
let action = self.behavior.pending_action.take();
println!("centraltiles_ui_3 {action:?}");
action
// self.behavior.pending_action.take()
}
}
@@ -219,6 +245,9 @@ impl PanelFactory {
T: TilePane + 'static,
F: Fn() -> T + 'static,
{
Self(name.to_string(), Box::new(move || Box::new(constructor()) as Box<dyn TilePane>))
Self(
name.to_string(),
Box::new(move || Box::new(constructor()) as Box<dyn TilePane>),
)
}
}
}
+150
View File
@@ -0,0 +1,150 @@
use egui::{InnerResponse, TextBuffer, Widget};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
#[derive(Clone, PartialOrd, Ord, Eq, PartialEq)]
pub enum NewFileTree {
Branch {
path: PathBuf,
children: Vec<NewFileTree>,
},
Leaf {
filename: PathBuf,
},
}
impl NewFileTree {
pub fn build(path: &Path) -> Self {
match path.is_dir() {
true => Self::Branch {
path: path.to_path_buf(),
children: {
let mut children: Vec<Self> = std::fs::read_dir(path)
.map(|ok| {
ok.filter_map(|file| file.ok())
.map(|file| Self::build(&path.join(file.file_name())))
.collect()
})
.unwrap_or_default();
children.sort_unstable();
children
},
},
false => Self::Leaf {
filename: path.to_path_buf(),
},
}
}
}
impl TreeView for NewFileTree {
fn node(&self) -> Node<'_, Self> {
match self {
Self::Branch { path, children } => Node::Branch {
name: path.file_name().unwrap().to_string_lossy(),
children,
},
Self::Leaf { filename } => Node::Leaf {
name: filename.file_name().unwrap().to_string_lossy(),
},
}
}
}
pub enum ContextTreeEvent<'t, A, T: TreeView> {
Clicked(Node<'t, T>),
ContextAction(A, Node<'t, T>),
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Node<'t, T: TreeView> {
Leaf {
name: Cow<'t, str>,
},
Branch {
name: Cow<'t, str>,
children: &'t Vec<T>,
},
}
pub trait TreeView: Sized + Clone {
fn node(&self) -> Node<'_, Self>;
}
pub struct ContextTree<A, T: TreeView> {
root: T,
context: Box<dyn FnMut(&mut egui::Ui, &T) -> Option<A>>,
}
impl<Action, Tree> ContextTree<Action, Tree>
where
Tree: TreeView,
{
type Ctx = Box<dyn FnMut(&mut egui::Ui, &Tree) -> Option<Action>>;
pub fn new<T: FnMut(&mut egui::Ui, &Tree) -> Option<Action> + 'static>(
root: Tree,
context: T,
) -> Self {
Self {
root,
context: Box::new(context),
}
}
fn ctx_menu<'t>(
ui: &mut egui::Ui,
ctx: &mut Self::Ctx,
tree: &'t Tree,
) -> Option<ContextTreeEvent<'t, Action, Tree>> {
ctx(ui, tree).and_then(|a| Some(ContextTreeEvent::ContextAction(a, tree.node())))
}
pub fn ui(
&mut self,
ui: &mut egui::Ui,
) -> InnerResponse<Option<ContextTreeEvent<'_, Action, Tree>>> {
let mut action = None;
let frame_response = egui::Frame::new().show(ui, |ui| {
egui::ScrollArea::both()
.auto_shrink([false; 2])
.show(ui, |ui| {
Self::ui_inner(ui, &mut action, &mut self.context, &self.root);
})
});
InnerResponse::new(action, frame_response.response)
}
fn ui_inner<'ctx, 't>(
ui: &mut egui::Ui,
action: &mut Option<ContextTreeEvent<'t, Action, Tree>>,
ctx: &mut Self::Ctx,
tree: &'t Tree,
) {
match tree.node() {
Node::Leaf { ref name } => {
let response = egui::Button::new(name.as_str()).frame(false).ui(ui);
response.context_menu(|ui| *action = Self::ctx_menu(ui, ctx, tree));
if response.clicked() {
*action = Some(ContextTreeEvent::Clicked(tree.node()));
}
}
Node::Branch {
ref name,
ref children,
} => {
let show_response = egui::CollapsingHeader::new(name.as_str()).show(ui, |ui| {
for child in children.iter() {
Self::ui_inner(ui, action, ctx, child);
}
});
show_response
.header_response
.context_menu(|ui| *action = Self::ctx_menu(ui, ctx, tree));
}
}
}
}
+9 -5
View File
@@ -1,4 +1,4 @@
use egui::{CollapsingHeader, InnerResponse};
use egui::{CollapsingHeader, InnerResponse, Widget};
use std::cmp::Ordering;
use std::path::{Path, PathBuf};
@@ -70,15 +70,19 @@ impl FileTree {
) {
match self {
Self::Leaf { filename } => {
let response = ui.button(filename.file_name().unwrap().to_string_lossy());
if response.clicked() {
*action = Some(FileTreeEvent::Clicked(PathBuf::from(filename)));
}
let response = egui::Button::new(filename.file_name().unwrap().to_string_lossy())
.frame(false)
.ui(ui);
response.context_menu(|ui| {
if let Some(a) = ctx(ui, filename) {
*action = Some(FileTreeEvent::ContextAction(a));
}
});
if response.clicked() {
*action = Some(FileTreeEvent::Clicked(PathBuf::from(filename)));
}
}
Self::Branch { path, children } => {
CollapsingHeader::new(path.file_name().unwrap().to_string_lossy())
+6 -1
View File
@@ -1,3 +1,8 @@
#![feature(trait_alias)]
#![feature(str_as_str)]
#![feature(inherent_associated_types)]
pub mod buttons;
pub mod tabs;
pub mod context_tree;
pub mod file_tree;
pub mod tabs;
+23 -67
View File
@@ -4,17 +4,11 @@ use crate::{assembler::AssembleError, expect_token};
use common::prelude::*;
use dsa_tasks::ProcessAgent;
pub fn codegen(nodes: Vec<Node>, ctx: &ProcessAgent) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![];
for node in nodes {
ctx.log(format!("{}", node));
instructions.push(build_instruction(&node)?);
}
ctx.log("Assembly Successful ✅");
Ok(instructions)
pub fn codegen(nodes: &[Node], ctx: &ProcessAgent) -> Result<Vec<Instruction>, AssembleError> {
nodes
.iter()
.map(build_instruction)
.collect::<Result<Vec<_>, _>>()
}
fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
@@ -41,6 +35,7 @@ fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
ins_jump_unconditional(opcode, &args)
}
Opcode::Jez | Opcode::Jnz => ins_jump_conditional(opcode, &args),
Opcode::Shl | Opcode::Shr => ins_bitshift(opcode, &args),
Opcode::Add
@@ -72,10 +67,7 @@ fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
}
}
fn ins_mov(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_mov(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -92,10 +84,7 @@ fn ins_mov(
}
}
fn ins_cmov(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_cmov(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -116,10 +105,7 @@ fn ins_cmov(
}
}
fn ins_ldx_stx(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_ldx_stx(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -147,10 +133,7 @@ fn ins_ldx_stx(
}
}
fn ins_stack(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_stack(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -163,10 +146,7 @@ fn ins_stack(
}
}
fn ins_load_imm(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_load_imm(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(value_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -184,10 +164,7 @@ fn ins_load_imm(
}
}
fn ins_jump_unconditional(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_jump_unconditional(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(addr_imm_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -206,21 +183,16 @@ fn ins_jump_unconditional(
}
}
fn ins_jump_conditional(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_jump_conditional(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(condition_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(addr_imm_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let addr_reg_token = if let Some(token) = args.get(2) {
token
} else {
&Token::Register(Register::Zero)
};
let addr_reg_token = args
.get(2)
.map_or(&Token::Register(Register::Zero), |token| token);
let condition = expect_token!(condition_token, Register)?;
let addr_imm = expect_token!(addr_imm_token, Immediate)?;
@@ -233,10 +205,7 @@ fn ins_jump_conditional(
})
}
fn ins_comparison(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_comparison(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -261,10 +230,7 @@ fn ins_comparison(
})
}
fn ins_bitshift(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_bitshift(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(src_reg) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -290,10 +256,7 @@ fn ins_bitshift(
})
}
fn ins_arithmetic(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_arithmetic(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -321,10 +284,7 @@ fn ins_arithmetic(
})
}
fn ins_imm_arithmetic(
opcode: Opcode,
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn ins_imm_arithmetic(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -368,9 +328,7 @@ fn ins_interrupt(args: &[Token]) -> Result<Instruction, AssembleError> {
Ok(Instruction::int(code))
}
fn build_data_instruction(
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn build_data_instruction(args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -379,9 +337,7 @@ fn build_data_instruction(
Ok(Instruction::data(immediate))
}
fn build_segment_instruction(
args: &[Token],
) -> Result<Instruction, AssembleError> {
fn build_segment_instruction(args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
@@ -390,6 +346,6 @@ fn build_segment_instruction(
Ok(Instruction::data(
// mask the first 6 bits for the opcode
// this is really deprecated tbh. may remove in future.
Opcode::Segment as u32 | (immediate & 0x05FFFFFF),
Opcode::Segment as u32 | (immediate & 0x05FF_FFFF),
))
}
+38 -34
View File
@@ -1,15 +1,16 @@
#![allow(dead_code, unused)]
use dsa_tasks::{ProcessAgent, TraceableProcess};
use crate::assembler::resolver2::{SectionId, resolve_address, resolve_dependencies, resolve_symbols, split_sections};
use crate::assembler::resolver2::{
SectionId, resolve_address, resolve_dependencies, resolve_symbols, split_sections,
};
use dsa_formats::binary_dse::{DseExecutable, SymbolEntry};
use dsa_tasks::{ProcessAgent, TraceableProcess};
use std::{
collections::HashSet,
fmt, fs,
hash::{DefaultHasher, Hash, Hasher},
path::{Path, PathBuf},
sync::
mpsc::{self},
sync::mpsc::{self},
};
// Module declarations
@@ -64,6 +65,16 @@ impl TraceableProcess for Assembler {
fn run(mut self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error> {
assemble(&self.src_path, &ctx)
}
fn label(&self) -> String {
format!(
"Assembling {}",
self.src_path
.file_name()
.expect("a valid path must have a file name")
.to_string_lossy()
)
}
}
fn assemble(src: &Path, ctx: &ProcessAgent) -> Result<DseExecutable, AssembleError> {
@@ -74,21 +85,14 @@ fn assemble(src: &Path, ctx: &ProcessAgent) -> Result<DseExecutable, AssembleErr
let nodes = program.nodes.clone();
// for node in &nodes {
// ctx.log(format!("{:?}", node));
// }
let (data_nodes, mut text_nodes) = split_sections(nodes);
let symbol_table = resolve_symbols(&data_nodes, &mut text_nodes)?;
let text_len = text_nodes.len() as u32;
ctx.log("Generating assembly output...");
ctx.log("Generating Instructions...");
// println!("Generating assembly output...");
let instructions = codegen(text_nodes, ctx)?;
let text: Vec<u32> = instructions
.iter()
.map(|i| i.0).collect();
let instructions = codegen(&text_nodes, ctx)?;
let text: Vec<u32> = instructions.iter().map(|i| i.0).collect();
let data = build_data_bytes(&data_nodes)?;
@@ -117,20 +121,16 @@ fn assemble(src: &Path, ctx: &ProcessAgent) -> Result<DseExecutable, AssembleErr
.iter()
.find(|(sym, _)| sym.name == "_init")
.map(|(_, entry)| resolve_address(*entry, text_len))
.ok_or(AssembleError::UndefinedSymbol(Symbol {
name: "_init".to_string(),
module: Module::Resolved(0),
}))?;
.ok_or_else(|| {
AssembleError::UndefinedSymbol(Symbol {
name: "_init".to_string(),
module: Module::Resolved(0),
})
})?;
ctx.log("Compilation Successful");
Ok(DseExecutable::new(
symbols.as_slice(),
&string_table,
&data,
&text,
entry_point,
).unwrap())
Ok(DseExecutable::new(symbols.as_slice(), &string_table, &data, &text, entry_point).unwrap())
}
fn build_data_bytes(data_nodes: &[Node]) -> Result<Vec<u8>, AssembleError> {
@@ -144,7 +144,6 @@ fn build_data_bytes(data_nodes: &[Node]) -> Result<Vec<u8>, AssembleError> {
Ok(bytes)
}
fn prepare_dependency(
path: &Path,
modules: &mut HashSet<u64>,
@@ -157,21 +156,26 @@ fn prepare_dependency(
.expect("Failed to get file name from path");
if let Ok(path) = path.canonicalize() {
ctx.log(format!("{:20} {:20} [{}]", "Building", filename, path.display()));
ctx.log(format!(
"{:30} {:20} [{}]",
"Building",
filename,
path.display()
));
}
let src =
fs::read_to_string(path).map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
let file_hash = quick_hash(path);
ctx.log(format!("{:20} {:20}", "Tokenising", filename));
ctx.log(format!("{:30} {:20}", "Lexing", filename));
let tokens = lexer::lexer(src, file_hash)?;
// let tokens = Lexer::new(src, file_hash).run()?;
ctx.log(format!("{:20} {:20}", "Parsing", filename));
ctx.log(format!("{:30} {:20}", "Parsing", filename));
let parsed = Parser::parse_nodes(tokens)?;
ctx.log(format!("{:20} {:20}", "Resolving Deps", filename));
ctx.log(format!("{:30} {:20}", "Resolving Dependencies", filename));
// Get the parent directory of the source file to use as the base directory
let base_dir = path
.parent()
@@ -181,20 +185,20 @@ fn prepare_dependency(
let deps = Parser::get_dependencies(&nodes, path)?;
ctx.log(format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
ctx.log(format!("{:30} {:20}", "Expanding Macros", filename));
// add a section instruction
nodes.insert(
0,
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
);
program.add_module(nodes);
for dep in deps {
ctx.log(format!(
"{:20} {:20}",
"Including",
"Including Dependency",
dep.file_name()
.and_then(|f| f.to_str())
.expect("Dependency path has no file name or is not valid UTF-8")
@@ -202,7 +206,7 @@ fn prepare_dependency(
let dep_hash = quick_hash(&dep);
if modules.insert(dep_hash) {
prepare_dependency(dep.as_path(), modules, program, &ctx)?;
prepare_dependency(dep.as_path(), modules, program, ctx)?;
}
}