merge commit
This commit is contained in:
Vendored
+2
-1
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
"editor.formatOnSave": true
|
||||
"editor.formatOnSave": true,
|
||||
"rust-analyzer.cargo.features": "all"
|
||||
}
|
||||
|
||||
Generated
+185
-18
@@ -558,6 +558,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
@@ -653,12 +659,6 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorful"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -750,6 +750,15 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
@@ -800,7 +809,29 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discord-presence"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91d7c2fc01ffdc327e2b66d65dd59b8bd3f31a17e88811ce0540412fa0b84c1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"crossbeam-channel",
|
||||
"log",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"quork",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -872,16 +903,6 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
||||
|
||||
[[package]]
|
||||
name = "dsa_editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colorful",
|
||||
"eframe",
|
||||
"egui",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.31.1"
|
||||
@@ -984,6 +1005,14 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_code_editor"
|
||||
version = "0.2.13"
|
||||
source = "git+https://github.com/zxq5-dev/egui_code_editor?rev=5eb313e#5eb313e38504410ce0a6b27231cda28842f542fe"
|
||||
dependencies = [
|
||||
"egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.31.1"
|
||||
@@ -1018,10 +1047,12 @@ dependencies = [
|
||||
"assembler",
|
||||
"common",
|
||||
"dirs",
|
||||
"dsa_editor",
|
||||
"discord-presence",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_code_editor",
|
||||
"rfd",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1602,6 +1633,12 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -1885,6 +1922,17 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -2396,6 +2444,28 @@ dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -2430,6 +2500,32 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quork"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bd9640e0addc098a3481fd53bdc23970e5dd0edf6b349403aa680fb576c8f83"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"nix 0.29.0",
|
||||
"quork-proc",
|
||||
"thiserror 2.0.12",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quork-proc"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "860d36740d9412e39fff90f57010e9870b15c2b48e5325295a6f5a824a480439"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
@@ -2613,6 +2709,12 @@ version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -2667,6 +2769,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
@@ -2678,6 +2792,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@@ -2955,11 +3078,26 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@@ -2968,10 +3106,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
@@ -3083,6 +3230,17 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -3561,6 +3719,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
|
||||
+14
-13
@@ -3,7 +3,7 @@ use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use common::prelude::Instruction;
|
||||
@@ -19,27 +19,27 @@ pub mod model;
|
||||
pub mod parser;
|
||||
pub mod resolver;
|
||||
|
||||
pub fn assemble(src: &PathBuf) -> Vec<Instruction> {
|
||||
pub fn assemble(src: &Path) -> Vec<Instruction> {
|
||||
let mut modules = HashSet::<u64>::new();
|
||||
let mut program = Program::new();
|
||||
|
||||
let hash = quick_hash(src);
|
||||
modules.insert(hash);
|
||||
|
||||
match prepare_dependency(src.clone(), &mut modules, &mut program) {
|
||||
match prepare_dependency(src, &mut modules, &mut program) {
|
||||
Ok(_) => {}
|
||||
Err(err) => println!("BIG ERROR {:?}", err),
|
||||
Err(err) => println!("BIG ERROR {err:?}"),
|
||||
}
|
||||
|
||||
for node in program.nodes {
|
||||
println!("{:?}", node);
|
||||
println!("{node:?}");
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn prepare_dependency(
|
||||
path: PathBuf,
|
||||
path: &Path,
|
||||
modules: &mut HashSet<u64>,
|
||||
program: &mut Program,
|
||||
) -> Result<(), AssembleError> {
|
||||
@@ -53,9 +53,9 @@ fn prepare_dependency(
|
||||
));
|
||||
}
|
||||
|
||||
let src = fs::read_to_string(&path)
|
||||
.map_err(|_| AssembleError::InvalidFile(path.clone()))?;
|
||||
let file_hash = quick_hash(&path);
|
||||
let src = fs::read_to_string(path)
|
||||
.map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
|
||||
let file_hash = quick_hash(path);
|
||||
|
||||
log(&format!("{:20} {:20}", "Tokenising", filename));
|
||||
let tokens = lexer::lexer(src, file_hash)?;
|
||||
@@ -84,14 +84,14 @@ fn prepare_dependency(
|
||||
|
||||
if !modules.contains(&quick_hash(&dep)) {
|
||||
modules.insert(quick_hash(&dep));
|
||||
prepare_dependency(dep, modules, program)?
|
||||
prepare_dependency(dep.as_path(), modules, program)?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(src: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
|
||||
fn _build(_src: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
@@ -125,14 +125,15 @@ impl fmt::Display for AssembleError {
|
||||
}
|
||||
}
|
||||
|
||||
fn quick_hash(value: &PathBuf) -> u64 {
|
||||
fn quick_hash(value: &Path) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
value.canonicalize().unwrap().to_str().hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
|
||||
fn log(message: &str) {
|
||||
println!("\x1b[32mINFO:\x1b[0m {}", message);
|
||||
println!("\x1b[32mINFO:\x1b[0m {message}");
|
||||
}
|
||||
|
||||
// create a macro that lexes and parses the input string into Nodes
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use std::{fs, io::Write, path::PathBuf};
|
||||
|
||||
use assembler::{lexer, parser::Parser};
|
||||
use common::prelude::{ITypeArgs, Instruction, RTypeArgs, Register};
|
||||
|
||||
fn main() {
|
||||
// parse args:
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
@@ -16,10 +13,10 @@ fn main() {
|
||||
let src = PathBuf::from(input_path);
|
||||
let mut output_file = fs::File::create(output_path).unwrap();
|
||||
|
||||
let res = assembler::assemble(&src)
|
||||
assembler::assemble(&src)
|
||||
.iter()
|
||||
.map(|i| i.encode())
|
||||
.for_each(|i| {
|
||||
output_file.write_all(&i.to_be_bytes()).unwrap();
|
||||
output_file.write_all(&i.to_le_bytes()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
+54
-5
@@ -3,13 +3,12 @@ use std::{fmt, str::FromStr};
|
||||
use common::prelude::Register;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[expect(dead_code)]
|
||||
pub struct Node(pub Option<Symbol>, pub Opcode, pub Vec<Token>);
|
||||
|
||||
impl fmt::Display for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let symbol = match &self.0 {
|
||||
Some(symbol) => format!("{}", symbol),
|
||||
Some(symbol) => format!("{symbol}"),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
@@ -26,15 +25,65 @@ impl fmt::Display for Symbol {
|
||||
impl fmt::Display for Module {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Module::Unresolved(name) => write!(f, "{}", name),
|
||||
Module::Resolved(name) => write!(f, "{}", name),
|
||||
Module::Unresolved(name) => write!(f, "{name}"),
|
||||
Module::Resolved(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Opcode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
match self {
|
||||
Opcode::Nop => write!(f, "nop"),
|
||||
Opcode::Mov => write!(f, "mov"),
|
||||
Opcode::Movs => write!(f, "movs"),
|
||||
Opcode::Ldb => write!(f, "ldb"),
|
||||
Opcode::Ldbs => write!(f, "ldbs"),
|
||||
Opcode::Ldh => write!(f, "ldh"),
|
||||
Opcode::Ldhs => write!(f, "ldhs"),
|
||||
Opcode::Ldw => write!(f, "ldw"),
|
||||
Opcode::Stb => write!(f, "stb"),
|
||||
Opcode::Sth => write!(f, "sth"),
|
||||
Opcode::Stw => write!(f, "stw"),
|
||||
Opcode::Lli => write!(f, "lli"),
|
||||
Opcode::Lui => write!(f, "lui"),
|
||||
Opcode::Jmp => write!(f, "jmp"),
|
||||
Opcode::Jeq => write!(f, "jeq"),
|
||||
Opcode::Jne => write!(f, "jne"),
|
||||
Opcode::Jgt => write!(f, "jgt"),
|
||||
Opcode::Jge => write!(f, "jge"),
|
||||
Opcode::Jlt => write!(f, "jlt"),
|
||||
Opcode::Jle => write!(f, "jle"),
|
||||
Opcode::Cmp => write!(f, "cmp"),
|
||||
Opcode::Inc => write!(f, "inc"),
|
||||
Opcode::Dec => write!(f, "dec"),
|
||||
Opcode::Shl => write!(f, "shl"),
|
||||
Opcode::Shr => write!(f, "shr"),
|
||||
Opcode::Add => write!(f, "add"),
|
||||
Opcode::Sub => write!(f, "sub"),
|
||||
Opcode::And => write!(f, "and"),
|
||||
Opcode::Or => write!(f, "or"),
|
||||
Opcode::Not => write!(f, "not"),
|
||||
Opcode::Xor => write!(f, "xor"),
|
||||
Opcode::Nand => write!(f, "nand"),
|
||||
Opcode::Nor => write!(f, "nor"),
|
||||
Opcode::Xnor => write!(f, "xnor"),
|
||||
Opcode::Int => write!(f, "int"),
|
||||
Opcode::Irt => write!(f, "irt"),
|
||||
Opcode::Hlt => write!(f, "hlt"),
|
||||
Opcode::Iadd => write!(f, "iadd"),
|
||||
Opcode::Isub => write!(f, "isub"),
|
||||
Opcode::Db => write!(f, "db"),
|
||||
Opcode::Dh => write!(f, "dh"),
|
||||
Opcode::Dw => write!(f, "dw"),
|
||||
Opcode::Resb => write!(f, "resb"),
|
||||
Opcode::Resh => write!(f, "resh"),
|
||||
Opcode::Resw => write!(f, "resw"),
|
||||
Opcode::Push => write!(f, "push"),
|
||||
Opcode::Pop => write!(f, "pop"),
|
||||
Opcode::Lwi => write!(f, "lwi"),
|
||||
Opcode::Include => write!(f, "include"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use core::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use common::prelude::{Instruction, Register};
|
||||
|
||||
@@ -33,6 +31,12 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Program {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
|
||||
let mut self_ = Parser {
|
||||
|
||||
-1
Submodule dsa_editor deleted from 1566784e20
Generated
+3985
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "dsa_editor"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "a fork of a code editor egui widget adapted to work with DSA syntax."
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.31", optional = true }
|
||||
serde = { version = "1", optional = true }
|
||||
|
||||
[lib]
|
||||
name = "dsa_editor"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["egui", "editor"]
|
||||
egui = ["dep:egui"]
|
||||
editor = []
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dev-dependencies]
|
||||
eframe = "0.31"
|
||||
colorful = "0.3"
|
||||
@@ -0,0 +1,253 @@
|
||||
#[cfg(feature = "editor")]
|
||||
use super::Editor;
|
||||
|
||||
use super::syntax::{Syntax, TokenType, QUOTES, SEPARATORS};
|
||||
use std::mem;
|
||||
|
||||
#[derive(Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
|
||||
/// Lexer and Token
|
||||
pub struct Token {
|
||||
ty: TokenType,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new<S: Into<String>>(ty: TokenType, buffer: S) -> Self {
|
||||
Token {
|
||||
ty,
|
||||
buffer: buffer.into(),
|
||||
}
|
||||
}
|
||||
pub fn ty(&self) -> TokenType {
|
||||
self.ty
|
||||
}
|
||||
pub fn buffer(&self) -> &str {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn first(&mut self, c: char, syntax: &Syntax) -> Option<Self> {
|
||||
self.buffer.push(c);
|
||||
let mut token = None;
|
||||
self.ty = match c {
|
||||
c if c.is_whitespace() => {
|
||||
self.ty = TokenType::Whitespace(c);
|
||||
token = self.drain(self.ty);
|
||||
TokenType::Whitespace(c)
|
||||
}
|
||||
c if syntax.is_keyword(c.to_string().as_str()) => TokenType::Keyword,
|
||||
c if syntax.is_type(c.to_string().as_str()) => TokenType::Type,
|
||||
c if syntax.is_special(c.to_string().as_str()) => TokenType::Special,
|
||||
c if syntax.comment == c.to_string().as_str() => TokenType::Comment(false),
|
||||
c if syntax.comment_multiline[0] == c.to_string().as_str() => {
|
||||
TokenType::Comment(true)
|
||||
}
|
||||
_ => TokenType::from(c),
|
||||
};
|
||||
token
|
||||
}
|
||||
|
||||
fn drain(&mut self, ty: TokenType) -> Option<Self> {
|
||||
let mut token = None;
|
||||
if !self.buffer().is_empty() {
|
||||
token = Some(Token {
|
||||
buffer: mem::take(&mut self.buffer),
|
||||
ty: self.ty,
|
||||
});
|
||||
}
|
||||
self.ty = ty;
|
||||
token
|
||||
}
|
||||
|
||||
fn push_drain(&mut self, c: char, ty: TokenType) -> Option<Self> {
|
||||
self.buffer.push(c);
|
||||
self.drain(ty)
|
||||
}
|
||||
|
||||
fn drain_push(&mut self, c: char, ty: TokenType) -> Option<Self> {
|
||||
let token = self.drain(self.ty);
|
||||
self.buffer.push(c);
|
||||
self.ty = ty;
|
||||
token
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
/// Syntax highlighting
|
||||
pub fn highlight<T: Editor>(&mut self, editor: &T, text: &str) -> LayoutJob {
|
||||
*self = Token::default();
|
||||
let mut job = LayoutJob::default();
|
||||
for c in text.chars() {
|
||||
for token in self.automata(c, editor.syntax()) {
|
||||
editor.append(&mut job, &token);
|
||||
}
|
||||
}
|
||||
editor.append(&mut job, self);
|
||||
job
|
||||
}
|
||||
|
||||
/// Lexer
|
||||
pub fn tokens(&mut self, syntax: &Syntax, text: &str) -> Vec<Self> {
|
||||
let mut tokens: Vec<Self> = text
|
||||
.chars()
|
||||
.flat_map(|c| self.automata(c, syntax))
|
||||
.collect();
|
||||
|
||||
if !self.buffer.is_empty() {
|
||||
tokens.push(mem::take(self));
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
fn automata(&mut self, c: char, syntax: &Syntax) -> Vec<Self> {
|
||||
use TokenType as Ty;
|
||||
let mut tokens = vec![];
|
||||
match (self.ty, Ty::from(c)) {
|
||||
(Ty::Comment(false), Ty::Whitespace('\n')) => {
|
||||
self.buffer.push(c);
|
||||
let n = self.buffer.pop();
|
||||
tokens.extend(self.drain(Ty::Whitespace(c)));
|
||||
if let Some(n) = n {
|
||||
tokens.extend(self.push_drain(n, self.ty));
|
||||
}
|
||||
}
|
||||
(Ty::Comment(false), _) => {
|
||||
self.buffer.push(c);
|
||||
}
|
||||
(Ty::Comment(true), _) => {
|
||||
self.buffer.push(c);
|
||||
if self.buffer.ends_with(syntax.comment_multiline[1]) {
|
||||
tokens.extend(self.drain(Ty::Unknown));
|
||||
}
|
||||
}
|
||||
(Ty::Literal | Ty::Punctuation(_), Ty::Whitespace(_)) => {
|
||||
tokens.extend(self.drain(Ty::Whitespace(c)));
|
||||
tokens.extend(self.first(c, syntax));
|
||||
}
|
||||
(Ty::Hyperlink, Ty::Whitespace(_)) => {
|
||||
tokens.extend(self.drain(Ty::Whitespace(c)));
|
||||
tokens.extend(self.first(c, syntax));
|
||||
}
|
||||
(Ty::Hyperlink, _) => {
|
||||
self.buffer.push(c);
|
||||
}
|
||||
(Ty::Literal, _) => match c {
|
||||
c if c == '(' => {
|
||||
self.ty = Ty::Function;
|
||||
tokens.extend(self.drain(Ty::Punctuation(c)));
|
||||
tokens.extend(self.push_drain(c, Ty::Unknown));
|
||||
}
|
||||
c if !c.is_alphanumeric() && !SEPARATORS.contains(&c) => {
|
||||
tokens.extend(self.drain(self.ty));
|
||||
self.buffer.push(c);
|
||||
self.ty = if QUOTES.contains(&c) {
|
||||
Ty::Str(c)
|
||||
} else {
|
||||
Ty::Punctuation(c)
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
self.buffer.push(c);
|
||||
self.ty = {
|
||||
if self.buffer.starts_with(syntax.comment) {
|
||||
Ty::Comment(false)
|
||||
} else if self.buffer.starts_with(syntax.comment_multiline[0]) {
|
||||
Ty::Comment(true)
|
||||
} else if syntax.is_hyperlink(&self.buffer) {
|
||||
Ty::Hyperlink
|
||||
} else if syntax.is_keyword(&self.buffer) {
|
||||
Ty::Keyword
|
||||
} else if syntax.is_type(&self.buffer) {
|
||||
Ty::Type
|
||||
} else if syntax.is_special(&self.buffer) {
|
||||
Ty::Special
|
||||
} else {
|
||||
Ty::Literal
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
(Ty::Numeric(false), Ty::Punctuation('.')) => {
|
||||
self.buffer.push(c);
|
||||
self.ty = Ty::Numeric(true);
|
||||
}
|
||||
(Ty::Numeric(_), Ty::Numeric(_)) => {
|
||||
self.buffer.push(c);
|
||||
}
|
||||
(Ty::Numeric(_), Ty::Literal) => {
|
||||
tokens.extend(self.drain(self.ty));
|
||||
self.buffer.push(c);
|
||||
}
|
||||
(Ty::Numeric(_), _) | (Ty::Punctuation(_), Ty::Literal | Ty::Numeric(_)) => {
|
||||
tokens.extend(self.drain(self.ty));
|
||||
tokens.extend(self.first(c, syntax));
|
||||
}
|
||||
(Ty::Punctuation(_), Ty::Str(_)) => {
|
||||
tokens.extend(self.drain_push(c, Ty::Str(c)));
|
||||
}
|
||||
(Ty::Punctuation(_), _) => {
|
||||
if !(syntax.comment.starts_with(&self.buffer)
|
||||
|| syntax.comment_multiline[0].starts_with(&self.buffer))
|
||||
{
|
||||
tokens.extend(self.drain(self.ty));
|
||||
tokens.extend(self.first(c, syntax));
|
||||
} else {
|
||||
self.buffer.push(c);
|
||||
if self.buffer.starts_with(syntax.comment) {
|
||||
self.ty = Ty::Comment(false);
|
||||
} else if self.buffer.starts_with(syntax.comment_multiline[0]) {
|
||||
self.ty = Ty::Comment(true);
|
||||
} else if let Some(c) = self.buffer.pop() {
|
||||
tokens.extend(self.drain(Ty::Punctuation(c)));
|
||||
tokens.extend(self.first(c, syntax));
|
||||
}
|
||||
}
|
||||
}
|
||||
(Ty::Str(q), _) => {
|
||||
let control = self.buffer.ends_with('\\');
|
||||
self.buffer.push(c);
|
||||
if c == q && !control {
|
||||
tokens.extend(self.drain(Ty::Unknown));
|
||||
}
|
||||
}
|
||||
(Ty::Whitespace(_) | Ty::Unknown, _) => {
|
||||
tokens.extend(self.first(c, syntax));
|
||||
}
|
||||
// Keyword, Type, Special
|
||||
(_reserved, Ty::Literal | Ty::Numeric(_)) => {
|
||||
self.buffer.push(c);
|
||||
self.ty = if syntax.is_keyword(&self.buffer) {
|
||||
Ty::Keyword
|
||||
} else if syntax.is_type(&self.buffer) {
|
||||
Ty::Type
|
||||
} else if syntax.is_special(&self.buffer) {
|
||||
Ty::Special
|
||||
} else {
|
||||
Ty::Literal
|
||||
};
|
||||
}
|
||||
(reserved, _) => {
|
||||
self.ty = reserved;
|
||||
tokens.extend(self.drain(self.ty));
|
||||
tokens.extend(self.first(c, syntax));
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
use egui::text::LayoutJob;
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
impl<T: Editor> egui::util::cache::ComputerMut<(&T, &str), LayoutJob> for Token {
|
||||
fn compute(&mut self, (cache, text): (&T, &str)) -> LayoutJob {
|
||||
self.highlight(cache, text)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Token>;
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn highlight<T: Editor>(ctx: &egui::Context, cache: &T, text: &str) -> LayoutJob {
|
||||
ctx.memory_mut(|mem| mem.caches.cache::<HighlightCache>().get((cache, text)))
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
pub mod highlighting;
|
||||
mod syntax;
|
||||
mod themes;
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
use egui::text::LayoutJob;
|
||||
#[cfg(feature = "egui")]
|
||||
use egui::widgets::text_edit::TextEditOutput;
|
||||
pub use highlighting::Token;
|
||||
#[cfg(feature = "egui")]
|
||||
use highlighting::highlight;
|
||||
#[cfg(feature = "editor")]
|
||||
use std::hash::{Hash, Hasher};
|
||||
pub use syntax::{Syntax, TokenType};
|
||||
pub use themes::ColorTheme;
|
||||
pub use themes::DEFAULT_THEMES;
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub trait Editor: Hash {
|
||||
fn append(&self, job: &mut LayoutJob, token: &Token);
|
||||
fn syntax(&self) -> &Syntax;
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
/// CodeEditor struct which stores settings for highlighting.
|
||||
pub struct CodeEditor {
|
||||
id: String,
|
||||
theme: ColorTheme,
|
||||
syntax: Syntax,
|
||||
numlines: bool,
|
||||
numlines_shift: isize,
|
||||
numlines_only_natural: bool,
|
||||
fontsize: f32,
|
||||
rows: usize,
|
||||
stick_to_bottom: bool,
|
||||
desired_width: f32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
impl Hash for CodeEditor {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.theme.hash(state);
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
(self.fontsize as u32).hash(state);
|
||||
self.syntax.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
impl Default for CodeEditor {
|
||||
fn default() -> CodeEditor {
|
||||
CodeEditor {
|
||||
id: String::from("Code Editor"),
|
||||
theme: ColorTheme::THEME,
|
||||
syntax: Syntax::dsa(),
|
||||
numlines: true,
|
||||
numlines_shift: 0,
|
||||
numlines_only_natural: false,
|
||||
fontsize: 10.0,
|
||||
rows: 10,
|
||||
stick_to_bottom: false,
|
||||
desired_width: f32::INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
impl CodeEditor {
|
||||
pub fn id_source(self, id_source: impl Into<String>) -> Self {
|
||||
CodeEditor {
|
||||
id: id_source.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimum number of rows to show.
|
||||
///
|
||||
/// **Default: 10**
|
||||
pub fn with_rows(self, rows: usize) -> Self {
|
||||
CodeEditor { rows, ..self }
|
||||
}
|
||||
|
||||
/// Use custom Color Theme
|
||||
///
|
||||
/// **Default: Gruvbox**
|
||||
pub fn with_theme(self, theme: ColorTheme) -> Self {
|
||||
CodeEditor { theme, ..self }
|
||||
}
|
||||
|
||||
/// Use custom font size
|
||||
///
|
||||
/// **Default: 10.0**
|
||||
pub fn with_fontsize(self, fontsize: f32) -> Self {
|
||||
CodeEditor { fontsize, ..self }
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
/// Use UI font size
|
||||
pub fn with_ui_fontsize(self, ui: &mut egui::Ui) -> Self {
|
||||
CodeEditor {
|
||||
fontsize: egui::TextStyle::Monospace.resolve(ui.style()).size,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Show or hide lines numbering
|
||||
///
|
||||
/// **Default: true**
|
||||
pub fn with_numlines(self, numlines: bool) -> Self {
|
||||
CodeEditor { numlines, ..self }
|
||||
}
|
||||
|
||||
/// Shift lines numbering by this value
|
||||
///
|
||||
/// **Default: 0**
|
||||
pub fn with_numlines_shift(self, numlines_shift: isize) -> Self {
|
||||
CodeEditor {
|
||||
numlines_shift,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Show lines numbering only above zero, useful for enabling numbering since nth row
|
||||
///
|
||||
/// **Default: false**
|
||||
pub fn with_numlines_only_natural(self, numlines_only_natural: bool) -> Self {
|
||||
CodeEditor {
|
||||
numlines_only_natural,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use custom syntax for highlighting
|
||||
///
|
||||
/// **Default: Rust**
|
||||
pub fn with_syntax(self, syntax: Syntax) -> Self {
|
||||
CodeEditor { syntax, ..self }
|
||||
}
|
||||
|
||||
/// Should the containing area shrink if the content is small?
|
||||
///
|
||||
/// **Default: false**
|
||||
pub fn auto_shrink(self, shrink: bool) -> Self {
|
||||
CodeEditor {
|
||||
desired_width: if shrink { 0.0 } else { self.desired_width },
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the desired width of the code editor
|
||||
///
|
||||
/// **Default: `f32::INFINITY`**
|
||||
pub fn desired_width(self, width: f32) -> Self {
|
||||
CodeEditor {
|
||||
desired_width: width,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Stick to bottom
|
||||
/// The scroll handle will stick to the bottom position even while the content size
|
||||
/// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers.
|
||||
/// The scroll handle remains stuck until user manually changes position. Once "unstuck"
|
||||
/// it will remain focused on whatever content viewport the user left it on. If the scroll
|
||||
/// handle is dragged to the bottom it will again become stuck and remain there until manually
|
||||
/// pulled from the end position.
|
||||
///
|
||||
/// **Default: false**
|
||||
pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self {
|
||||
CodeEditor {
|
||||
stick_to_bottom,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn format(&self, ty: TokenType) -> egui::text::TextFormat {
|
||||
let font_id = egui::FontId::monospace(self.fontsize);
|
||||
let color = self.theme.type_color(ty);
|
||||
egui::text::TextFormat::simple(font_id, color)
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
fn numlines_show(&self, ui: &mut egui::Ui, text: &str) {
|
||||
let total = if text.ends_with('\n') || text.is_empty() {
|
||||
text.lines().count() + 1
|
||||
} else {
|
||||
text.lines().count()
|
||||
}
|
||||
.max(self.rows) as isize;
|
||||
let max_indent = total.to_string().len().max(
|
||||
!self.numlines_only_natural as usize * self.numlines_shift.to_string().len(),
|
||||
);
|
||||
let mut counter = (1..=total)
|
||||
.map(|i| {
|
||||
let num = i + self.numlines_shift;
|
||||
if num <= 0 && self.numlines_only_natural {
|
||||
String::new()
|
||||
} else {
|
||||
let label = num.to_string();
|
||||
format!(
|
||||
"{}{label}",
|
||||
" ".repeat(max_indent.saturating_sub(label.len()))
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let width = max_indent as f32
|
||||
* self.fontsize
|
||||
* 0.5
|
||||
* !(total + self.numlines_shift <= 0 && self.numlines_only_natural) as u8
|
||||
as f32;
|
||||
|
||||
let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| {
|
||||
let layout_job = egui::text::LayoutJob::single_section(
|
||||
string.to_string(),
|
||||
egui::TextFormat::simple(
|
||||
egui::FontId::monospace(self.fontsize),
|
||||
self.theme.type_color(TokenType::Comment(true)),
|
||||
),
|
||||
);
|
||||
ui.fonts(|f| f.layout_job(layout_job))
|
||||
};
|
||||
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut counter)
|
||||
.id_source(format!("{}_numlines", self.id))
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.interactive(false)
|
||||
.frame(false)
|
||||
.desired_rows(self.rows)
|
||||
.desired_width(width)
|
||||
.layouter(&mut layouter),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
/// Show Code Editor
|
||||
pub fn show(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
text: &mut dyn egui::TextBuffer,
|
||||
) -> TextEditOutput {
|
||||
let mut text_edit_output: Option<TextEditOutput> = None;
|
||||
let mut code_editor = |ui: &mut egui::Ui| {
|
||||
ui.horizontal_top(|h| {
|
||||
self.theme.modify_style(h, self.fontsize);
|
||||
if self.numlines {
|
||||
self.numlines_show(h, text.as_str());
|
||||
}
|
||||
egui::ScrollArea::horizontal()
|
||||
.hscroll(true)
|
||||
.id_salt(format!("{}_inner_scroll", self.id))
|
||||
.show(h, |ui| {
|
||||
let mut layouter =
|
||||
|ui: &egui::Ui, string: &str, _wrap_width: f32| {
|
||||
let layout_job = highlight(ui.ctx(), self, string);
|
||||
ui.fonts(|f| f.layout_job(layout_job))
|
||||
};
|
||||
let output = egui::TextEdit::multiline(text)
|
||||
.id_source(&self.id)
|
||||
.lock_focus(true)
|
||||
.desired_rows(self.rows)
|
||||
.frame(false)
|
||||
.desired_width(self.desired_width)
|
||||
.layouter(&mut layouter)
|
||||
.show(ui);
|
||||
text_edit_output = Some(output);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt(format!("{}_outer_scroll", self.id))
|
||||
.stick_to_bottom(self.stick_to_bottom)
|
||||
.show(ui, code_editor);
|
||||
|
||||
text_edit_output.expect("TextEditOutput should exist at this point")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
#[cfg(feature = "egui")]
|
||||
impl Editor for CodeEditor {
|
||||
fn append(&self, job: &mut LayoutJob, token: &Token) {
|
||||
job.append(token.buffer(), 0.0, self.format(token.ty()));
|
||||
}
|
||||
|
||||
fn syntax(&self) -> &Syntax {
|
||||
&self.syntax
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use super::Syntax;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
impl Syntax {
|
||||
pub fn dsa() -> Self {
|
||||
Syntax {
|
||||
language: "Assembly",
|
||||
case_sensitive: false,
|
||||
comment: "//",
|
||||
comment_multiline: ["/*", "*/"],
|
||||
hyperlinks: BTreeSet::from(["http"]),
|
||||
keywords: BTreeSet::from([
|
||||
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth",
|
||||
"stw", "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle",
|
||||
"cmp", "inc", "dec", "shl", "shr", "add", "sub", "and", "or", "not",
|
||||
"xor", "nand", "nor", "xnor", "irt", "int", "hlt",
|
||||
// pseduo-instructions
|
||||
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call",
|
||||
"ret",
|
||||
]),
|
||||
types: BTreeSet::from(["ptr", "byte", "word", "dword", "qword"]),
|
||||
special: BTreeSet::from([
|
||||
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9",
|
||||
"rga", "rgb", "rgc", "rgd", "rge", "rgf", "acc", "spr", "bpr", "ret",
|
||||
"idr", "mmr", "zero", "null",
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod dsa;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
pub const SEPARATORS: [char; 1] = ['_'];
|
||||
pub const QUOTES: [char; 3] = ['\'', '"', '`'];
|
||||
|
||||
type MultiLine = bool;
|
||||
type Float = bool;
|
||||
|
||||
#[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum TokenType {
|
||||
Comment(MultiLine),
|
||||
Function,
|
||||
Keyword,
|
||||
Literal,
|
||||
Hyperlink,
|
||||
Numeric(Float),
|
||||
Punctuation(char),
|
||||
Special,
|
||||
Str(char),
|
||||
Type,
|
||||
Whitespace(char),
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
impl std::fmt::Debug for TokenType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut name = String::new();
|
||||
match &self {
|
||||
TokenType::Comment(multiline) => {
|
||||
name.push_str("Comment");
|
||||
{
|
||||
if *multiline {
|
||||
name.push_str(" MultiLine");
|
||||
} else {
|
||||
name.push_str(" SingleLine");
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenType::Function => name.push_str("Function"),
|
||||
TokenType::Keyword => name.push_str("Keyword"),
|
||||
TokenType::Literal => name.push_str("Literal"),
|
||||
TokenType::Hyperlink => name.push_str("Hyperlink"),
|
||||
TokenType::Numeric(float) => {
|
||||
name.push_str("Numeric");
|
||||
if *float {
|
||||
name.push_str(" Float");
|
||||
} else {
|
||||
name.push_str(" Integer");
|
||||
}
|
||||
}
|
||||
TokenType::Punctuation(_) => name.push_str("Punctuation"),
|
||||
TokenType::Special => name.push_str("Special"),
|
||||
TokenType::Str(quote) => {
|
||||
name.push_str("Str ");
|
||||
name.push(*quote);
|
||||
}
|
||||
TokenType::Type => name.push_str("Type"),
|
||||
TokenType::Whitespace(c) => {
|
||||
name.push_str("Whitespace");
|
||||
match c {
|
||||
' ' => name.push_str(" Space"),
|
||||
'\t' => name.push_str(" Tab"),
|
||||
'\n' => name.push_str(" New Line"),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
TokenType::Unknown => name.push_str("Unknown"),
|
||||
};
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
impl From<char> for TokenType {
|
||||
fn from(c: char) -> Self {
|
||||
match c {
|
||||
c if c.is_whitespace() => TokenType::Whitespace(c),
|
||||
c if QUOTES.contains(&c) => TokenType::Str(c),
|
||||
c if c.is_numeric() => TokenType::Numeric(false),
|
||||
c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal,
|
||||
c if c.is_ascii_punctuation() => TokenType::Punctuation(c),
|
||||
_ => TokenType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
/// Rules for highlighting.
|
||||
pub struct Syntax {
|
||||
pub language: &'static str,
|
||||
pub case_sensitive: bool,
|
||||
pub comment: &'static str,
|
||||
pub comment_multiline: [&'static str; 2],
|
||||
pub hyperlinks: BTreeSet<&'static str>,
|
||||
pub keywords: BTreeSet<&'static str>,
|
||||
pub types: BTreeSet<&'static str>,
|
||||
pub special: BTreeSet<&'static str>,
|
||||
}
|
||||
impl Default for Syntax {
|
||||
fn default() -> Self {
|
||||
Syntax::dsa()
|
||||
}
|
||||
}
|
||||
impl Hash for Syntax {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.language.hash(state);
|
||||
}
|
||||
}
|
||||
impl Syntax {
|
||||
pub fn new(language: &'static str) -> Self {
|
||||
Syntax {
|
||||
language,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn with_case_sensitive(self, case_sensitive: bool) -> Self {
|
||||
Syntax {
|
||||
case_sensitive,
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_comment(self, comment: &'static str) -> Self {
|
||||
Syntax { comment, ..self }
|
||||
}
|
||||
pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self {
|
||||
Syntax {
|
||||
comment_multiline,
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_hyperlinks<T: Into<BTreeSet<&'static str>>>(self, hyperlinks: T) -> Self {
|
||||
Syntax {
|
||||
hyperlinks: hyperlinks.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_keywords<T: Into<BTreeSet<&'static str>>>(self, keywords: T) -> Self {
|
||||
Syntax {
|
||||
keywords: keywords.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_types<T: Into<BTreeSet<&'static str>>>(self, types: T) -> Self {
|
||||
Syntax {
|
||||
types: types.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn with_special<T: Into<BTreeSet<&'static str>>>(self, special: T) -> Self {
|
||||
Syntax {
|
||||
special: special.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language(&self) -> &str {
|
||||
self.language
|
||||
}
|
||||
pub fn comment(&self) -> &str {
|
||||
self.comment
|
||||
}
|
||||
pub fn is_hyperlink(&self, word: &str) -> bool {
|
||||
self.hyperlinks.contains(word.to_ascii_lowercase().as_str())
|
||||
}
|
||||
pub fn is_keyword(&self, word: &str) -> bool {
|
||||
if self.case_sensitive {
|
||||
self.keywords.contains(&word)
|
||||
} else {
|
||||
self.keywords.contains(word.to_ascii_lowercase().as_str())
|
||||
}
|
||||
}
|
||||
pub fn is_type(&self, word: &str) -> bool {
|
||||
if self.case_sensitive {
|
||||
self.types.contains(&word)
|
||||
} else {
|
||||
self.types.contains(word.to_ascii_lowercase().as_str())
|
||||
}
|
||||
}
|
||||
pub fn is_special(&self, word: &str) -> bool {
|
||||
if self.case_sensitive {
|
||||
self.special.contains(&word)
|
||||
} else {
|
||||
self.special.contains(word.to_ascii_lowercase().as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Syntax {
|
||||
pub fn simple(comment: &'static str) -> Self {
|
||||
Syntax {
|
||||
language: "",
|
||||
case_sensitive: false,
|
||||
comment,
|
||||
comment_multiline: [comment; 2],
|
||||
hyperlinks: BTreeSet::new(),
|
||||
keywords: BTreeSet::new(),
|
||||
types: BTreeSet::new(),
|
||||
special: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod theme;
|
||||
|
||||
use super::syntax::TokenType;
|
||||
#[cfg(feature = "egui")]
|
||||
use egui::Color32;
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub const ERROR_COLOR: Color32 = Color32::from_rgb(255, 0, 255);
|
||||
|
||||
/// Array of default themes.
|
||||
pub const DEFAULT_THEMES: [ColorTheme; 1] = [ColorTheme::THEME];
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
fn color_from_hex(hex: &str) -> Option<Color32> {
|
||||
if hex == "none" {
|
||||
return Some(Color32::from_rgba_premultiplied(255, 0, 255, 0));
|
||||
}
|
||||
let rgb = (1..hex.len())
|
||||
.step_by(2)
|
||||
.filter_map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok())
|
||||
.collect::<Vec<u8>>();
|
||||
let color = Color32::from_rgb(*rgb.first()?, *rgb.get(1)?, *rgb.get(2)?);
|
||||
Some(color)
|
||||
}
|
||||
|
||||
#[derive(Hash, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Colors in hexadecimal notation as used in HTML and CSS.
|
||||
pub struct ColorTheme {
|
||||
pub name: &'static str,
|
||||
pub dark: bool,
|
||||
pub bg: &'static str,
|
||||
pub cursor: &'static str,
|
||||
pub selection: &'static str,
|
||||
pub comments: &'static str,
|
||||
pub functions: &'static str,
|
||||
pub keywords: &'static str,
|
||||
pub literals: &'static str,
|
||||
pub numerics: &'static str,
|
||||
pub punctuation: &'static str,
|
||||
pub strs: &'static str,
|
||||
pub types: &'static str,
|
||||
pub special: &'static str,
|
||||
}
|
||||
|
||||
impl Default for ColorTheme {
|
||||
fn default() -> Self {
|
||||
ColorTheme::THEME
|
||||
}
|
||||
}
|
||||
impl ColorTheme {
|
||||
pub fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
pub fn is_dark(&self) -> bool {
|
||||
self.dark
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn bg(&self) -> Color32 {
|
||||
color_from_hex(self.bg).unwrap_or(ERROR_COLOR)
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn cursor(&self) -> Color32 {
|
||||
color_from_hex(self.cursor).unwrap_or(ERROR_COLOR)
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn selection(&self) -> Color32 {
|
||||
color_from_hex(self.selection).unwrap_or(ERROR_COLOR)
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn modify_style(&self, ui: &mut egui::Ui, fontsize: f32) {
|
||||
let style = ui.style_mut();
|
||||
style.visuals.widgets.noninteractive.bg_fill = self.bg();
|
||||
style.visuals.window_fill = self.bg();
|
||||
style.visuals.selection.stroke.color = self.cursor();
|
||||
style.visuals.selection.bg_fill = self.selection();
|
||||
style.visuals.extreme_bg_color = self.bg();
|
||||
style.override_font_id = Some(egui::FontId::monospace(fontsize));
|
||||
style.visuals.text_cursor.stroke.width = fontsize * 0.1;
|
||||
}
|
||||
|
||||
pub const fn type_color_str(&self, ty: TokenType) -> &'static str {
|
||||
match ty {
|
||||
TokenType::Comment(_) => self.comments,
|
||||
TokenType::Function => self.functions,
|
||||
TokenType::Keyword => self.keywords,
|
||||
TokenType::Literal => self.literals,
|
||||
TokenType::Hyperlink => self.special,
|
||||
TokenType::Numeric(_) => self.numerics,
|
||||
TokenType::Punctuation(_) => self.punctuation,
|
||||
TokenType::Special => self.special,
|
||||
TokenType::Str(_) => self.strs,
|
||||
TokenType::Type => self.types,
|
||||
TokenType::Whitespace(_) | TokenType::Unknown => self.comments,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "egui")]
|
||||
pub fn type_color(&self, ty: TokenType) -> Color32 {
|
||||
match ty {
|
||||
TokenType::Comment(_) => color_from_hex(self.comments),
|
||||
TokenType::Function => color_from_hex(self.functions),
|
||||
TokenType::Keyword => color_from_hex(self.keywords),
|
||||
TokenType::Literal => color_from_hex(self.literals),
|
||||
TokenType::Hyperlink => color_from_hex(self.special),
|
||||
TokenType::Numeric(_) => color_from_hex(self.numerics),
|
||||
TokenType::Punctuation(_) => color_from_hex(self.punctuation),
|
||||
TokenType::Special => color_from_hex(self.special),
|
||||
TokenType::Str(_) => color_from_hex(self.strs),
|
||||
TokenType::Type => color_from_hex(self.types),
|
||||
TokenType::Whitespace(_) | TokenType::Unknown => {
|
||||
color_from_hex(self.comments)
|
||||
}
|
||||
}
|
||||
.unwrap_or(ERROR_COLOR)
|
||||
}
|
||||
|
||||
pub fn monocolor(
|
||||
dark: bool,
|
||||
bg: &'static str,
|
||||
fg: &'static str,
|
||||
cursor: &'static str,
|
||||
selection: &'static str,
|
||||
) -> Self {
|
||||
ColorTheme {
|
||||
name: "monocolor",
|
||||
dark,
|
||||
bg,
|
||||
cursor,
|
||||
selection,
|
||||
literals: fg,
|
||||
numerics: fg,
|
||||
keywords: fg,
|
||||
functions: fg,
|
||||
punctuation: fg,
|
||||
types: fg,
|
||||
strs: fg,
|
||||
comments: fg,
|
||||
special: fg,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use super::ColorTheme;
|
||||
|
||||
impl ColorTheme {
|
||||
/// Author : Jakub Bartodziej <kubabartodziej@gmail.com>
|
||||
/// Theme uses the gruvbox dark palette with standard contrast <https://github.com/morhetz/gruvbox>
|
||||
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
|
||||
};
|
||||
}
|
||||
+6
-1
@@ -10,9 +10,14 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
common = { path = "../common" }
|
||||
assembler = { path = "../assembler" }
|
||||
dsa_editor = { path = "../dsa_editor" }
|
||||
dsa_editor = { git = "https://github.com/zxq5-dev/egui_code_editor", package = "egui_code_editor", rev = "5eb313e" }
|
||||
eframe = "0.31.1"
|
||||
egui = "0.31.1"
|
||||
rfd = "0.15.3"
|
||||
dirs = "6.0.0"
|
||||
discord-presence = { version = "1.6.0", optional = true }
|
||||
toml = { version = "0.8.23", optional = true }
|
||||
|
||||
[features]
|
||||
discord-rpc = ["dep:discord-presence"]
|
||||
config = ["dep:toml"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
//! Loads configuration information from a TOML file in the current working directory.
|
||||
//! Currently doesn't do much but this may be expanded.
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "discord-rpc")]
|
||||
pub mod rpc;
|
||||
@@ -0,0 +1,56 @@
|
||||
//! Just for fun I thought I would add a Discord RPC client to the emulator.
|
||||
//!
|
||||
//! This will display information like the current value of PCX, architecture name and
|
||||
//! GitHub repo links to show off the ISA. Perhaps in the future if we cross-compile to
|
||||
//! WASM we could include a link to run this software in the browser.
|
||||
//!
|
||||
//!
|
||||
//! # Configuration
|
||||
//!
|
||||
//! This may be disabled like so in your `.dsarc.toml` file:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [misc]
|
||||
//! use_discord_rpc = false
|
||||
//! ```
|
||||
//!
|
||||
//! Alternatively, you can hide this in your Discord settings.
|
||||
|
||||
use discord_presence::{Client, DiscordError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DiscordRpcError {
|
||||
Client(DiscordError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DiscordRpcError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Client(why) => write!(f, "discord RPC error: {why}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for DiscordRpcError {}
|
||||
|
||||
impl From<DiscordError> for DiscordRpcError {
|
||||
fn from(err: DiscordError) -> Self {
|
||||
Self::Client(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the Discord RPC client.
|
||||
#[expect(clippy::unreadable_literal)]
|
||||
pub fn start_rpc() -> Result<Client, DiscordRpcError> {
|
||||
let mut client = discord_presence::Client::new(1384303074088190042);
|
||||
|
||||
_ = client.on_ready(|ctx| {
|
||||
eprintln!("The discord RPC client is ready. Got event {:?}", ctx.event);
|
||||
});
|
||||
|
||||
client.start();
|
||||
|
||||
client.set_activity(|act| act)?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
#[cfg(feature = "config")]
|
||||
pub mod config;
|
||||
pub mod misc;
|
||||
pub mod system;
|
||||
pub mod ui;
|
||||
|
||||
@@ -78,7 +78,7 @@ impl MemoryUnit for MainStore {
|
||||
bytes[1] = block.data[(offset + 1) as usize];
|
||||
bytes[2] = block.data[(offset + 2) as usize];
|
||||
bytes[3] = block.data[(offset + 3) as usize];
|
||||
u32::from_be_bytes(bytes)
|
||||
u32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
|
||||
|
||||
@@ -8,7 +8,9 @@ use crate::emulator::system::{
|
||||
model::{IODevice, RegFile},
|
||||
};
|
||||
|
||||
use common::instructions::{Instruction, Interrupt, Register, errors::InstructionDecodeError};
|
||||
use common::instructions::{
|
||||
Instruction, Interrupt, Register, errors::InstructionDecodeError,
|
||||
};
|
||||
|
||||
pub struct Processor {
|
||||
pub memory: Box<dyn MemoryUnit>,
|
||||
@@ -168,71 +170,90 @@ impl Executable for Instruction {
|
||||
*cpu.reg(a.dr) = cpu.get(a.sr1);
|
||||
}
|
||||
|
||||
// Copies from SrcReg to a.drReg, sign extending the value to take up a full word.
|
||||
// Copies from SrcReg to a.drReg, sign extending the value to take up a full
|
||||
// word.
|
||||
Self::MovSigned(a) => {
|
||||
*cpu.reg(a.dr) = sign_extend(cpu.get(a.sr1));
|
||||
}
|
||||
|
||||
// Loads a byte from memory address (base + offset) into a.drReg. The effective address must be byte-aligned.
|
||||
// Loads a byte from memory address (base + offset) into a.drReg. The
|
||||
// effective address must be byte-aligned.
|
||||
Self::LoadByte(a) => {
|
||||
*cpu.reg(a.r2) =
|
||||
u32::from(cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)));
|
||||
*cpu.reg(a.r2) = u32::from(
|
||||
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)),
|
||||
);
|
||||
}
|
||||
|
||||
// Loads a sign-extended byte from memory address (base + offset) into a.drReg. The effective address must be byte-aligned.
|
||||
// Loads a sign-extended byte from memory address (base + offset) into
|
||||
// a.drReg. The effective address must be byte-aligned.
|
||||
Self::LoadByteSigned(a) => {
|
||||
*cpu.reg(a.r2) = sign_extend(u32::from(
|
||||
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)),
|
||||
));
|
||||
}
|
||||
|
||||
// Loads a half-word from memory address (base + offset) into a.drReg. The effective address must be 2-byte-aligned.
|
||||
// Loads a half-word from memory address (base + offset) into a.drReg. The
|
||||
// effective address must be 2-byte-aligned.
|
||||
Self::LoadHalfword(a) => {
|
||||
// we read an entire word, then right shift so we only get the first half of the word
|
||||
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16;
|
||||
}
|
||||
|
||||
// Loads a sign-extended half-word from memory address (base + offset) into a.drReg. The effective address must be 2-byte-aligned.
|
||||
Self::LoadHalfwordSigned(a) => {
|
||||
// we read an entire word, then right shift so we only get the first half
|
||||
// of the word
|
||||
*cpu.reg(a.r2) =
|
||||
sign_extend(cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16);
|
||||
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16;
|
||||
}
|
||||
|
||||
// Loads a word from memory address (base + offset) into a.drReg. The effective address must be 4-byte-aligned.
|
||||
// Loads a sign-extended half-word from memory address (base + offset) into
|
||||
// a.drReg. The effective address must be 2-byte-aligned.
|
||||
Self::LoadHalfwordSigned(a) => {
|
||||
*cpu.reg(a.r2) = sign_extend(
|
||||
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16,
|
||||
);
|
||||
}
|
||||
|
||||
// Loads a word from memory address (base + offset) into a.drReg. The
|
||||
// effective address must be 4-byte-aligned.
|
||||
Self::LoadWord(a) => {
|
||||
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate));
|
||||
*cpu.reg(a.r2) =
|
||||
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate));
|
||||
}
|
||||
|
||||
// Stores a byte from SrcReg in memory address (base + offset) The effective address must be byte-aligned.
|
||||
// Stores a byte from SrcReg in memory address (base + offset) The effective
|
||||
// address must be byte-aligned.
|
||||
Self::StoreByte(a) => {
|
||||
cpu.memory
|
||||
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2) as u8);
|
||||
cpu.memory.write_byte(
|
||||
cpu.get(a.r1) + u32::from(a.immediate),
|
||||
cpu.get(a.r2) as u8,
|
||||
);
|
||||
}
|
||||
|
||||
// Stores a half-word from SrcReg in memory address (base + offset) The effective address must be 2-byte-aligned.
|
||||
// Stores a half-word from SrcReg in memory address (base + offset) The
|
||||
// effective address must be 2-byte-aligned.
|
||||
Self::StoreHalfword(a) => {
|
||||
// split the value into bytes and then write two bytes
|
||||
let bytes = (cpu.get(a.r1) as u16).to_be_bytes();
|
||||
let bytes = (cpu.get(a.r1) as u16).to_le_bytes();
|
||||
cpu.memory
|
||||
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]);
|
||||
cpu.memory
|
||||
.write_byte(cpu.get(a.r1) + u32::from(a.immediate) + 1, bytes[1]);
|
||||
}
|
||||
|
||||
// Stores a word from SrcReg in memory address (base + offset) The effective address must be 4-byte-aligned.
|
||||
// Stores a word from SrcReg in memory address (base + offset) The effective
|
||||
// address must be 4-byte-aligned.
|
||||
Self::StoreWord(a) => {
|
||||
cpu.memory
|
||||
.write_word(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2));
|
||||
}
|
||||
|
||||
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the word. To populate the upper 16 bits, see LUI.
|
||||
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
|
||||
// word. To populate the upper 16 bits, see LUI.
|
||||
Self::LoadLowerImmediate(a) => {
|
||||
*cpu.reg(a.r1) = u32::from(a.immediate);
|
||||
}
|
||||
|
||||
// Loads a 16-bit literal value into reg, setting the top 16 bits of the word. To populate the lower 16 bits, see LLI.
|
||||
// Loads a 16-bit literal value into reg, setting the top 16 bits of the word.
|
||||
// To populate the lower 16 bits, see LLI.
|
||||
Self::LoadUpperImmediate(a) => {
|
||||
*cpu.reg(a.r1) = (cpu.get(a.r1) & 0x0000_FFFF) | u32::from(a.immediate) << 16;
|
||||
*cpu.reg(a.r1) =
|
||||
(cpu.get(a.r1) & 0x0000_FFFF) | u32::from(a.immediate) << 16;
|
||||
}
|
||||
|
||||
// Unconditionally jumps to the calculated address or direct address
|
||||
@@ -259,7 +280,8 @@ impl Executable for Instruction {
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if greater than flag or equal flag set.
|
||||
// Jumps to the calculated address or direct address if greater than flag or
|
||||
// equal flag set.
|
||||
Self::JumpGe(a) => {
|
||||
if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
@@ -273,7 +295,8 @@ impl Executable for Instruction {
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if less than flag or equal flag set.
|
||||
// Jumps to the calculated address or direct address if less than flag or
|
||||
// equal flag set.
|
||||
Self::JumpLe(a) => {
|
||||
if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
@@ -286,20 +309,24 @@ impl Executable for Instruction {
|
||||
// Decrements the value in the given register
|
||||
Self::Decrement(a) => *cpu.reg(a.sr1) = dec(cpu.get(a.sr1)),
|
||||
|
||||
// Left shifts the value in Reg by the given amount (either a register, or a literal value)
|
||||
// Left shifts the value in Reg by the given amount (either a register, or a
|
||||
// literal value)
|
||||
Self::ShiftLeft(a) => {
|
||||
let regval = cpu.get(a.sr2);
|
||||
let val = cpu.get(a.sr1);
|
||||
|
||||
*cpu.reg(a.sr1) = shl(val, if regval != 0 { regval as u8 } else { a.shamt });
|
||||
*cpu.reg(a.sr1) =
|
||||
shl(val, if regval != 0 { regval as u8 } else { a.shamt });
|
||||
}
|
||||
|
||||
// Right shifts the value in Reg by the given amount (either a register, or a literal value).
|
||||
// Right shifts the value in Reg by the given amount (either a register, or a
|
||||
// literal value).
|
||||
Self::ShiftRight(a) => {
|
||||
let regval = cpu.get(a.sr2);
|
||||
let val = cpu.get(a.sr1);
|
||||
|
||||
*cpu.reg(a.sr1) = shr(val, if regval != 0 { regval as u8 } else { a.shamt });
|
||||
*cpu.reg(a.sr1) =
|
||||
shr(val, if regval != 0 { regval as u8 } else { a.shamt });
|
||||
}
|
||||
|
||||
// Adds the value of Src2 to Src1 and writes the result to a.dr
|
||||
@@ -333,7 +360,8 @@ impl Executable for Instruction {
|
||||
// Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr
|
||||
Self::Xnor(a) => *cpu.reg(a.dr) = xnor(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Compares the value of Reg1 to the value in Reg2. The results of the comparisons are set in the Status register.
|
||||
// Compares the value of Reg1 to the value in Reg2. The results of the
|
||||
// comparisons are set in the Status register.
|
||||
Self::Compare(a) => {
|
||||
cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2));
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ impl Component for Editor {
|
||||
|
||||
if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) {
|
||||
self.save();
|
||||
};
|
||||
}
|
||||
|
||||
self.render_toolbar(state, ui, ctx);
|
||||
|
||||
@@ -87,17 +87,23 @@ impl Editor {
|
||||
fn filename(&self) -> &str {
|
||||
self.path
|
||||
.file_name()
|
||||
.unwrap_or(OsStr::new("Unnamed!"))
|
||||
.unwrap_or_else(|| OsStr::new("Unnamed!"))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.map_or_else(
|
||||
|| unreachable!("File name should be valid UTF-8."),
|
||||
|ext| ext,
|
||||
)
|
||||
}
|
||||
|
||||
fn extension(&self) -> &str {
|
||||
self.path
|
||||
.extension()
|
||||
.unwrap_or(OsStr::new("Unknown!"))
|
||||
.unwrap_or_else(|| OsStr::new("Unknown!"))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.map_or_else(
|
||||
|| unreachable!("File name should be valid UTF-8."),
|
||||
|ext| ext,
|
||||
)
|
||||
}
|
||||
|
||||
fn save(&mut self) {
|
||||
@@ -172,7 +178,7 @@ impl Editor {
|
||||
bytes[i] = byte;
|
||||
}
|
||||
}
|
||||
let value = u32::from_be_bytes(bytes);
|
||||
let value = u32::from_le_bytes(bytes);
|
||||
|
||||
// Address column
|
||||
ui.with_layout(
|
||||
@@ -210,10 +216,10 @@ impl Editor {
|
||||
);
|
||||
|
||||
// Instruction column
|
||||
let instruction = match Instruction::decode(value) {
|
||||
Ok(instruction) => instruction.to_string(),
|
||||
Err(_) => format!("{value:10}"),
|
||||
};
|
||||
let instruction = Instruction::decode(value).map_or_else(
|
||||
|_| format!("{value:10}"),
|
||||
|instruction| instruction.to_string(),
|
||||
);
|
||||
|
||||
ui.label(
|
||||
egui::RichText::new(instruction)
|
||||
@@ -230,19 +236,25 @@ impl Editor {
|
||||
fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
|
||||
let available_width = ui.available_width();
|
||||
let syntax = match self.extension() {
|
||||
"dsa" => Syntax::dsa(),
|
||||
_ => Syntax::dsa(),
|
||||
"dsa" => Some(Syntax::new("dsa")),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
CodeEditor::default()
|
||||
let ed = CodeEditor::default()
|
||||
.id_source("editor")
|
||||
.with_fontsize(12.0)
|
||||
.with_rows(0)
|
||||
.with_theme(ColorTheme::default())
|
||||
.with_syntax(syntax)
|
||||
.with_numlines(true)
|
||||
.desired_width(available_width - 450.0)
|
||||
.show(ui, &mut self.text);
|
||||
.desired_width(available_width - 450.0);
|
||||
|
||||
let mut editor = ed.clone();
|
||||
|
||||
if let Some(syntax) = syntax {
|
||||
editor = ed.with_syntax(syntax);
|
||||
}
|
||||
|
||||
editor.show(ui, &mut self.text);
|
||||
}
|
||||
|
||||
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
include print "../resources/dsa/print.dsa"
|
||||
// include print "../resources/dsa/print.dsa"
|
||||
// Fibonacci sequence calculator in DSA assembly
|
||||
// Calculates the first 8 Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
|
||||
dw fib_count: 6 // How many more numbers to calculate after F(0) and F(1)
|
||||
|
||||
init:
|
||||
// Initialize the first two Fibonacci numbers
|
||||
lli 0, rg0 // F(0) = 0
|
||||
lli 1, rg1 // F(1) = 1
|
||||
|
||||
lli rg0, 0 // F(0) = 0
|
||||
lli rg1, 1 // F(1) = 1
|
||||
push rg0
|
||||
// Load loop counter
|
||||
ldw fib_count, zero, rg2 // Load number of iterations remaining
|
||||
|
||||
@@ -21,7 +21,7 @@ fibonacci_loop:
|
||||
|
||||
// Decrement loop counter
|
||||
dec rg2 // rg2 = rg2 - 1
|
||||
|
||||
|
||||
// Check if we should continue looping
|
||||
cmp rg2, zero // Compare counter with 0
|
||||
jgt fibonacci_loop // Jump back if counter > 0
|
||||
|
||||
@@ -10,8 +10,8 @@ init:
|
||||
|
||||
start:
|
||||
ldb length, rg0
|
||||
lwi string, rg1
|
||||
lwi display, rg2
|
||||
lwi rg1, string
|
||||
lwi rg2, display
|
||||
|
||||
loop:
|
||||
// read from string and write to display
|
||||
|
||||
Reference in New Issue
Block a user