wrote new "ContextTree" system, emulator filetree is kind of broken for now. next commit will fix this.
This commit is contained in:
@@ -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>),
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user