rewrote assembler backend to support custom executable format (DSE) and also refactored (moving crates around)
This commit is contained in:
Generated
+7
-3
@@ -7,9 +7,13 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/compiler/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/linker/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/assembler/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/backend/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/frontend/benches" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/frontend/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/disassembler/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/emulator_core/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/emulator_ui/benches" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/emulator_ui/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_client/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_common/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_server/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["dsa/common", "dsa/assembler", "dsa/emulator/frontend", "dsa/linker", "dsa/compiler", "dsa/emulator/backend"]
|
||||
members = ["dsa/common", "dsa/assembler", "dsa/emulator/emulator_ui", "dsa/linker", "dsa/compiler", "dsa/emulator/emulator_core", "dsx/dsx_common", "dsx/dsx_server", "dsx/dsx_client", "dsa/disassembler"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@@ -13,6 +13,7 @@ name = "assembler"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.15.1"
|
||||
clap = { version = "4.6.0", features = ["derive"] }
|
||||
common = { path = "../common" }
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
|
||||
|
||||
fn ins_mov(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -98,7 +98,7 @@ fn ins_mov(
|
||||
|
||||
fn ins_cmov(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -122,7 +122,7 @@ fn ins_cmov(
|
||||
|
||||
fn ins_ldx_stx(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -153,7 +153,7 @@ fn ins_ldx_stx(
|
||||
|
||||
fn ins_stack(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(reg_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -169,7 +169,7 @@ fn ins_stack(
|
||||
|
||||
fn ins_load_imm(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(value_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -190,7 +190,7 @@ fn ins_load_imm(
|
||||
|
||||
fn ins_jump_unconditional(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(addr_imm_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -212,7 +212,7 @@ fn ins_jump_unconditional(
|
||||
|
||||
fn ins_jump_conditional(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(condition_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -239,7 +239,7 @@ fn ins_jump_conditional(
|
||||
|
||||
fn ins_comparison(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(left_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -267,7 +267,7 @@ fn ins_comparison(
|
||||
|
||||
fn ins_bitshift(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_reg) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -296,7 +296,7 @@ fn ins_bitshift(
|
||||
|
||||
fn ins_arithmetic(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(left_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -327,7 +327,7 @@ fn ins_arithmetic(
|
||||
|
||||
fn ins_imm_arithmetic(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(reg_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -350,7 +350,7 @@ fn ins_imm_arithmetic(
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_not(args: &[crate::assembler::model::Token]) -> Result<Instruction, AssembleError> {
|
||||
fn ins_not(args: &[Token]) -> Result<Instruction, AssembleError> {
|
||||
let Some(reg_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
@@ -363,7 +363,7 @@ fn ins_not(args: &[crate::assembler::model::Token]) -> Result<Instruction, Assem
|
||||
Ok(Instruction::not(src, dest))
|
||||
}
|
||||
|
||||
fn ins_interrupt(args: &[crate::assembler::model::Token]) -> Result<Instruction, AssembleError> {
|
||||
fn ins_interrupt(args: &[Token]) -> Result<Instruction, AssembleError> {
|
||||
let Some(code_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
@@ -373,7 +373,7 @@ fn ins_interrupt(args: &[crate::assembler::model::Token]) -> Result<Instruction,
|
||||
}
|
||||
|
||||
fn build_data_instruction(
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(immediate_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
@@ -384,7 +384,7 @@ fn build_data_instruction(
|
||||
}
|
||||
|
||||
fn build_segment_instruction(
|
||||
args: &[crate::assembler::model::Token],
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(immediate_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
|
||||
@@ -11,8 +11,11 @@ use std::{
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
use common::formats::binary_dse::{DseExecutable, SymbolEntry};
|
||||
use common::prelude::Instruction;
|
||||
use crate::assembler::resolver2::{resolve_address, resolve_dependencies, resolve_symbols, split_sections, SectionId};
|
||||
|
||||
// Module declarations
|
||||
#[macro_use]
|
||||
@@ -21,12 +24,14 @@ pub mod macros;
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod codegen;
|
||||
pub mod expand;
|
||||
pub mod lexer;
|
||||
pub mod model;
|
||||
pub mod parser;
|
||||
pub mod resolver;
|
||||
|
||||
use crate::assemblerv2::lexer::Lexer;
|
||||
mod lexer;
|
||||
mod model;
|
||||
pub mod parser;
|
||||
// pub mod resolver;
|
||||
pub mod resolver2;
|
||||
|
||||
|
||||
|
||||
// Re-exports
|
||||
pub use self::{
|
||||
@@ -35,7 +40,7 @@ pub use self::{
|
||||
lexer::lexer,
|
||||
model::{Module, Node, Opcode, Symbol, Token, TokenType},
|
||||
parser::{Parser, Program},
|
||||
resolver::{create_sections, resolve_dependencies, resolve_symbols},
|
||||
// resolver::{create_sections, resolve_dependencies, resolve_symbols},
|
||||
};
|
||||
|
||||
pub struct Assembler {
|
||||
@@ -67,23 +72,33 @@ impl Assembler {
|
||||
let tx = self.result_tx.clone();
|
||||
|
||||
thread::spawn(move || match assemble(&src) {
|
||||
Ok(res) => {
|
||||
let buffer: Vec<u8> = res
|
||||
.iter()
|
||||
.flat_map(|instruction| instruction.to_le_bytes())
|
||||
.collect();
|
||||
tx.send(Ok(buffer))
|
||||
.expect("Failed to send compilation result from worker thread");
|
||||
}
|
||||
Err(err) => {
|
||||
tx.send(Err(err))
|
||||
.expect("Failed to send compilation error from worker thread");
|
||||
Ok(exe) => {
|
||||
let mut buffer = Cursor::new(Vec::new());
|
||||
match exe.write(&mut buffer) {
|
||||
Ok(()) => {
|
||||
tx.send(Ok(buffer.into_inner()))
|
||||
.expect("Failed to send compilation result from worker thread");
|
||||
}
|
||||
Err(e) => {
|
||||
tx.send(Err(AssembleError::Generic))
|
||||
.expect("...");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => { /* unchanged */ }
|
||||
});
|
||||
|
||||
self.is_running = true;
|
||||
}
|
||||
|
||||
/// Polls the result channel to check if a compilation result is ready.
|
||||
///
|
||||
/// This method attempts a non-blocking read (`try_recv`) from the result receiver.
|
||||
/// It returns `Some(Ok(Vec<u8>))` if the compilation successfully finished and
|
||||
/// the binary output is available, or `None` if no result was received yet.
|
||||
///
|
||||
/// If it receives an error via `mpsc::TryRecvError::Disconnected`, it indicates that
|
||||
/// the worker thread terminated unexpectedly, returning a specific error string.
|
||||
pub fn poll(&mut self) -> Option<Result<Vec<u8>, String>> {
|
||||
if !self.is_running {
|
||||
return None;
|
||||
@@ -130,36 +145,114 @@ impl Assembler {
|
||||
|
||||
impl Assembler {}
|
||||
|
||||
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
||||
// fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
||||
// let mut modules = HashSet::new();
|
||||
// let mut program = Program::new();
|
||||
//
|
||||
// let hash = quick_hash(src);
|
||||
//
|
||||
// if modules.contains(&hash) {
|
||||
// return Ok(vec![]);
|
||||
// }
|
||||
//
|
||||
// prepare_dependency(src, &mut modules, &mut program)?;
|
||||
//
|
||||
// let mut nodes = program.nodes.clone();
|
||||
//
|
||||
// println!("PRE LINK");
|
||||
// for node in &nodes {
|
||||
// println!("{:?}", node);
|
||||
// }
|
||||
//
|
||||
// create_sections(&mut nodes)?;
|
||||
// resolve_symbols(&mut nodes)?;
|
||||
//
|
||||
// println!("Generating assembly output...");
|
||||
//
|
||||
// let instructions = codegen(nodes)?;
|
||||
//
|
||||
// println!("Compilation Successful");
|
||||
// Ok(instructions)
|
||||
// }
|
||||
|
||||
fn assemble(src: &Path) -> Result<DseExecutable, AssembleError> {
|
||||
let mut modules = HashSet::new();
|
||||
let mut program = Program::new();
|
||||
|
||||
let hash = quick_hash(src);
|
||||
|
||||
if modules.contains(&hash) {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
prepare_dependency(src, &mut modules, &mut program)?;
|
||||
|
||||
let mut nodes = program.nodes.clone();
|
||||
let nodes = program.nodes.clone();
|
||||
|
||||
println!("PRE LINK");
|
||||
for node in &nodes {
|
||||
println!("{:?}", node);
|
||||
}
|
||||
|
||||
create_sections(&mut nodes)?;
|
||||
resolve_symbols(&mut nodes)?;
|
||||
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;
|
||||
|
||||
println!("Generating assembly output...");
|
||||
let instructions = codegen(text_nodes)?;
|
||||
let text: Vec<u32> = instructions
|
||||
.iter()
|
||||
.map(|i| i.0).collect();
|
||||
|
||||
let instructions = codegen(nodes)?;
|
||||
let data = build_data_bytes(&data_nodes)?;
|
||||
|
||||
// Build string table + SymbolEntry list from the resolved symbol table
|
||||
let mut string_table = Vec::new();
|
||||
let mut symbols = Vec::new();
|
||||
for (symbol, entry) in &symbol_table {
|
||||
let name_offset = string_table.len() as u32;
|
||||
string_table.extend_from_slice(symbol.name.as_bytes());
|
||||
string_table.push(0); // null terminator
|
||||
|
||||
symbols.push(SymbolEntry {
|
||||
name_offset,
|
||||
value: resolve_address(*entry, text_len),
|
||||
section: match entry.1 {
|
||||
SectionId::Text => 2,
|
||||
SectionId::Data => 1,
|
||||
},
|
||||
binding: 1, // global — refine later if adding local/weak
|
||||
_pad: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Entry point: first symbol to jump to is '_init'
|
||||
let entry_point = symbol_table
|
||||
.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),
|
||||
}))?;
|
||||
|
||||
println!("Compilation Successful");
|
||||
Ok(instructions)
|
||||
|
||||
Ok(DseExecutable::new(
|
||||
symbols.as_slice(),
|
||||
&string_table,
|
||||
&data,
|
||||
&text,
|
||||
entry_point,
|
||||
).unwrap())
|
||||
}
|
||||
|
||||
fn build_data_bytes(data_nodes: &[Node]) -> Result<Vec<u8>, AssembleError> {
|
||||
let mut bytes = Vec::with_capacity(data_nodes.len() * 4);
|
||||
for node in data_nodes {
|
||||
let Token::Immediate(value) = node.arg(0)? else {
|
||||
return Err(AssembleError::InvalidArg);
|
||||
};
|
||||
bytes.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
|
||||
fn prepare_dependency(
|
||||
path: &Path,
|
||||
modules: &mut HashSet<u64>,
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
|
||||
use crate::assembler::{quick_hash, AssembleError};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SectionId {
|
||||
Data,
|
||||
Text,
|
||||
}
|
||||
|
||||
/// Splits a flat (possibly multi-module) node list into separate data and
|
||||
/// text streams. Segment/Include markers are dropped here — module
|
||||
/// disambiguation lives on `Symbol.module`, not node position, so we don't
|
||||
/// need them past this point.
|
||||
pub fn split_sections(nodes: Vec<Node>) -> (Vec<Node>, Vec<Node>) {
|
||||
let mut data = Vec::new();
|
||||
let mut text = Vec::new();
|
||||
|
||||
for node in nodes {
|
||||
match node.opcode() {
|
||||
Opcode::Data => data.push(node),
|
||||
Opcode::Segment | Opcode::Include => {}
|
||||
_ => text.push(node),
|
||||
}
|
||||
}
|
||||
|
||||
(data, text)
|
||||
}
|
||||
|
||||
pub fn generate_symbol_table(
|
||||
data_nodes: &[Node],
|
||||
text_nodes: &[Node],
|
||||
) -> HashMap<Symbol, (u32, SectionId)> {
|
||||
let mut table = HashMap::new();
|
||||
|
||||
for (i, node) in data_nodes.iter().enumerate() {
|
||||
if let Some(symbol) = node.label() {
|
||||
table.insert(symbol, (4 * i as u32, SectionId::Data));
|
||||
}
|
||||
}
|
||||
for (i, node) in text_nodes.iter().enumerate() {
|
||||
if let Some(symbol) = node.label() {
|
||||
table.insert(symbol, (4 * i as u32, SectionId::Text));
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
/// v1 loader convention: TEXT is placed at address 0, DATA immediately
|
||||
/// follows it. This is a stopgap until real relocations exist — it's the
|
||||
/// same assumption your current jmp-hack already makes, just explicit now.
|
||||
pub fn resolve_address((offset, section): (u32, SectionId), text_len_words: u32) -> u32 {
|
||||
match section {
|
||||
SectionId::Text => offset,
|
||||
SectionId::Data => (text_len_words * 4) + offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Patches every symbol reference in `text_nodes` with its resolved absolute
|
||||
/// address, and returns the symbol table so the caller can build the
|
||||
/// executable's symbol/string tables from it.
|
||||
pub fn resolve_symbols(
|
||||
data_nodes: &[Node],
|
||||
text_nodes: &mut [Node],
|
||||
) -> Result<HashMap<Symbol, (u32, SectionId)>, AssembleError> {
|
||||
let symbol_table = generate_symbol_table(data_nodes, text_nodes);
|
||||
let text_len = text_nodes.len() as u32;
|
||||
|
||||
for node in text_nodes.iter_mut() {
|
||||
match node.opcode() {
|
||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc | Opcode::Lli | Opcode::Lui => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument 0 for jump-like opcode")
|
||||
{
|
||||
let Some(entry) = symbol_table.get(&symbol) else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
};
|
||||
node.tokens[0] = Token::Immediate(resolve_address(*entry, text_len));
|
||||
}
|
||||
}
|
||||
Opcode::Jez | Opcode::Jnz => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(1)
|
||||
.expect("Expected argument 1 for jump-like opcode")
|
||||
{
|
||||
let Some(entry) = symbol_table.get(&symbol) else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
};
|
||||
node.tokens[1] = Token::Immediate(resolve_address(*entry, text_len));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(symbol_table)
|
||||
}
|
||||
|
||||
// resolve_dependencies is unchanged — keep exactly as you have it.
|
||||
pub fn resolve_dependencies(
|
||||
mut nodes: Vec<Node>,
|
||||
base_dir: &Path,
|
||||
) -> Result<Vec<Node>, AssembleError> {
|
||||
// First we get a list of imports.
|
||||
let mut dependencies = Vec::new();
|
||||
for node in &nodes {
|
||||
if node.opcode() == Opcode::Include {
|
||||
// we want the path, and the name
|
||||
let name = if let Token::Symbol(name) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument #0 for Include directive.")
|
||||
{
|
||||
name.name.clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
}; //node.2.get(0).unwrap()
|
||||
|
||||
let Ok(Token::StringLit(path)) = node.arg(1) else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let full_path = base_dir.join(path);
|
||||
let canonical_path = full_path
|
||||
.canonicalize()
|
||||
.map_err(|_| AssembleError::InvalidFile(full_path.clone()))?;
|
||||
|
||||
let hash = quick_hash(&canonical_path);
|
||||
|
||||
dependencies.push((name, hash));
|
||||
}
|
||||
}
|
||||
|
||||
let mut changes = Vec::<(u32, u32, Symbol)>::new();
|
||||
// now we resolve the symbols on all the nodes
|
||||
// we need to check all operands for unresolved signals
|
||||
for (i, node) in nodes.clone().iter().enumerate() {
|
||||
let Node {
|
||||
tokens: operands, ..
|
||||
} = node;
|
||||
for (j, token) in operands.iter().enumerate() {
|
||||
if let Token::Symbol(symbol) = token {
|
||||
for d in &dependencies {
|
||||
if let Module::Unresolved(name) = symbol.module.clone() {
|
||||
if name != d.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let symbol = Symbol {
|
||||
name: symbol.name.clone(),
|
||||
module: Module::Resolved(d.1),
|
||||
};
|
||||
changes.push((i as u32, j as u32, symbol));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, j, symbol) in changes {
|
||||
nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol);
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct BuildContext {
|
||||
filename: PathBuf,
|
||||
stage: BuildStage,
|
||||
// Logging
|
||||
}
|
||||
|
||||
impl BuildContext {
|
||||
pub fn new(filename: PathBuf) -> Self {
|
||||
Self {
|
||||
filename,
|
||||
stage: BuildStage::Lexing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BuildStage {
|
||||
Lexing,
|
||||
Parsing,
|
||||
PseudoExpansion,
|
||||
Linking,
|
||||
CodeGeneration,
|
||||
}
|
||||
@@ -2,7 +2,8 @@ use std::{str::FromStr, thread, time::Duration};
|
||||
|
||||
use common::{asm::AsmOpcode, prelude::Register};
|
||||
|
||||
use crate::assembler::{AssembleError, Module, Opcode, Symbol, Token, lexer::parse_opcode};
|
||||
use crate::assembler::{AssembleError, Module, Symbol};
|
||||
use crate::assemblerv2::Token;
|
||||
|
||||
pub struct Lexer {
|
||||
src: Vec<u8>,
|
||||
@@ -75,7 +76,7 @@ impl Lexer {
|
||||
buffer.push(self.src.pop().unwrap() as char);
|
||||
}
|
||||
|
||||
if let Ok(opcode) = Opcode::from_str(&buffer) {
|
||||
if let Ok(opcode) = AsmOpcode::from_str(&buffer) {
|
||||
tokens.push(Token::Opcode(opcode));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
// mod error;
|
||||
pub mod lexer;
|
||||
// pub mod parser;
|
||||
mod error;
|
||||
mod lexer;
|
||||
mod parser;
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::assembler::{AssembleError, Symbol};
|
||||
// use common::{asm::AsmOpcode, prelude::Register};
|
||||
use common::{asm::AsmOpcode, prelude::Register};
|
||||
use lexer::Lexer;
|
||||
// use parser::Parser;
|
||||
use parser::Parser;
|
||||
|
||||
pub fn asm(input: &str) -> Result<Vec<u8>, AssembleError> {
|
||||
let mut lexer = Lexer::new(input.to_string(), 0);
|
||||
|
||||
let tokens = lexer.run()?;
|
||||
|
||||
// let ast = Parser::new(tokens).parse().unwrap();
|
||||
let ast = Parser::new(tokens).parse().unwrap();
|
||||
|
||||
// println!("{:#?}", ast);
|
||||
|
||||
// let ast = parser::parse(tokens)?;
|
||||
println!("{:#?}", ast);
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone)]
|
||||
// pub enum Token {
|
||||
// Symbol(Symbol),
|
||||
// Register(Register),
|
||||
// Immediate(u32),
|
||||
// StringLit(String),
|
||||
// CharLit(char),
|
||||
// Opcode(AsmOpcode),
|
||||
// }
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
Symbol(Symbol),
|
||||
Register(Register),
|
||||
Immediate(u32),
|
||||
StringLit(String),
|
||||
CharLit(char),
|
||||
Opcode(AsmOpcode),
|
||||
}
|
||||
|
||||
// impl fmt::Display for Token {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// match self {
|
||||
// Self::Symbol(symbol) => write!(f, "{symbol}"),
|
||||
// Self::Register(register) => write!(f, "{register}",),
|
||||
// Self::Immediate(immediate) => write!(f, "{immediate}",),
|
||||
// Self::StringLit(string_lit) => write!(f, "{string_lit}",),
|
||||
// Self::CharLit(char_lit) => write!(f, "{char_lit}",),
|
||||
// Self::Opcode(opcode) => write!(f, "{opcode}",),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Symbol(symbol) => write!(f, "{symbol}"),
|
||||
Self::Register(register) => write!(f, "{register}",),
|
||||
Self::Immediate(immediate) => write!(f, "{immediate}",),
|
||||
Self::StringLit(string_lit) => write!(f, "{string_lit}",),
|
||||
Self::CharLit(char_lit) => write!(f, "{char_lit}",),
|
||||
Self::Opcode(opcode) => write!(f, "{opcode}",),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ use std::path::PathBuf;
|
||||
|
||||
use common::asm::{AsmInstruction, AsmOpcode};
|
||||
|
||||
use crate::assembler::Symbol;
|
||||
use crate::assembler::{Symbol};
|
||||
|
||||
use crate::{expect_tt, expect_value};
|
||||
|
||||
use super::{Token, error::AssembleError};
|
||||
use super::{error::AssembleError, Token};
|
||||
|
||||
pub struct AsmNode {
|
||||
pub label: Option<Symbol>,
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::fs::File;
|
||||
use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite};
|
||||
|
||||
use common::prelude::Instruction;
|
||||
use crate::assembler::Symbol;
|
||||
|
||||
pub fn disassemble(in_path: PathBuf, out_path: PathBuf) -> Result<(), String> {
|
||||
|
||||
let Ok(content) = fs::read(in_path) else {
|
||||
return Err(String::new())
|
||||
};
|
||||
|
||||
for (line_num, chunk) in content.chunks(4).enumerate() {
|
||||
let address = line_num * 4;
|
||||
|
||||
let value = u32::from_le_bytes(*chunk.as_array().unwrap());
|
||||
println!("{:#?}", Instruction(value));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[binrw]
|
||||
#[brw(magic = b"DSAX", little)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct DseHeader {
|
||||
version: u16,
|
||||
flags: u16,
|
||||
entry_point: u32,
|
||||
|
||||
symbol_table_offset: u32,
|
||||
symbol_table_size: u32,
|
||||
|
||||
string_table_offset: u32,
|
||||
string_table_size: u32,
|
||||
|
||||
data_offset: u32,
|
||||
data_size: u32,
|
||||
|
||||
text_offset: u32,
|
||||
text_size: u32
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct SymbolEntry {
|
||||
name_offset: u32,
|
||||
value: u32,
|
||||
section: u8,
|
||||
binding: u8,
|
||||
_pad: u16
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DseExecutable {
|
||||
header: DseHeader,
|
||||
symbols: Vec<SymbolEntry>,
|
||||
string_table: Vec<u8>,
|
||||
data: Vec<u8>,
|
||||
text: Vec<u32>
|
||||
}
|
||||
|
||||
impl DseExecutable {
|
||||
fn load(path: PathBuf) -> binrw::BinResult<Self> {
|
||||
let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?;
|
||||
Self::read(&mut file)
|
||||
}
|
||||
|
||||
fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
|
||||
let header = DseHeader::read(reader)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.symbol_table_offset as u64))?;
|
||||
let symbols: Vec<SymbolEntry> = (0..header.symbol_table_size)
|
||||
.map(|_| SymbolEntry::read(reader))
|
||||
.collect::<binrw::BinResult<_>>()?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.string_table_offset as u64))?;
|
||||
let mut string_table = vec![0u8; header.string_table_size as usize];
|
||||
reader.read_exact(&mut string_table).map_err(|e| binrw::Error::Io(e))?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.data_offset as u64))?;
|
||||
let mut data = vec![0u8; header.data_size as usize];
|
||||
reader.read_exact(&mut data).map_err(|e| binrw::Error::Io(e))?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.text_offset as u64))?;
|
||||
let text: Vec<u32> = (0..header.text_size)
|
||||
.map(|_| reader.read_le::<u32>())
|
||||
.collect::<binrw::BinResult<_>>()?;
|
||||
|
||||
Ok(DseExecutable { header, symbols, string_table, data, text })
|
||||
}
|
||||
|
||||
fn save(&self, path: PathBuf) -> binrw::BinResult<()> {
|
||||
let mut file = File::create(path)
|
||||
.map_err(|e| binrw::Error::Io(e))?;
|
||||
self.write(&mut file)
|
||||
}
|
||||
|
||||
fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
|
||||
self.header.write(writer)?;
|
||||
|
||||
for symbol in &self.symbols {
|
||||
symbol.write(writer)?;
|
||||
}
|
||||
|
||||
writer.write_all(&self.string_table)?;
|
||||
writer.write_all(&self.data)?;
|
||||
|
||||
for word in &self.text {
|
||||
writer.write_le(word)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new(
|
||||
symbols: &[SymbolEntry],
|
||||
string_table: &[u8],
|
||||
data: &[u8],
|
||||
text: &[u32],
|
||||
entry_point: u32,
|
||||
) -> DseExecutable {
|
||||
const HEADER_SIZE: u32 = 4 + 2 + 2 + 4 + 4*2 + 4*2 + 4*2 + 4*2; // adjust to actual packed size
|
||||
|
||||
let symbol_table_offset = HEADER_SIZE;
|
||||
let symbol_table_size = symbols.len() as u32 * 8; // 4+4+1+1+2 padded = 8 bytes each
|
||||
let string_table_offset = symbol_table_offset + symbol_table_size;
|
||||
let data_offset = string_table_offset + string_table.len() as u32;
|
||||
let text_offset = data_offset + data.len() as u32;
|
||||
|
||||
let header = DseHeader {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
entry_point,
|
||||
symbol_table_offset,
|
||||
symbol_table_size: symbols.len() as u32,
|
||||
string_table_offset,
|
||||
string_table_size: string_table.len() as u32,
|
||||
data_offset,
|
||||
data_size: data.len() as u32,
|
||||
text_offset,
|
||||
text_size: text.len() as u32,
|
||||
};
|
||||
|
||||
DseExecutable {
|
||||
header,
|
||||
symbols: symbols.to_vec(),
|
||||
string_table: string_table.to_vec(),
|
||||
data: data.to_vec(),
|
||||
text: text.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use assembler::prelude::*;
|
||||
use clap::{Parser, arg, command};
|
||||
use clap::{Parser, arg};
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
@@ -23,11 +23,4 @@ fn main() {
|
||||
eprintln!("Failed to write to output file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// let input = fs::read_to_string("../../dsa_resources/framebuffer.dsa").unwrap();
|
||||
// let output = asm(&input).unwrap();
|
||||
|
||||
// for token in output {
|
||||
// println!("{:?}", token);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ edition = "2024"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.15.1"
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::path::PathBuf;
|
||||
use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite};
|
||||
use std::fmt;
|
||||
use crate::prelude::Instruction;
|
||||
|
||||
#[binrw]
|
||||
#[brw(magic = b"DSAX", little)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DseHeader {
|
||||
version: u16,
|
||||
flags: u16,
|
||||
pub entry_point: u32,
|
||||
|
||||
symbol_table_offset: u32,
|
||||
symbol_table_size: u32,
|
||||
|
||||
string_table_offset: u32,
|
||||
string_table_size: u32,
|
||||
|
||||
data_offset: u32,
|
||||
data_size: u32,
|
||||
|
||||
pub text_offset: u32,
|
||||
text_size: u32
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolEntry {
|
||||
pub name_offset: u32,
|
||||
pub value: u32,
|
||||
pub section: u8,
|
||||
pub binding: u8,
|
||||
pub _pad: u16
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DseExecutable {
|
||||
pub header: DseHeader,
|
||||
pub symbols: Vec<SymbolEntry>,
|
||||
pub string_table: Vec<u8>,
|
||||
pub data: Vec<u8>,
|
||||
pub text: Vec<u32>
|
||||
}
|
||||
|
||||
impl DseExecutable {
|
||||
pub fn load(path: PathBuf) -> binrw::BinResult<Self> {
|
||||
let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?;
|
||||
Self::read(&mut file)
|
||||
}
|
||||
|
||||
pub fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
|
||||
let header = DseHeader::read(reader)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.symbol_table_offset as u64))?;
|
||||
let symbols: Vec<SymbolEntry> = (0..header.symbol_table_size)
|
||||
.map(|_| SymbolEntry::read(reader))
|
||||
.collect::<binrw::BinResult<_>>()?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.string_table_offset as u64))?;
|
||||
let mut string_table = vec![0u8; header.string_table_size as usize];
|
||||
reader.read_exact(&mut string_table).map_err(|e| binrw::Error::Io(e))?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.data_offset as u64))?;
|
||||
let mut data = vec![0u8; header.data_size as usize];
|
||||
reader.read_exact(&mut data).map_err(|e| binrw::Error::Io(e))?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.text_offset as u64))?;
|
||||
let text: Vec<u32> = (0..header.text_size)
|
||||
.map(|_| reader.read_le::<u32>())
|
||||
.collect::<binrw::BinResult<_>>()?;
|
||||
|
||||
Ok(DseExecutable { header, symbols, string_table, data, text })
|
||||
}
|
||||
|
||||
pub fn save(&self, path: PathBuf) -> binrw::BinResult<()> {
|
||||
let mut file = File::create(path)
|
||||
.map_err(|e| binrw::Error::Io(e))?;
|
||||
self.write(&mut file)
|
||||
}
|
||||
|
||||
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
|
||||
self.header.write(writer)?;
|
||||
|
||||
for symbol in &self.symbols {
|
||||
symbol.write(writer)?;
|
||||
}
|
||||
|
||||
writer.write_all(&self.string_table)?;
|
||||
writer.write_all(&self.data)?;
|
||||
|
||||
for word in &self.text {
|
||||
writer.write_le(word)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
symbols: &[SymbolEntry],
|
||||
string_table: &[u8],
|
||||
data: &[u8],
|
||||
text: &[u32],
|
||||
entry_point: u32,
|
||||
) -> binrw::BinResult<DseExecutable> {
|
||||
// Derive sizes from real serialization instead of hand-counted constants.
|
||||
let header_size = {
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
// dummy header just to measure length; fields' values don't matter here
|
||||
let dummy = DseHeader {
|
||||
version: 0, flags: 0, entry_point: 0,
|
||||
symbol_table_offset: 0, symbol_table_size: 0,
|
||||
string_table_offset: 0, string_table_size: 0,
|
||||
data_offset: 0, data_size: 0,
|
||||
text_offset: 0, text_size: 0,
|
||||
};
|
||||
dummy.write(&mut buf)?;
|
||||
buf.into_inner().len() as u32
|
||||
};
|
||||
|
||||
let symbol_entry_size = if symbols.is_empty() {
|
||||
// measure using a zeroed entry even if symbols is empty
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
SymbolEntry { name_offset: 0, value: 0, section: 0, binding: 0, _pad: 0 }
|
||||
.write(&mut buf)?;
|
||||
buf.into_inner().len() as u32
|
||||
} else {
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
symbols[0].write(&mut buf)?;
|
||||
buf.into_inner().len() as u32
|
||||
};
|
||||
|
||||
let symbol_table_offset = header_size;
|
||||
let symbol_table_bytes = symbols.len() as u32 * symbol_entry_size;
|
||||
let string_table_offset = symbol_table_offset + symbol_table_bytes;
|
||||
let data_offset = string_table_offset + string_table.len() as u32;
|
||||
let text_offset = data_offset + data.len() as u32;
|
||||
|
||||
let header = DseHeader {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
entry_point,
|
||||
symbol_table_offset,
|
||||
symbol_table_size: symbols.len() as u32,
|
||||
string_table_offset,
|
||||
string_table_size: string_table.len() as u32,
|
||||
data_offset,
|
||||
data_size: data.len() as u32,
|
||||
text_offset,
|
||||
text_size: text.len() as u32,
|
||||
};
|
||||
|
||||
Ok(DseExecutable {
|
||||
header,
|
||||
symbols: symbols.to_vec(),
|
||||
string_table: string_table.to_vec(),
|
||||
data: data.to_vec(),
|
||||
text: text.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Produces the runtime memory image: TEXT followed immediately by DATA,
|
||||
/// matching the exact layout `resolve_address` assumed when the assembler
|
||||
/// resolved symbol addresses. This is NOT the same as the on-disk file
|
||||
/// layout (header/symtab/strtab/data/text) — this is what actually needs
|
||||
/// to end up in emulator memory.
|
||||
pub fn to_memory_image(&self) -> Vec<u8> {
|
||||
let mut image = Vec::with_capacity(self.text.len() * 4 + self.data.len());
|
||||
|
||||
for word in &self.text {
|
||||
image.extend_from_slice(&word.to_le_bytes());
|
||||
}
|
||||
image.extend_from_slice(&self.data);
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
/// Runtime address where DATA begins, given TEXT is loaded at `text_base`.
|
||||
/// Matches the assembler's `resolve_address` convention exactly.
|
||||
pub fn data_base(&self, text_base: u32) -> u32 {
|
||||
text_base + (self.text.len() as u32 * 4)
|
||||
}
|
||||
|
||||
/// Absolute runtime entry point address, given TEXT is loaded at `text_base`.
|
||||
/// `self.header.entry_point` is already an absolute address under the
|
||||
/// assumption text_base == 0, so this just re-bases it if you're loading
|
||||
/// somewhere other than address 0.
|
||||
pub fn entry_point(&self, text_base: u32) -> u32 {
|
||||
text_base + self.header.entry_point
|
||||
}
|
||||
}
|
||||
|
||||
// Display implementations for dumping binary contents
|
||||
impl fmt::Display for DseHeader {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DseHeader {{ version: {}, flags: {}, entry_point: 0x{:08X}, symbol_table_offset: 0x{:08X}, symbol_table_size: {}, string_table_offset: 0x{:08X}, string_table_size: {}, data_offset: 0x{:08X}, data_size: {}, text_offset: 0x{:08X}, text_size: {} }}",
|
||||
self.version,
|
||||
self.flags,
|
||||
self.entry_point,
|
||||
self.symbol_table_offset,
|
||||
self.symbol_table_size,
|
||||
self.string_table_offset,
|
||||
self.string_table_size,
|
||||
self.data_offset,
|
||||
self.data_size,
|
||||
self.text_offset,
|
||||
self.text_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SymbolEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "SymbolEntry {{ name_offset: 0x{:08X}, value: 0x{:08X}, section: {}, binding: {} }}",
|
||||
self.name_offset,
|
||||
self.value,
|
||||
self.section,
|
||||
self.binding)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DseExecutable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Header
|
||||
writeln!(f, "{}", self.header)?;
|
||||
// Symbols
|
||||
writeln!(f, "Symbols:")?;
|
||||
for s in &self.symbols {
|
||||
writeln!(f, " {}", s)?;
|
||||
}
|
||||
// String table as hex dump
|
||||
writeln!(f, "String Table ({} bytes):", self.string_table.len())?;
|
||||
for chunk in self.string_table.chunks(16) {
|
||||
write!(f, "{}", String::from_utf8_lossy(chunk))?;
|
||||
// for byte in chunk { write!(f, "{:02X} ", byte)?; }
|
||||
writeln!(f)?;
|
||||
}
|
||||
// Data section
|
||||
writeln!(f, "Data ({} bytes):", self.data.len())?;
|
||||
for chunk in self.data.chunks(16) {
|
||||
write!(f, " ")?;
|
||||
for byte in chunk { write!(f, "{:02X} ", byte)?; }
|
||||
for byte in chunk { write!(f, "{} ", *byte as char)?; }
|
||||
writeln!(f)?;
|
||||
}
|
||||
// Text section as Instructions
|
||||
writeln!(f, "Text ({} words):", self.text.len())?;
|
||||
for word in &self.text {
|
||||
writeln!(f, " {:#010X} {:?}", word, Instruction(*word))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_entry_size_is_12_bytes() {
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
SymbolEntry { name_offset: 0, value: 0, section: 0, binding: 0, _pad: 0 }
|
||||
.write(&mut buf).unwrap();
|
||||
assert_eq!(buf.into_inner().len(), 12);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod binary_dse;
|
||||
pub mod binary_dso;
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod asm;
|
||||
pub mod isa;
|
||||
pub mod formats;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::isa::instructions::Instruction;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "disassembler"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "dsa-a"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "disassembler"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.6.0", features = ["derive"] }
|
||||
common = { path = "../common" }
|
||||
@@ -0,0 +1,350 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use common::isa::instructions::Opcode;
|
||||
use common::prelude::{Instruction, Register};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Disassembly {
|
||||
pub symbols: Vec<SymbolLine>,
|
||||
pub text_lines: Vec<String>,
|
||||
pub data_lines: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolLine {
|
||||
pub name: String,
|
||||
pub address: u32,
|
||||
pub section: SectionKind,
|
||||
pub binding: BindingKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SectionKind { Data, Text, Abs, Undef }
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BindingKind { Local, Global, Weak }
|
||||
|
||||
impl SectionKind {
|
||||
fn from_raw(v: u8) -> Self {
|
||||
match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef }
|
||||
}
|
||||
}
|
||||
impl BindingKind {
|
||||
fn from_raw(v: u8) -> Self {
|
||||
match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassemble(exe: &DseExecutable) -> Disassembly {
|
||||
let symbols = build_symbol_lines(exe);
|
||||
|
||||
let mut text_labels: HashMap<u32, String> = HashMap::new();
|
||||
let mut data_labels: HashMap<u32, String> = HashMap::new();
|
||||
for sym in &symbols {
|
||||
match sym.section {
|
||||
SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); }
|
||||
SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// symbols can refer to either section by full resolved address (per your
|
||||
// v1 loader convention: data lives right after text), so a single combined
|
||||
// map is actually what address-resolution needs — keep both, but merge
|
||||
// for lookups against full 32-bit addresses.
|
||||
let mut all_labels = text_labels.clone();
|
||||
all_labels.extend(data_labels.clone());
|
||||
|
||||
let text_lines = build_text_lines(&exe.text, &all_labels);
|
||||
let data_lines = build_data_lines(&exe.data, &data_labels);
|
||||
|
||||
Disassembly { symbols, text_lines, data_lines }
|
||||
}
|
||||
|
||||
fn build_symbol_lines(exe: &DseExecutable) -> Vec<SymbolLine> {
|
||||
exe.symbols
|
||||
.iter()
|
||||
.map(|s| SymbolLine {
|
||||
name: read_string(&exe.string_table, s.name_offset),
|
||||
address: s.value,
|
||||
section: SectionKind::from_raw(s.section),
|
||||
binding: BindingKind::from_raw(s.binding),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_string(strtab: &[u8], offset: u32) -> String {
|
||||
let start = offset as usize;
|
||||
let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len());
|
||||
String::from_utf8_lossy(&strtab[start..end]).into_owned()
|
||||
}
|
||||
|
||||
/// Address a jump-like instruction actually targets, if statically known.
|
||||
/// Returns None when the jump is register-relative (addr reg != Zero) since
|
||||
/// that depends on runtime state and can't be resolved here.
|
||||
fn static_jump_target(addr_reg: Register, imm: u16) -> Option<u32> {
|
||||
(addr_reg == Register::Zero).then_some(imm as u32)
|
||||
}
|
||||
|
||||
fn build_text_lines(words: &[u32], labels: &HashMap<u32, String>) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < words.len() {
|
||||
let addr = 4 * i as u32;
|
||||
if let Some(label) = labels.get(&addr) {
|
||||
out.push(format!("{label}:"));
|
||||
}
|
||||
|
||||
let instr = Instruction(words[i]);
|
||||
let opcode = Opcode::from_u8(instr.opcode());
|
||||
|
||||
// Collapse Lli+Lui address-load pairs back into `lwi` (or a full
|
||||
// ldx/stx pseudo-op if a matching mem instruction follows), mirroring
|
||||
// what expand_ldx/expand_stx/expand_lwi originally expanded from.
|
||||
if let Some(Opcode::Lli) = opcode {
|
||||
if let Some(collapsed) = try_collapse_addr_load(words, i, labels) {
|
||||
out.push(format!(" {}", collapsed.text));
|
||||
i += collapsed.consumed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out.push(format!(" {}", render_instruction(instr, opcode, labels)));
|
||||
i += 1;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
struct Collapsed {
|
||||
text: String,
|
||||
consumed: usize,
|
||||
}
|
||||
|
||||
fn try_collapse_addr_load(
|
||||
words: &[u32],
|
||||
i: usize,
|
||||
labels: &HashMap<u32, String>,
|
||||
) -> Option<Collapsed> {
|
||||
let lli = Instruction(words[i]);
|
||||
let lui = Instruction(*words.get(i + 1)?);
|
||||
|
||||
if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 {
|
||||
return None;
|
||||
}
|
||||
if lui.dest_checked() != lli.dest_checked() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let dest = lli.dest_checked();
|
||||
let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16);
|
||||
let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}"));
|
||||
|
||||
// Check whether a mem op immediately follows, referencing `dest` —
|
||||
// that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi.
|
||||
if let Some(&mem_word) = words.get(i + 2) {
|
||||
let mem = Instruction(mem_word);
|
||||
if let Some(mem_op) = Opcode::from_u8(mem.opcode()) {
|
||||
let is_mem_op = matches!(
|
||||
mem_op,
|
||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
||||
| Opcode::Stb | Opcode::Sth | Opcode::Stw
|
||||
);
|
||||
if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) {
|
||||
let mnemonic = format!("{mem_op:?}").to_lowercase();
|
||||
let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() };
|
||||
return Some(Collapsed {
|
||||
text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()),
|
||||
consumed: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Collapsed {
|
||||
text: format!("lwi {addr_text}, {dest:?}"),
|
||||
consumed: 2,
|
||||
})
|
||||
}
|
||||
|
||||
fn render_instruction(instr: Instruction, opcode: Option<Opcode>, labels: &HashMap<u32, String>) -> String {
|
||||
let Some(op) = opcode else {
|
||||
return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0);
|
||||
};
|
||||
|
||||
let mnemonic = format!("{op:?}").to_lowercase();
|
||||
|
||||
match op {
|
||||
Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic,
|
||||
|
||||
Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()),
|
||||
Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()),
|
||||
|
||||
Opcode::Lli | Opcode::Lui => {
|
||||
format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16())
|
||||
}
|
||||
|
||||
Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()),
|
||||
|
||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
||||
| Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:#06x}",
|
||||
instr.src1_checked(), instr.dest_checked(), instr.imm16()
|
||||
),
|
||||
|
||||
Opcode::Jez | Opcode::Jnz => {
|
||||
let addr_reg = instr.dest_checked();
|
||||
let target = static_jump_target(addr_reg, instr.imm16())
|
||||
.and_then(|a| labels.get(&a).cloned());
|
||||
match target {
|
||||
Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()),
|
||||
None if addr_reg == Register::Zero => {
|
||||
format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16())
|
||||
}
|
||||
None => format!(
|
||||
"{mnemonic} {:?}, {:#06x}, {addr_reg:?}",
|
||||
instr.src1_checked(), instr.imm16()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
|
||||
let addr_reg = instr.dest_checked();
|
||||
let target = static_jump_target(addr_reg, instr.imm16())
|
||||
.and_then(|a| labels.get(&a).cloned());
|
||||
match target {
|
||||
Some(label) => format!("{mnemonic} {label}"),
|
||||
None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()),
|
||||
None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()),
|
||||
}
|
||||
}
|
||||
|
||||
Opcode::Mov | Opcode::Not => {
|
||||
format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked())
|
||||
}
|
||||
|
||||
Opcode::CMov => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:?}",
|
||||
instr.src1_checked(), instr.dest_checked(), instr.src2_checked()
|
||||
),
|
||||
|
||||
Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor
|
||||
| Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile
|
||||
| Opcode::Igt | Opcode::Ige => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:?}",
|
||||
instr.src1_checked(), instr.src2_checked(), instr.dest_checked()
|
||||
),
|
||||
|
||||
Opcode::Shl | Opcode::Shr => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:?}, {}",
|
||||
instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_data_lines(data: &[u8], data_labels: &HashMap<u32, String>) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
let addr = i as u32;
|
||||
if let Some(label) = data_labels.get(&addr) {
|
||||
out.push(format!("{label}:"));
|
||||
}
|
||||
|
||||
// Try string detection only when this byte could actually start one.
|
||||
if data[i] != 0 {
|
||||
if let Some(end) = data[i..].iter().position(|&b| b == 0) {
|
||||
let slice = &data[i..i + end];
|
||||
if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
|
||||
out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default()));
|
||||
i += end + 1; // skip the null terminator too
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not a string start: consume only the run of zero bytes here,
|
||||
// grouped into 4-byte words, stopping the instant a non-zero byte
|
||||
// appears so we never swallow the start of the next string/value.
|
||||
let run_end = data[i..]
|
||||
.iter()
|
||||
.position(|&b| b != 0)
|
||||
.map(|p| i + p)
|
||||
.unwrap_or(data.len());
|
||||
|
||||
if run_end == i {
|
||||
// data[i] is non-zero but didn't form a printable/terminated
|
||||
// string (e.g. binary constant) — dump one raw word.
|
||||
let mut buf = [0u8; 4];
|
||||
let take = (data.len() - i).min(4);
|
||||
buf[..take].copy_from_slice(&data[i..i + take]);
|
||||
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
||||
i += take;
|
||||
} else {
|
||||
let mut j = i;
|
||||
while j < run_end {
|
||||
let take = (run_end - j).min(4);
|
||||
let mut buf = [0u8; 4];
|
||||
buf[..take].copy_from_slice(&data[j..j + take]);
|
||||
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
||||
j += take;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
impl fmt::Display for SectionKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Data => write!(f, "DATA"),
|
||||
Self::Text => write!(f, "TEXT"),
|
||||
Self::Abs => write!(f, "ABS"),
|
||||
Self::Undef => write!(f, "UNDEF"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BindingKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Local => write!(f, "local"),
|
||||
Self::Global => write!(f, "global"),
|
||||
Self::Weak => write!(f, "weak"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SymbolLine {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"; {:<24} {:#010x} {:<5} {}",
|
||||
self.name, self.address, self.section, self.binding
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Disassembly {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "; ==== symbols ====")?;
|
||||
for sym in &self.symbols {
|
||||
writeln!(f, "{sym}")?;
|
||||
}
|
||||
|
||||
writeln!(f, "\n; ==== data ====")?;
|
||||
for line in &self.data_lines {
|
||||
writeln!(f, "{line}")?;
|
||||
}
|
||||
|
||||
writeln!(f, "\n; ==== text ====")?;
|
||||
for line in &self.text_lines {
|
||||
writeln!(f, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
mod internal;
|
||||
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use common::isa::instructions::{Instruction, Opcode};
|
||||
use common::isa::register::Register;
|
||||
|
||||
pub use internal::{
|
||||
disassemble, Disassembly, BindingKind, SectionKind, SymbolLine
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use clap::Parser;
|
||||
use disassembler::disassemble;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short = 'i')]
|
||||
pub input_path: PathBuf,
|
||||
#[arg(short = 'o')]
|
||||
pub output_path: PathBuf,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let Ok(exec) = DseExecutable::load(args.input_path) else {
|
||||
panic!("Failed to load the input file")
|
||||
};
|
||||
|
||||
let result = disassemble(&exec);
|
||||
File::create(args.output_path).unwrap()
|
||||
.write(result.to_string().as_bytes()).unwrap();
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "emu_core"
|
||||
name = "emulator_core"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "emu_core"
|
||||
name = "emulator_core"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
+5
@@ -164,6 +164,11 @@ impl Emulator {
|
||||
self.update();
|
||||
}
|
||||
|
||||
if let x = self.shared_state.goto.load(Ordering::Relaxed) && x != -1 {
|
||||
*self.mut_reg(Register::Pcx) = x as u32;
|
||||
self.shared_state.goto.store(-1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||
self.internal_state.registers = [0; Register::COUNT as usize];
|
||||
self.internal_state.clock = 0;
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
||||
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
use crate::processor::processor::ProcessorSnapshot;
|
||||
@@ -9,6 +9,7 @@ use super::interrupts::Interrupt;
|
||||
pub struct SharedState {
|
||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||
|
||||
pub goto: AtomicIsize,
|
||||
pub reseting: AtomicBool,
|
||||
pub paused: AtomicBool,
|
||||
pub update_req: AtomicBool,
|
||||
@@ -21,6 +22,7 @@ impl SharedState {
|
||||
reseting: AtomicBool::new(false),
|
||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||
|
||||
goto: AtomicIsize::new(-1),
|
||||
paused: AtomicBool::new(true),
|
||||
interrupt_queue: sender,
|
||||
update_req: AtomicBool::new(false),
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "emulator"
|
||||
name = "emulator_ui"
|
||||
version = "0.3.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
@@ -13,7 +13,7 @@ name = "dsa"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5.0", features = ["html_reports"] }
|
||||
criterion = { version = "0.8.2", features = ["html_reports"] }
|
||||
|
||||
[dependencies]
|
||||
arc-swap = "1.8.2"
|
||||
@@ -23,15 +23,15 @@ ron = "0.12.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
|
||||
common = { path = "../../common" }
|
||||
egui = "0.33.3"
|
||||
eframe = "0.33.3"
|
||||
egui_tiles = "0.14.1"
|
||||
egui_code_editor = "0.2.21"
|
||||
egui_file = "0.25.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"
|
||||
dirs = "6.0.0"
|
||||
assembler = { path = "../../assembler" }
|
||||
egui_extras = "0.33.3"
|
||||
emu_core = { version = "0.3.0", path = "../backend" }
|
||||
egui_extras = "0.35.0"
|
||||
emulator_core = { version = "0.3.0", path = "../emulator_core" }
|
||||
|
||||
[features]
|
||||
default = ["mainstore-prealloc"]
|
||||
@@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use emu_core::config::{IoMap, IoMapping, MemoryMap};
|
||||
use emulator_core::config::{IoMap, IoMapping, MemoryMap};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
@@ -4,8 +4,10 @@ use common::prelude::Instruction;
|
||||
use std::thread;
|
||||
use dsa::args::DsaArgs;
|
||||
use dsa::ui::run_app;
|
||||
use emu_core::memory::ram::Page;
|
||||
use emu_core::processor::processor::Emulator;
|
||||
use emulator_core::{
|
||||
memory::ram::Page,
|
||||
processor::processor::Emulator
|
||||
};
|
||||
|
||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||
|
||||
+5
-3
@@ -2,8 +2,10 @@ use std::sync::{Arc, atomic::Ordering};
|
||||
|
||||
use super::Component;
|
||||
use common::prelude::Register;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::{
|
||||
memory::ram::MemoryBank,
|
||||
processor::state::SharedState
|
||||
};
|
||||
|
||||
pub struct Controller {
|
||||
state: Arc<SharedState>,
|
||||
@@ -30,7 +32,7 @@ impl Component for Controller {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let state = self.state.proc.load();
|
||||
|
||||
let paused = self.state.paused.load(Ordering::Relaxed);
|
||||
+5
-3
@@ -1,6 +1,8 @@
|
||||
use egui::{Color32, FontId, Vec2};
|
||||
use emu_core::memory::PhysAddr;
|
||||
use emu_core::memory::ram::{MemoryBank, Page};
|
||||
use emulator_core::{
|
||||
memory::PhysAddr,
|
||||
memory::ram::{MemoryBank, Page}
|
||||
};
|
||||
use super::Component;
|
||||
|
||||
pub struct Display {
|
||||
@@ -72,7 +74,7 @@ impl Component for Display {
|
||||
self.visible()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let display_w = self.width();
|
||||
let display_h = self.height();
|
||||
let data = self.read();
|
||||
+54
-157
@@ -1,7 +1,8 @@
|
||||
use std::fmt::Write;
|
||||
use std::fs::{self, File};
|
||||
use std::sync::Arc;
|
||||
use std::fs::{self};
|
||||
use std::io::Cursor;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
@@ -9,15 +10,18 @@ use std::{
|
||||
|
||||
pub mod syntax;
|
||||
|
||||
use super::Component;
|
||||
use assembler::prelude::Assembler;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use common::prelude::Instruction;
|
||||
use egui::{Align, Context, Key, Layout, Ui};
|
||||
use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use egui_file::FileDialog;
|
||||
use emu_core::memory::ram::{MemoryBank, Page};
|
||||
use emu_core::processor::state::SharedState;
|
||||
use super::Component;
|
||||
use emulator_core::{
|
||||
memory::ram::{MemoryBank, Page},
|
||||
processor::state::SharedState
|
||||
};
|
||||
|
||||
pub struct Editor {
|
||||
state: Arc<SharedState>,
|
||||
@@ -32,16 +36,13 @@ pub struct Editor {
|
||||
// editor widget
|
||||
editor: CodeEditor,
|
||||
completer: Completer,
|
||||
syntax: Syntax,
|
||||
|
||||
// output / loading
|
||||
output: Output,
|
||||
load_offset: u32,
|
||||
offset_str: String,
|
||||
|
||||
// cursor - currently unused
|
||||
cursor_col: usize,
|
||||
cursor_line: usize,
|
||||
|
||||
// file dialogs
|
||||
open_file_dialog: Option<FileDialog>,
|
||||
save_file_dialog: Option<FileDialog>,
|
||||
@@ -61,7 +62,7 @@ impl Component for Editor {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, ctx: &Context) {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
if self.buffer != self.text {
|
||||
self.unsaved = true;
|
||||
}
|
||||
@@ -73,7 +74,7 @@ impl Component for Editor {
|
||||
self.save();
|
||||
}
|
||||
|
||||
self.render_toolbar(ui, ctx);
|
||||
self.render_toolbar(ui);
|
||||
|
||||
ui.add_space(4.0); // Add some spacing instead of just a separator
|
||||
ui.separator();
|
||||
@@ -85,15 +86,15 @@ impl Component for Editor {
|
||||
Layout::left_to_right(Align::Min),
|
||||
|ui| {
|
||||
if self.show_output {
|
||||
egui::SidePanel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
|
||||
self.output.ui(ui, ctx);
|
||||
egui::Panel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
|
||||
self.output.ui(ui);
|
||||
});
|
||||
}
|
||||
self.render_editor(ui, ctx);
|
||||
self.render_editor(ui);
|
||||
},
|
||||
);
|
||||
|
||||
self.render_bottom_bar(ui, ctx);
|
||||
self.render_bottom_bar(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,17 +109,15 @@ impl Editor {
|
||||
text: String::new(),
|
||||
buffer: String::new(),
|
||||
completer: Completer::new_with_syntax(&Syntax::default()).with_user_words(),
|
||||
syntax: Syntax::default(),
|
||||
editor: CodeEditor::default()
|
||||
.id_source("editor")
|
||||
.with_fontsize(12.0)
|
||||
.with_rows(0)
|
||||
.with_theme(THEME)
|
||||
.with_syntax(Syntax::default())
|
||||
.with_numlines(true),
|
||||
output: Output::new(),
|
||||
unsaved: true,
|
||||
cursor_col: 1,
|
||||
cursor_line: 1,
|
||||
visible: false,
|
||||
load_offset: 0,
|
||||
offset_str: String::new(),
|
||||
@@ -226,21 +225,20 @@ impl Editor {
|
||||
self.open_file_dialog = Some(dialog);
|
||||
}
|
||||
|
||||
let syntax = match self.extension() {
|
||||
self.syntax = match self.extension() {
|
||||
"dsa" => syntax::dsa(),
|
||||
"dsc" => syntax::dsc(),
|
||||
"rs" => Syntax::rust(),
|
||||
_ => Syntax::default(),
|
||||
};
|
||||
|
||||
self.completer = Completer::new_with_syntax(&syntax).with_user_words();
|
||||
self.editor = self.editor.clone().with_syntax(syntax);
|
||||
self.completer = Completer::new_with_syntax(&self.syntax).with_user_words();
|
||||
}
|
||||
|
||||
fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
|
||||
fn handle_file_dialogs(&mut self, ui: &Ui) {
|
||||
// Handle open dialog
|
||||
if let Some(dialog) = &mut self.open_file_dialog
|
||||
&& dialog.show(ctx).selected()
|
||||
&& dialog.show(ui).selected()
|
||||
{
|
||||
if let Some(file) = dialog.path() {
|
||||
// check if the file is a binary file
|
||||
@@ -284,7 +282,7 @@ impl Editor {
|
||||
|
||||
// Handle save dialog
|
||||
if let Some(dialog) = &mut self.save_file_dialog
|
||||
&& dialog.show(ctx).selected()
|
||||
&& dialog.show(ui).selected()
|
||||
{
|
||||
if let Some(file) = dialog.path() {
|
||||
self.buffer = self.text.clone();
|
||||
@@ -324,24 +322,19 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) {
|
||||
fn render_editor(&mut self, ui: &mut Ui) {
|
||||
let available_width = ui.available_width();
|
||||
let mut editor = self.editor.clone().desired_width(available_width);
|
||||
editor.show_with_completer(ui, &mut self.text, &mut self.completer);
|
||||
editor.show_with_completer(ui, &mut self.text, &self.syntax, &mut self.completer);
|
||||
}
|
||||
|
||||
fn render_bottom_bar(&self, ui: &mut Ui, _ctx: &Context) {
|
||||
fn render_bottom_bar(&self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
// error display
|
||||
ui.label(
|
||||
egui::RichText::new(self.error.clone().unwrap_or_default())
|
||||
.color(egui::Color32::RED),
|
||||
);
|
||||
|
||||
// line and col
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -399,8 +392,8 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_toolbar(&mut self, ui: &mut Ui, ctx: &Context) {
|
||||
self.handle_file_dialogs(ctx);
|
||||
fn render_toolbar(&mut self, ui: &mut Ui) {
|
||||
self.handle_file_dialogs(ui);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("File type: {}", self.extension()));
|
||||
@@ -443,13 +436,21 @@ impl Editor {
|
||||
self.error = Some("Can't load program at invalid offset!".to_string());
|
||||
}
|
||||
|
||||
let program: Vec<Page> = Page::paginate(self.output.data()).collect();
|
||||
let mut reader = Cursor::new(self.output.data().clone());
|
||||
let Ok(executable) = DseExecutable::read(&mut reader) else {
|
||||
self.error = Some("Failed to read program!".to_string());
|
||||
return
|
||||
};
|
||||
|
||||
let image: Vec<Page> = Page::paginate(&executable.to_memory_image()).collect();
|
||||
|
||||
self.state.reseting.store(true, Ordering::Relaxed);
|
||||
|
||||
self.memory.clear();
|
||||
for (i, page) in program.iter().enumerate() {
|
||||
for (i, page) in image.iter().enumerate() {
|
||||
self.memory.write_page(i as u32 * 4096, &page);
|
||||
}
|
||||
self.state.goto.store(executable.entry_point(0) as isize, Ordering::Relaxed);
|
||||
self.state.reseting.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -463,7 +464,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
ui.checkbox(&mut self.show_output, "Show Output")
|
||||
ui.checkbox(&mut self.show_output, "Show Output");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -488,18 +489,18 @@ fn parse_address(address: &str) -> Option<u32> {
|
||||
pub const THEME: ColorTheme = ColorTheme {
|
||||
name: "Theme",
|
||||
dark: true,
|
||||
bg: "#1b1b1b",
|
||||
cursor: "#de5252", // fg4
|
||||
selection: "#28323B", // bg2
|
||||
comments: "#444444", // gray1
|
||||
functions: "#7CCCC7", // green1
|
||||
keywords: "#6C81E0", // red1
|
||||
literals: "#A3ABFF", // fg1
|
||||
numerics: "#8A46CF", // purple1
|
||||
punctuation: "#99C9C9", // orange1
|
||||
strs: "#618c84", // aqua1
|
||||
types: "#B8B9D4", // yellow1
|
||||
special: "#de5252", // blue1
|
||||
bg: "1b1b1b",
|
||||
cursor: "de5252", // fg4
|
||||
selection: "28323B", // bg2
|
||||
comments: "444444", // gray1
|
||||
functions: "7CCCC7", // green1
|
||||
keywords: "6C81E0", // red1
|
||||
literals: "A3ABFF", // fg1
|
||||
numerics: "8A46CF", // purple1
|
||||
punctuation: "99C9C9", // orange1
|
||||
strs: "618c84", // aqua1
|
||||
types: "B8B9D4", // yellow1
|
||||
special: "de5252", // blue1
|
||||
};
|
||||
|
||||
struct Output {
|
||||
@@ -532,7 +533,7 @@ impl Output {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _ctx: &Context) {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
// Output area with synchronized scrolling
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("output_scroll")
|
||||
@@ -613,8 +614,9 @@ impl Output {
|
||||
for (line_num, chunk) in self.data.chunks(4).enumerate() {
|
||||
body.row(18.0, |mut row| {
|
||||
let address = line_num * 4;
|
||||
|
||||
let value = u32::from_le_bytes(*chunk.as_array().unwrap());
|
||||
let mut buf = [0u8; 4];
|
||||
buf[..chunk.len()].copy_from_slice(chunk);
|
||||
let value = u32::from_le_bytes(buf);
|
||||
|
||||
// Address column
|
||||
row.col(|ui| {
|
||||
@@ -688,111 +690,6 @@ impl Output {
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// egui::Grid::new("output_grid")
|
||||
// .spacing([5.0, 2.0]) // Horizontal and vertical spacing
|
||||
// .num_columns(4)
|
||||
// .striped(false)
|
||||
// .show(ui, |ui| {
|
||||
// ui.label("Address");
|
||||
|
||||
// if self.show_bytes {
|
||||
// ui.label("Bytes");
|
||||
// }
|
||||
// if self.show_words {
|
||||
// ui.label("Word");
|
||||
// }
|
||||
// if self.show_decimal {
|
||||
// ui.label("Decimal");
|
||||
// }
|
||||
// if self.show_ascii {
|
||||
// ui.label("Ascii");
|
||||
// }
|
||||
// if self.show_instruction {
|
||||
// ui.label("Hex");
|
||||
// }
|
||||
// ui.end_row();
|
||||
|
||||
// // Process bytes in chunks of 4
|
||||
// for (line_num, chunk) in self.data.chunks(4).enumerate() {
|
||||
// let address = line_num * 4;
|
||||
|
||||
// // Convert chunk to u32 (little-endian)
|
||||
// let mut bytes = [0u8; 4];
|
||||
// for (i, &byte) in chunk.iter().enumerate() {
|
||||
// if i < 4 {
|
||||
// bytes[i] = byte;
|
||||
// }
|
||||
// }
|
||||
// let value = u32::from_le_bytes(bytes);
|
||||
|
||||
// // Address column
|
||||
// ui.with_layout(
|
||||
// egui::Layout::left_to_right(egui::Align::Center),
|
||||
// |ui| {
|
||||
// ui.set_min_width(80.0);
|
||||
// let style = ui.style_mut();
|
||||
// style.visuals.widgets.inactive.bg_fill =
|
||||
// egui::Color32::from_gray(30);
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("0x{address:04X}"))
|
||||
// .font(egui::FontId::monospace(12.0)),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
|
||||
// // Individual bytes column
|
||||
// let byte_str = chunk
|
||||
// .iter()
|
||||
// .map(|b| format!("{b:02X}"))
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(" ");
|
||||
|
||||
// if self.show_bytes {
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("{byte_str:<11}"))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(200, 200, 255)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// if self.show_words {
|
||||
// // Hex column
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("0x{value:08X}"))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(255, 100, 100)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// if self.show_decimal {
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("{value}"))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(100, 255, 100)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// if self.show_ascii {
|
||||
// let ascii = value.to_le_bytes();
|
||||
// ui.label(
|
||||
// egui::RichText::new(String::from_utf8_lossy(&ascii))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(100, 255, 255)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// if self.show_instruction {
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("{:?}", Instruction(value)))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(255, 100, 255)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// ui.end_row();
|
||||
// }
|
||||
// });
|
||||
});
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,9 +1,10 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use egui_code_editor::Syntax;
|
||||
use egui_code_editor::{Patch, Syntax};
|
||||
|
||||
pub fn dsa() -> Syntax {
|
||||
Syntax {
|
||||
word_start: BTreeSet::new(),
|
||||
quotes: BTreeSet::from(['"', '\'']),
|
||||
language: "Assembly",
|
||||
case_sensitive: false,
|
||||
@@ -22,11 +23,13 @@ pub fn dsa() -> Syntax {
|
||||
"RGC", "RGD", "RGE", "RGF", "ACC", "SPR", "BPR", "IDR", "MMR", "ZERO", "PCX", "STS",
|
||||
"CIR",
|
||||
]),
|
||||
patch: Patch::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dsc() -> Syntax {
|
||||
Syntax {
|
||||
word_start: BTreeSet::from(['_']),
|
||||
quotes: BTreeSet::from(['"', '\'', '`']),
|
||||
language: "Damn Simple Code",
|
||||
case_sensitive: false,
|
||||
@@ -44,5 +47,6 @@ pub fn dsc() -> Syntax {
|
||||
",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "?", "<",
|
||||
">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||",
|
||||
]),
|
||||
patch: Patch::default(),
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -1,6 +1,8 @@
|
||||
use egui::Vec2;
|
||||
use emu_core::memory::PhysAddr;
|
||||
use emu_core::memory::ram::{MemoryBank, Page};
|
||||
use emulator_core::{
|
||||
memory::PhysAddr,
|
||||
memory::ram::{MemoryBank, Page}
|
||||
};
|
||||
use super::Component;
|
||||
|
||||
pub struct FrameBuffer {
|
||||
@@ -86,12 +88,12 @@ impl Component for FrameBuffer {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let changed = self.changed();
|
||||
|
||||
// get or create the texture
|
||||
let texture = self.texture.get_or_insert_with(|| {
|
||||
ctx.load_texture(
|
||||
ui.load_texture(
|
||||
"framebuffer",
|
||||
egui::ColorImage {
|
||||
size: [self.width, self.height],
|
||||
@@ -0,0 +1,264 @@
|
||||
use std::num::ParseIntError;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use common::prelude::Instruction;
|
||||
use emulator_core::memory::ram::MemoryBank;
|
||||
use super::Component;
|
||||
|
||||
pub struct MemoryInspector {
|
||||
memory: MemoryBank,
|
||||
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
|
||||
// settings
|
||||
show_bytes: bool,
|
||||
show_words: bool,
|
||||
show_ascii: bool,
|
||||
show_instruction: bool,
|
||||
show_decimal: bool,
|
||||
}
|
||||
|
||||
impl MemoryInspector {
|
||||
#[must_use]
|
||||
pub fn new(memory: MemoryBank) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
view_size: 1024,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::from("0x00"),
|
||||
|
||||
show_bytes: true,
|
||||
show_words: true,
|
||||
show_ascii: true,
|
||||
show_instruction: true,
|
||||
show_decimal: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn title(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
// Right column - Memory
|
||||
// ui.vertical(|ui| {
|
||||
// ui.add_space(10.0);
|
||||
// });
|
||||
// Memory table
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.id_salt("memory_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
|
||||
// ui.heading("Memory Inspector");
|
||||
// ui.add_space(10.0);
|
||||
//
|
||||
// // Address input section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
let address_response = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.addr_input)
|
||||
.hint_text("0x1000 or 4096")
|
||||
.desired_width(150.0),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Search button
|
||||
let search_clicked = ui.button("🔍 Search").clicked();
|
||||
|
||||
// Handle Enter key in text field
|
||||
let enter_pressed =
|
||||
address_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
|
||||
if search_clicked || enter_pressed {
|
||||
if let Ok(new) = parse_address(&self.addr_input) {
|
||||
if new % 4 == 0 {
|
||||
self.view_addr = new;
|
||||
} else {
|
||||
println!("Address must be 4-byte aligned");
|
||||
}
|
||||
} else {
|
||||
println!("Invalid address");
|
||||
// state.error_log.push("Invalid address".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.toggle_value(&mut self.show_bytes, "Bytes");
|
||||
ui.toggle_value(&mut self.show_words, "Words");
|
||||
ui.toggle_value(&mut self.show_decimal, "Decimal");
|
||||
ui.toggle_value(&mut self.show_ascii, "Ascii");
|
||||
ui.toggle_value(&mut self.show_instruction, "Instructions");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
let mut table = TableBuilder::new(ui)
|
||||
.resizable(false) // makes all columns resizable at once
|
||||
.column(Column::initial(75.0)); // addr
|
||||
|
||||
if self.show_bytes {
|
||||
table = table.column(Column::initial(90.0));
|
||||
}
|
||||
if self.show_words {
|
||||
table = table.column(Column::initial(75.0));
|
||||
}
|
||||
if self.show_decimal {
|
||||
table = table.column(Column::initial(80.0));
|
||||
}
|
||||
if self.show_ascii {
|
||||
table = table.column(Column::initial(50.0));
|
||||
}
|
||||
if self.show_instruction {
|
||||
table = table.column(Column::initial(200.0).clip(true));
|
||||
}
|
||||
|
||||
table
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.label("Addr");
|
||||
});
|
||||
if self.show_bytes {
|
||||
header.col(|ui| {
|
||||
ui.label("Bytes");
|
||||
});
|
||||
}
|
||||
if self.show_words {
|
||||
header.col(|ui| {
|
||||
ui.label("Word");
|
||||
});
|
||||
}
|
||||
if self.show_decimal {
|
||||
header.col(|ui| {
|
||||
ui.label("Decimal");
|
||||
});
|
||||
}
|
||||
if self.show_ascii {
|
||||
header.col(|ui| {
|
||||
ui.label("Ascii");
|
||||
});
|
||||
}
|
||||
if self.show_instruction {
|
||||
header.col(|ui| {
|
||||
ui.label("Instruction");
|
||||
});
|
||||
}
|
||||
})
|
||||
.body(|mut body| {
|
||||
// Process bytes in chunks of 4
|
||||
for (line_num, value) in (0..self.view_size / 4)
|
||||
.map(|i| (i, self.memory.read_word(self.view_addr + i * 4))) {
|
||||
body.row(18.0, |mut row| {
|
||||
let address = line_num * 4 * self.view_addr;
|
||||
|
||||
let chunk = value.to_le_bytes();
|
||||
|
||||
// Address column
|
||||
row.col(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("0x{address:08X}"))
|
||||
.font(egui::FontId::monospace(12.0)),
|
||||
);
|
||||
});
|
||||
|
||||
if self.show_bytes {
|
||||
row.col(|ui| {
|
||||
// Individual bytes column
|
||||
ui.label(
|
||||
egui::RichText::new(
|
||||
chunk
|
||||
.iter()
|
||||
.map(|b| format!("{b:02X}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
)
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(200, 200, 255)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if self.show_words {
|
||||
row.col(|ui| {
|
||||
// Hex column
|
||||
ui.label(
|
||||
egui::RichText::new(format!("0x{value:08X}"))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(255, 100, 100)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if self.show_decimal {
|
||||
row.col(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{value}"))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(100, 255, 100)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if self.show_ascii {
|
||||
let ascii = value.to_le_bytes();
|
||||
row.col(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(String::from_utf8_lossy(&ascii))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(100, 255, 255)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if self.show_instruction {
|
||||
row.col(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"{:?}",
|
||||
Instruction(value)
|
||||
))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(255, 100, 255)),
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
|
||||
if let Some(hex_part) = address.strip_prefix("0x") {
|
||||
return u32::from_str_radix(hex_part, 16);
|
||||
}
|
||||
|
||||
if let Some(bin_part) = address.strip_prefix("0b") {
|
||||
return u32::from_str_radix(bin_part, 2);
|
||||
}
|
||||
|
||||
if let Some(oct_part) = address.strip_prefix("0o") {
|
||||
return u32::from_str_radix(oct_part, 8);
|
||||
}
|
||||
|
||||
address.parse::<u32>()
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,5 +13,5 @@ pub trait Component {
|
||||
fn visible(&mut self) -> &mut bool;
|
||||
|
||||
/// Draw the component's UI inside the provided `Ui`.
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context);
|
||||
fn ui(&mut self, ui: &mut egui::Ui);
|
||||
}
|
||||
+4
-7
@@ -1,7 +1,7 @@
|
||||
use std::{f32, sync::Arc};
|
||||
|
||||
use common::prelude::Register;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::processor::state::SharedState;
|
||||
|
||||
use super::Component;
|
||||
|
||||
@@ -14,7 +14,6 @@ impl Registers {
|
||||
fn section(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
ctx: &egui::Context,
|
||||
registers: Vec<(Register, u32)>,
|
||||
col_pairs: usize,
|
||||
name: &str,
|
||||
@@ -67,7 +66,7 @@ impl Component for Registers {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.set_min_width(200.0);
|
||||
|
||||
let state = self.state.proc.load();
|
||||
@@ -87,7 +86,6 @@ impl Component for Registers {
|
||||
.collect();
|
||||
self.section(
|
||||
ui,
|
||||
ctx,
|
||||
gp_registers,
|
||||
col_pairs,
|
||||
"General Purpose Registers",
|
||||
@@ -99,7 +97,7 @@ impl Component for Registers {
|
||||
(Register::Spr, state.registers[Register::Spr as usize]),
|
||||
(Register::Bpr, state.registers[Register::Bpr as usize]),
|
||||
];
|
||||
self.section(ui, ctx, stack_registers, col_pairs, "Stack Registers");
|
||||
self.section(ui, stack_registers, col_pairs, "Stack Registers");
|
||||
ui.separator();
|
||||
|
||||
// ── Special Purpose ───────────────────────────────────────
|
||||
@@ -111,7 +109,6 @@ impl Component for Registers {
|
||||
];
|
||||
self.section(
|
||||
ui,
|
||||
ctx,
|
||||
special_registers,
|
||||
col_pairs,
|
||||
"Special Purpose Registers",
|
||||
@@ -124,7 +121,7 @@ impl Component for Registers {
|
||||
(Register::Sts, state.registers[Register::Sts as usize]),
|
||||
(Register::Cir, state.registers[Register::Cir as usize]),
|
||||
];
|
||||
self.section(ui, ctx, system_registers, col_pairs, "System Registers");
|
||||
self.section(ui, system_registers, col_pairs, "System Registers");
|
||||
});
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -6,10 +6,16 @@ use std::{
|
||||
mpsc::{Receiver, Sender},
|
||||
},
|
||||
};
|
||||
use emu_core::memory::PhysAddr;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use emu_core::processor::interrupts::Interrupt;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::{
|
||||
memory::{
|
||||
PhysAddr,
|
||||
ram::MemoryBank
|
||||
},
|
||||
processor::{
|
||||
interrupts::Interrupt,
|
||||
state::SharedState
|
||||
}
|
||||
};
|
||||
use super::Component;
|
||||
|
||||
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
|
||||
@@ -100,7 +106,7 @@ impl Component for Serial {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
self.update();
|
||||
|
||||
// Text field for input and a send button
|
||||
@@ -8,9 +8,11 @@ use components::{
|
||||
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
|
||||
registers::Registers, serial::Serial, Component,
|
||||
};
|
||||
use emu_core::config::VERSION;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::{
|
||||
config::VERSION,
|
||||
memory::ram::MemoryBank,
|
||||
processor::state::SharedState
|
||||
};
|
||||
|
||||
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
||||
eframe::run_native(
|
||||
@@ -21,12 +23,13 @@ pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Resul
|
||||
}
|
||||
|
||||
impl eframe::App for DsaUi {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
// request an update from ui every cycle
|
||||
self.state.update_req.store(true, Ordering::Relaxed);
|
||||
|
||||
// title panel with dsa title
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::containers::Panel::top("top_panel").show(ui, |ui| {
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down_justified(egui::Align::Center)
|
||||
.with_main_align(egui::Align::Min),
|
||||
@@ -39,7 +42,7 @@ impl eframe::App for DsaUi {
|
||||
});
|
||||
|
||||
// menu bar.
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::Panel::top("menu_bar").show(ui, |ui| {
|
||||
egui::MenuBar::new().ui(ui, |ui| {
|
||||
ui.menu_button("Panels", |ui| {
|
||||
ui.set_max_width(300.0);
|
||||
@@ -54,21 +57,21 @@ impl eframe::App for DsaUi {
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::CentralPanel::default().show(ui, |ui| {
|
||||
for c in &mut self.components {
|
||||
let mut visible = *c.visible();
|
||||
if visible {
|
||||
egui::Window::new(c.title())
|
||||
.open(&mut visible)
|
||||
.show(ctx, |ui| {
|
||||
c.ui(ui, ctx);
|
||||
.show(ui, |ui| {
|
||||
c.ui(ui);
|
||||
});
|
||||
}
|
||||
*c.visible() = visible;
|
||||
}
|
||||
});
|
||||
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
|
||||
ui.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use common::prelude::Instruction;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use super::Component;
|
||||
|
||||
pub struct MemoryInspector {
|
||||
memory: MemoryBank,
|
||||
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
}
|
||||
|
||||
impl MemoryInspector {
|
||||
#[must_use]
|
||||
pub fn new(memory: MemoryBank) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
view_size: 1024,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::from("0x00"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn title(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
// Right column - Memory
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Memory Inspector");
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Address input section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
let address_response = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.addr_input)
|
||||
.hint_text("0x1000 or 4096")
|
||||
.desired_width(150.0),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Search button
|
||||
let search_clicked = ui.button("🔍 Search").clicked();
|
||||
|
||||
// Handle Enter key in text field
|
||||
let enter_pressed =
|
||||
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
|
||||
if search_clicked || enter_pressed {
|
||||
if let Ok(new) = parse_address(&self.addr_input) {
|
||||
if new % 4 == 0 {
|
||||
self.view_addr = new;
|
||||
} else {
|
||||
println!("Address must be 4-byte aligned");
|
||||
}
|
||||
} else {
|
||||
println!("Invalid address");
|
||||
// state.error_log.push("Invalid address".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Memory table
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink(true)
|
||||
.id_salt("memory_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("memory_grid")
|
||||
.spacing([12.0, 2.0])
|
||||
.min_col_width(5.0)
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
// Header
|
||||
ui.strong("Address");
|
||||
|
||||
for i in 0..4 {
|
||||
ui.strong(format!("{i:X}"));
|
||||
}
|
||||
|
||||
ui.strong("Decimal");
|
||||
ui.strong("Instruction");
|
||||
|
||||
ui.end_row();
|
||||
|
||||
// Memory data (8 bytes per row)
|
||||
for (offset, word) in (0..self.view_size / 4)
|
||||
.map(|i| i * 4)
|
||||
.map(|i| (i, self.memory.read_word(self.view_addr + i)))
|
||||
{
|
||||
let row_address = self.view_addr + offset;
|
||||
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
|
||||
for byte in word.to_le_bytes() {
|
||||
ui.monospace(format!("{byte:02X}"));
|
||||
}
|
||||
|
||||
ui.monospace(format!("{word}"));
|
||||
ui.monospace(format!("{:?}", Instruction(word)));
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
|
||||
if let Some(hex_part) = address.strip_prefix("0x") {
|
||||
return u32::from_str_radix(hex_part, 16);
|
||||
}
|
||||
|
||||
if let Some(bin_part) = address.strip_prefix("0b") {
|
||||
return u32::from_str_radix(bin_part, 2);
|
||||
}
|
||||
|
||||
if let Some(oct_part) = address.strip_prefix("0o") {
|
||||
return u32::from_str_radix(oct_part, 8);
|
||||
}
|
||||
|
||||
address.parse::<u32>()
|
||||
}
|
||||
@@ -37,11 +37,11 @@ db program: "++++++++++++++++++++++++++++++++++++++++++++
|
||||
db error: "Invalid Instruction!"
|
||||
dh counter: 0xFF
|
||||
dw stack: 0x10000
|
||||
dw input: 0x30000
|
||||
dw input_addr: 0x30000
|
||||
resb data: 1024
|
||||
|
||||
// set up a stack so we can call functions
|
||||
_init_stack:
|
||||
_init:
|
||||
ldw stack, bpr
|
||||
mov bpr, spr
|
||||
|
||||
@@ -162,7 +162,7 @@ output:
|
||||
// ------------------------------------------
|
||||
// read a byte into the current cell
|
||||
input:
|
||||
ldw input, rg2
|
||||
ldw input_addr, rg2
|
||||
jmp loop_end
|
||||
|
||||
// ------------------------------------------
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dsx_client"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dsx_common"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dsx_server"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
Reference in New Issue
Block a user