merge commit

This commit is contained in:
2025-06-17 23:50:16 +01:00
25 changed files with 5387 additions and 102 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
{ {
"rust-analyzer.check.command": "clippy", "rust-analyzer.check.command": "clippy",
"editor.formatOnSave": true "editor.formatOnSave": true,
"rust-analyzer.cargo.features": "all"
} }
Generated
+185 -18
View File
@@ -558,6 +558,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "byteorder-lite" name = "byteorder-lite"
version = "0.1.0" version = "0.1.0"
@@ -653,12 +659,6 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "colorful"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef"
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@@ -750,6 +750,15 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@@ -800,7 +809,29 @@ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "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]] [[package]]
@@ -872,16 +903,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "dsa_editor"
version = "0.1.0"
dependencies = [
"colorful",
"eframe",
"egui",
"serde",
]
[[package]] [[package]]
name = "ecolor" name = "ecolor"
version = "0.31.1" version = "0.31.1"
@@ -984,6 +1005,14 @@ dependencies = [
"winit", "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]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.31.1" version = "0.31.1"
@@ -1018,10 +1047,12 @@ dependencies = [
"assembler", "assembler",
"common", "common",
"dirs", "dirs",
"dsa_editor", "discord-presence",
"eframe", "eframe",
"egui", "egui",
"egui_code_editor",
"rfd", "rfd",
"toml",
] ]
[[package]] [[package]]
@@ -1602,6 +1633,12 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jni" name = "jni"
version = "0.21.1" version = "0.21.1"
@@ -1885,6 +1922,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" 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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -2396,6 +2444,28 @@ dependencies = [
"toml_edit", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.95"
@@ -2430,6 +2500,32 @@ dependencies = [
"memchr", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@@ -2613,6 +2709,12 @@ version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@@ -2667,6 +2769,18 @@ dependencies = [
"syn", "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]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.20" version = "0.1.20"
@@ -2678,6 +2792,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@@ -2955,11 +3078,26 @@ dependencies = [
"zerovec", "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]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.11" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@@ -2968,10 +3106,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde",
"serde_spanned",
"toml_datetime", "toml_datetime",
"toml_write",
"winnow", "winnow",
] ]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.41" version = "0.1.41"
@@ -3083,6 +3230,17 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 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]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@@ -3561,6 +3719,15 @@ dependencies = [
"windows-targets 0.52.6", "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]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.42.2" version = "0.42.2"
+14 -13
View File
@@ -3,7 +3,7 @@ use std::{
collections::HashSet, collections::HashSet,
fs, fs,
hash::{DefaultHasher, Hash, Hasher}, hash::{DefaultHasher, Hash, Hasher},
path::PathBuf, path::{Path, PathBuf},
}; };
use common::prelude::Instruction; use common::prelude::Instruction;
@@ -19,27 +19,27 @@ pub mod model;
pub mod parser; pub mod parser;
pub mod resolver; 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 modules = HashSet::<u64>::new();
let mut program = Program::new(); let mut program = Program::new();
let hash = quick_hash(src); let hash = quick_hash(src);
modules.insert(hash); modules.insert(hash);
match prepare_dependency(src.clone(), &mut modules, &mut program) { match prepare_dependency(src, &mut modules, &mut program) {
Ok(_) => {} Ok(_) => {}
Err(err) => println!("BIG ERROR {:?}", err), Err(err) => println!("BIG ERROR {err:?}"),
} }
for node in program.nodes { for node in program.nodes {
println!("{:?}", node); println!("{node:?}");
} }
vec![] vec![]
} }
fn prepare_dependency( fn prepare_dependency(
path: PathBuf, path: &Path,
modules: &mut HashSet<u64>, modules: &mut HashSet<u64>,
program: &mut Program, program: &mut Program,
) -> Result<(), AssembleError> { ) -> Result<(), AssembleError> {
@@ -53,9 +53,9 @@ fn prepare_dependency(
)); ));
} }
let src = fs::read_to_string(&path) let src = fs::read_to_string(path)
.map_err(|_| AssembleError::InvalidFile(path.clone()))?; .map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
let file_hash = quick_hash(&path); let file_hash = quick_hash(path);
log(&format!("{:20} {:20}", "Tokenising", filename)); log(&format!("{:20} {:20}", "Tokenising", filename));
let tokens = lexer::lexer(src, file_hash)?; let tokens = lexer::lexer(src, file_hash)?;
@@ -84,14 +84,14 @@ fn prepare_dependency(
if !modules.contains(&quick_hash(&dep)) { if !modules.contains(&quick_hash(&dep)) {
modules.insert(quick_hash(&dep)); modules.insert(quick_hash(&dep));
prepare_dependency(dep, modules, program)? prepare_dependency(dep.as_path(), modules, program)?
} }
} }
Ok(()) Ok(())
} }
fn build(src: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> { fn _build(_src: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
Ok(vec![]) 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(); let mut hasher = DefaultHasher::new();
value.canonicalize().unwrap().to_str().hash(&mut hasher); value.canonicalize().unwrap().to_str().hash(&mut hasher);
hasher.finish() hasher.finish()
} }
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
fn log(message: &str) { 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 // create a macro that lexes and parses the input string into Nodes
+2 -5
View File
@@ -1,8 +1,5 @@
use std::{fs, io::Write, path::PathBuf}; use std::{fs, io::Write, path::PathBuf};
use assembler::{lexer, parser::Parser};
use common::prelude::{ITypeArgs, Instruction, RTypeArgs, Register};
fn main() { fn main() {
// parse args: // parse args:
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
@@ -16,10 +13,10 @@ fn main() {
let src = PathBuf::from(input_path); let src = PathBuf::from(input_path);
let mut output_file = fs::File::create(output_path).unwrap(); let mut output_file = fs::File::create(output_path).unwrap();
let res = assembler::assemble(&src) assembler::assemble(&src)
.iter() .iter()
.map(|i| i.encode()) .map(|i| i.encode())
.for_each(|i| { .for_each(|i| {
output_file.write_all(&i.to_be_bytes()).unwrap(); output_file.write_all(&i.to_le_bytes()).unwrap();
}); });
} }
+54 -5
View File
@@ -3,13 +3,12 @@ use std::{fmt, str::FromStr};
use common::prelude::Register; use common::prelude::Register;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[expect(dead_code)]
pub struct Node(pub Option<Symbol>, pub Opcode, pub Vec<Token>); pub struct Node(pub Option<Symbol>, pub Opcode, pub Vec<Token>);
impl fmt::Display for Node { impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let symbol = match &self.0 { let symbol = match &self.0 {
Some(symbol) => format!("{}", symbol), Some(symbol) => format!("{symbol}"),
None => "".to_string(), None => "".to_string(),
}; };
@@ -26,15 +25,65 @@ impl fmt::Display for Symbol {
impl fmt::Display for Module { impl fmt::Display for Module {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Module::Unresolved(name) => write!(f, "{}", name), Module::Unresolved(name) => write!(f, "{name}"),
Module::Resolved(name) => write!(f, "{}", name), Module::Resolved(name) => write!(f, "{name}"),
} }
} }
} }
impl fmt::Display for Opcode { impl fmt::Display for Opcode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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"),
}
} }
} }
+6 -2
View File
@@ -1,6 +1,4 @@
use core::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use common::prelude::{Instruction, Register}; use common::prelude::{Instruction, Register};
@@ -33,6 +31,12 @@ impl Program {
} }
} }
impl Default for Program {
fn default() -> Self {
Self::new()
}
}
impl Parser { impl Parser {
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> { pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
let mut self_ = Parser { let mut self_ = Parser {
Submodule dsa_editor deleted from 1566784e20
+3985
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -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"
+253
View File
@@ -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)))
}
+296
View File
@@ -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
}
}
+29
View File
@@ -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",
]),
}
}
}
+204
View File
@@ -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(),
}
}
}
+147
View File
@@ -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,
}
}
}
+22
View File
@@ -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
View File
@@ -10,9 +10,14 @@ path = "src/lib.rs"
[dependencies] [dependencies]
common = { path = "../common" } common = { path = "../common" }
assembler = { path = "../assembler" } 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" eframe = "0.31.1"
egui = "0.31.1" egui = "0.31.1"
rfd = "0.15.3" rfd = "0.15.3"
dirs = "6.0.0" 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"]
+2
View File
@@ -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.
+2
View File
@@ -0,0 +1,2 @@
#[cfg(feature = "discord-rpc")]
pub mod rpc;
+56
View File
@@ -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)
}
+3
View File
@@ -1,2 +1,5 @@
#[cfg(feature = "config")]
pub mod config;
pub mod misc;
pub mod system; pub mod system;
pub mod ui; pub mod ui;
+1 -1
View File
@@ -78,7 +78,7 @@ impl MemoryUnit for MainStore {
bytes[1] = block.data[(offset + 1) as usize]; bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize]; bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) 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> { fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
+60 -32
View File
@@ -8,7 +8,9 @@ use crate::emulator::system::{
model::{IODevice, RegFile}, model::{IODevice, RegFile},
}; };
use common::instructions::{Instruction, Interrupt, Register, errors::InstructionDecodeError}; use common::instructions::{
Instruction, Interrupt, Register, errors::InstructionDecodeError,
};
pub struct Processor { pub struct Processor {
pub memory: Box<dyn MemoryUnit>, pub memory: Box<dyn MemoryUnit>,
@@ -168,71 +170,90 @@ impl Executable for Instruction {
*cpu.reg(a.dr) = cpu.get(a.sr1); *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) => { Self::MovSigned(a) => {
*cpu.reg(a.dr) = sign_extend(cpu.get(a.sr1)); *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) => { Self::LoadByte(a) => {
*cpu.reg(a.r2) = *cpu.reg(a.r2) = u32::from(
u32::from(cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate))); 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) => { Self::LoadByteSigned(a) => {
*cpu.reg(a.r2) = sign_extend(u32::from( *cpu.reg(a.r2) = sign_extend(u32::from(
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)), 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) => { Self::LoadHalfword(a) => {
// we read an entire word, then right shift so we only get the first half of the word // we read an entire word, then right shift so we only get the first half
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16; // of the word
}
// 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) = *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) => { 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) => { Self::StoreByte(a) => {
cpu.memory cpu.memory.write_byte(
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2) as u8); 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) => { Self::StoreHalfword(a) => {
// split the value into bytes and then write two bytes // 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 cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]); .write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]);
cpu.memory cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate) + 1, bytes[1]); .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) => { Self::StoreWord(a) => {
cpu.memory cpu.memory
.write_word(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2)); .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) => { Self::LoadLowerImmediate(a) => {
*cpu.reg(a.r1) = u32::from(a.immediate); *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) => { 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 // 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) => { Self::JumpGe(a) => {
if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) { if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate); 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) => { Self::JumpLe(a) => {
if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) { if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate); cpu.jump(a.r1, a.immediate);
@@ -286,20 +309,24 @@ impl Executable for Instruction {
// Decrements the value in the given register // Decrements the value in the given register
Self::Decrement(a) => *cpu.reg(a.sr1) = dec(cpu.get(a.sr1)), 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) => { Self::ShiftLeft(a) => {
let regval = cpu.get(a.sr2); let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1); 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) => { Self::ShiftRight(a) => {
let regval = cpu.get(a.sr2); let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1); 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 // 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 // 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)), 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) => { Self::Compare(a) => {
cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2)); cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2));
} }
+28 -16
View File
@@ -43,7 +43,7 @@ impl Component for Editor {
if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) { if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) {
self.save(); self.save();
}; }
self.render_toolbar(state, ui, ctx); self.render_toolbar(state, ui, ctx);
@@ -87,17 +87,23 @@ impl Editor {
fn filename(&self) -> &str { fn filename(&self) -> &str {
self.path self.path
.file_name() .file_name()
.unwrap_or(OsStr::new("Unnamed!")) .unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str() .to_str()
.unwrap() .map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
)
} }
fn extension(&self) -> &str { fn extension(&self) -> &str {
self.path self.path
.extension() .extension()
.unwrap_or(OsStr::new("Unknown!")) .unwrap_or_else(|| OsStr::new("Unknown!"))
.to_str() .to_str()
.unwrap() .map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
)
} }
fn save(&mut self) { fn save(&mut self) {
@@ -172,7 +178,7 @@ impl Editor {
bytes[i] = byte; bytes[i] = byte;
} }
} }
let value = u32::from_be_bytes(bytes); let value = u32::from_le_bytes(bytes);
// Address column // Address column
ui.with_layout( ui.with_layout(
@@ -210,10 +216,10 @@ impl Editor {
); );
// Instruction column // Instruction column
let instruction = match Instruction::decode(value) { let instruction = Instruction::decode(value).map_or_else(
Ok(instruction) => instruction.to_string(), |_| format!("{value:10}"),
Err(_) => format!("{value:10}"), |instruction| instruction.to_string(),
}; );
ui.label( ui.label(
egui::RichText::new(instruction) egui::RichText::new(instruction)
@@ -230,19 +236,25 @@ impl Editor {
fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
let available_width = ui.available_width(); let available_width = ui.available_width();
let syntax = match self.extension() { let syntax = match self.extension() {
"dsa" => Syntax::dsa(), "dsa" => Some(Syntax::new("dsa")),
_ => Syntax::dsa(), _ => None,
}; };
CodeEditor::default() let ed = CodeEditor::default()
.id_source("editor") .id_source("editor")
.with_fontsize(12.0) .with_fontsize(12.0)
.with_rows(0) .with_rows(0)
.with_theme(ColorTheme::default()) .with_theme(ColorTheme::default())
.with_syntax(syntax)
.with_numlines(true) .with_numlines(true)
.desired_width(available_width - 450.0) .desired_width(available_width - 450.0);
.show(ui, &mut self.text);
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) { fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
+5 -5
View File
@@ -1,13 +1,13 @@
include print "../resources/dsa/print.dsa" // include print "../resources/dsa/print.dsa"
// Fibonacci sequence calculator in DSA assembly // Fibonacci sequence calculator in DSA assembly
// Calculates the first 8 Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13 // 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) dw fib_count: 6 // How many more numbers to calculate after F(0) and F(1)
init: init:
// Initialize the first two Fibonacci numbers // Initialize the first two Fibonacci numbers
lli 0, rg0 // F(0) = 0 lli rg0, 0 // F(0) = 0
lli 1, rg1 // F(1) = 1 lli rg1, 1 // F(1) = 1
push rg0
// Load loop counter // Load loop counter
ldw fib_count, zero, rg2 // Load number of iterations remaining ldw fib_count, zero, rg2 // Load number of iterations remaining
@@ -21,7 +21,7 @@ fibonacci_loop:
// Decrement loop counter // Decrement loop counter
dec rg2 // rg2 = rg2 - 1 dec rg2 // rg2 = rg2 - 1
// Check if we should continue looping // Check if we should continue looping
cmp rg2, zero // Compare counter with 0 cmp rg2, zero // Compare counter with 0
jgt fibonacci_loop // Jump back if counter > 0 jgt fibonacci_loop // Jump back if counter > 0
+2 -2
View File
@@ -10,8 +10,8 @@ init:
start: start:
ldb length, rg0 ldb length, rg0
lwi string, rg1 lwi rg1, string
lwi display, rg2 lwi rg2, display
loop: loop:
// read from string and write to display // read from string and write to display