| @@ -81,6 +81,7 @@ name = "ceres-asm" | |||
| version = "0.1.0" | |||
| dependencies = [ | |||
| "logos 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| ] | |||
| [[package]] | |||
| @@ -428,7 +429,7 @@ version = "0.1.0" | |||
| dependencies = [ | |||
| "image 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| "minifb 0.16.0 (git+https://github.com/emoon/rust_minifb)", | |||
| "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| ] | |||
| [[package]] | |||
| @@ -829,15 +830,15 @@ dependencies = [ | |||
| [[package]] | |||
| name = "thiserror" | |||
| version = "1.0.19" | |||
| version = "1.0.20" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| dependencies = [ | |||
| "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| "thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| ] | |||
| [[package]] | |||
| name = "thiserror-impl" | |||
| version = "1.0.19" | |||
| version = "1.0.20" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| dependencies = [ | |||
| "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", | |||
| @@ -1109,8 +1110,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" | |||
| "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" | |||
| "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" | |||
| "checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" | |||
| "checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" | |||
| "checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" | |||
| "checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" | |||
| "checksum tiff 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" | |||
| "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" | |||
| "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" | |||
| @@ -2,6 +2,7 @@ | |||
| a shitty fantasy console written in rust using a proprietary MIPS based asm instruction set. a lot of inspiration from PICO-8. ceres is structured of these crates: | |||
| - ceres-sys: the core system structure of ceres-16 | |||
| - ceres-asm: the assembler for ceres-16 | |||
| ### Graphics | |||
| @@ -24,4 +25,18 @@ all registers are unsigned 16 bit | |||
| ### Memory map and info | |||
| god oh fuck what am i even doing | |||
| god oh fuck what am i even doing | |||
| ### instructions | |||
| ##### load - ld:signifier | |||
| can be vram/cram/imed | |||
| - immediate `ld:immd $dest immediate` | |||
| | opcode | signifier | destination | padding | immediate | | |||
| | ------- | --------- | ----------- | ------- | ------------------ | | |||
| | `00001` | `100` | `0000` | `0000` | `0000000000000000` | | |||
| @@ -8,3 +8,4 @@ edition = "2018" | |||
| [dependencies] | |||
| logos = "0.11.4" | |||
| thiserror = "1.0.20" | |||
| @@ -1,14 +1,107 @@ | |||
| use logos::Logos; | |||
| // izzy do not forget to remove these later dummy | |||
| #![allow(dead_code)] | |||
| #![allow(unused_imports)] | |||
| #![allow(unused_variables)] | |||
| // namespacing | |||
| use logos::{Lexer, Logos}; | |||
| /// ceres-asm result type | |||
| pub type Result<T> = std::result::Result<T, CeresAsmError>; | |||
| /// ceres-asm error type | |||
| #[derive(Debug, thiserror::Error)] | |||
| pub enum CeresAsmError { | |||
| #[error("wrong token found:\nexpected: {expected:?}\n found: {found:?}")] | |||
| BadToken { expected: Token, found: Token }, | |||
| #[error("bad token: {token:?}")] | |||
| LazyBadToken { token: Token }, | |||
| #[error("was looking for token, found nothing ???")] | |||
| NoToken, | |||
| } | |||
| /// assembler struct | |||
| pub struct Assembler<'a> { | |||
| lexer: Lexer<'a, Token>, | |||
| } | |||
| impl<'a> Assembler<'a> { | |||
| /// create a new assembler | |||
| pub fn new(data: &'a str) -> Assembler<'a> { | |||
| let lexer = Token::lexer(data); | |||
| Assembler { lexer } | |||
| } | |||
| /// assemble | |||
| pub fn assemble(&mut self) -> Result<Vec<u32>> { | |||
| let mut machine_code: Vec<u32> = Vec::new(); | |||
| while let Some(token) = self.lexer.next() { | |||
| match token { | |||
| // cases that should continue | |||
| Token::Comment => continue, | |||
| Token::Newline => continue, | |||
| // cases that like, actually make sense to process | |||
| Token::Load => { | |||
| if let Some(new_token) = self.lexer.next() { | |||
| let signifier = Signifier::from_token(new_token)?; | |||
| machine_code.push(self.load(signifier)?); | |||
| } else { | |||
| return Err(CeresAsmError::NoToken); | |||
| } | |||
| } | |||
| // cases that should straight up not happen | |||
| _ => return Err(CeresAsmError::LazyBadToken { token }), | |||
| } | |||
| } | |||
| Ok(machine_code) | |||
| } | |||
| // load instruction assembly | |||
| fn load(&mut self, signifier: Signifier) -> Result<u32> { | |||
| let opcode: u32 = 0b00001; | |||
| let signifier: u32 = signifier.as_bits() as u32; | |||
| let instruction: u32 = (opcode << 27) | (signifier << 24); | |||
| Ok(instruction) | |||
| } | |||
| } | |||
| enum Signifier { | |||
| VideoMemory, | |||
| CartMemory, | |||
| Immediate, | |||
| } | |||
| impl Signifier { | |||
| fn from_token(token: Token) -> Result<Self> { | |||
| match token { | |||
| Token::VideoMemory => Ok(Self::VideoMemory), | |||
| Token::CartMemory => Ok(Self::CartMemory), | |||
| Token::Immediate => Ok(Self::Immediate), | |||
| _ => Err(CeresAsmError::LazyBadToken { token }), | |||
| } | |||
| } | |||
| // returns the signifier as bits stored in a u8 | |||
| fn as_bits(self) -> u8 { | |||
| match self { | |||
| Self::VideoMemory => 0b010, | |||
| Self::CartMemory => 0b001, | |||
| Self::Immediate => 0b100, | |||
| } | |||
| } | |||
| } | |||
| /// token | |||
| #[derive(Logos, Debug, PartialEq)] | |||
| pub enum Token { | |||
| // general stuff | |||
| #[regex(";.+")] | |||
| Comment, | |||
| #[token("@")] | |||
| At, | |||
| #[regex("/[a-z-_]+:/g")] | |||
| Label, | |||
| #[token("\n")] | |||
| Newline, | |||
| // registers | |||
| #[token("$t0")] | |||
| ZeroRegister, | |||
| @@ -25,13 +118,21 @@ pub enum Token { | |||
| #[token("$a2")] | |||
| ArgumentTwo, | |||
| #[token("$v0")] | |||
| ReturnOne, | |||
| ReturnZero, | |||
| #[token("$v1")] | |||
| ReturnTwo, | |||
| #[regex("\\$t[0-6]")] | |||
| TemoraryRegister, | |||
| #[regex("\\$[0-9]+")] | |||
| RegisterIndex, | |||
| ReturnOne, | |||
| #[regex("\\$t[0-6]", |lex| lex.slice()[2..].parse::<u16>())] | |||
| TemporaryRegister(u16), | |||
| #[regex("\\$[0-9]+", |lex| lex.slice()[1..].parse::<u16>())] | |||
| RegisterIndex(u16), | |||
| // types | |||
| #[token("immd")] | |||
| Immediate, | |||
| #[token("vram")] | |||
| VideoMemory, | |||
| #[token("cram")] | |||
| CartMemory, | |||
| // literals | |||
| #[regex("0x[a-fA-F0-9]+", |lex| u16::from_str_radix(&lex.slice()[2..], 16).unwrap() )] | |||
| @@ -42,18 +143,80 @@ pub enum Token { | |||
| DecimalLiteral(u16), | |||
| // instructions | |||
| #[token("add")] | |||
| Add, | |||
| #[token("sub")] | |||
| Sub, | |||
| #[token("mul")] | |||
| Mul, | |||
| #[token("div")] | |||
| Div, | |||
| #[token("jmp")] | |||
| Jmp, | |||
| #[token("ld:")] | |||
| Load, | |||
| // logos error | |||
| #[error] | |||
| #[regex(" +", logos::skip)] | |||
| Error, | |||
| } | |||
| impl Token { | |||
| // returns the token as a str | |||
| fn as_str(&self) -> String { | |||
| match self { | |||
| // general stuff | |||
| Self::Comment => "a comment".to_owned(), | |||
| Self::Newline => "\\n".to_owned(), | |||
| // registers | |||
| Self::ZeroRegister => "$z0".to_owned(), | |||
| Self::ProgramCounter => "$pc".to_owned(), | |||
| Self::StackPointer => "$sp".to_owned(), | |||
| Self::ReturnAddress => "$ra".to_owned(), | |||
| Self::ArgumentZero => "$a0".to_owned(), | |||
| Self::ArgumentOne => "$z1".to_owned(), | |||
| Self::ArgumentTwo => "$a2".to_owned(), | |||
| Self::ReturnZero => "$v0".to_owned(), | |||
| Self::ReturnOne => "$v1".to_owned(), | |||
| Self::TemporaryRegister(idx) => format!("$t{}", idx), | |||
| Self::RegisterIndex(idx) => format!("${}", idx), | |||
| // types | |||
| Self::Immediate => "immd".to_owned(), | |||
| Self::VideoMemory => "vram".to_owned(), | |||
| Self::CartMemory => "cram".to_owned(), | |||
| // literals | |||
| Self::HexLiteral(_) => "hex lit".to_owned(), | |||
| Self::BinaryLiteral(_) => "bin lit".to_owned(), | |||
| Self::DecimalLiteral(_) => "dec lit".to_owned(), | |||
| // instructions | |||
| Self::Load => "ld:".to_owned(), | |||
| // errors | |||
| Self::Error => "ERR".to_owned(), | |||
| } | |||
| } | |||
| // returns true if given token is a register | |||
| fn is_register(&self) -> bool { | |||
| match self { | |||
| Self::ZeroRegister | |||
| | Self::ProgramCounter | |||
| | Self::StackPointer | |||
| | Self::ReturnAddress | |||
| | Self::ArgumentZero | |||
| | Self::ArgumentOne | |||
| | Self::ArgumentTwo | |||
| | Self::ReturnZero | |||
| | Self::ReturnOne | |||
| | Self::TemporaryRegister(_) | |||
| | Self::RegisterIndex(_) => true, | |||
| _ => false, | |||
| } | |||
| } | |||
| // returns true if given token is a literal | |||
| fn is_literal(&self) -> bool { | |||
| match self { | |||
| Self::HexLiteral(_) | Self::BinaryLiteral(_) | Self::DecimalLiteral(_) => true, | |||
| _ => false, | |||
| } | |||
| } | |||
| // returns true if it's a valid memory location or immediate | |||
| fn is_signifier(&self) -> bool { | |||
| match self { | |||
| Self::VideoMemory | Self::CartMemory | Self::Immediate => true, | |||
| _ => false, | |||
| } | |||
| } | |||
| } | |||
| @@ -8,6 +8,16 @@ impl VideoMemory { | |||
| pub fn init() -> VideoMemory { | |||
| VideoMemory { data: [0x0000; crate::VIDEO_MEMORY_LEN] } | |||
| } | |||
| /// an iterator | |||
| pub fn iter(&self) -> impl Iterator<Item = &u16> { | |||
| self.data.iter() | |||
| } | |||
| /// mutable iterator | |||
| pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut u16> { | |||
| self.data.iter_mut() | |||
| } | |||
| } | |||
| impl std::ops::Index<u16> for VideoMemory { | |||
| @@ -0,0 +1,10 @@ | |||
| alias r := run | |||
| alias b := build | |||
| build: | |||
| clear | |||
| cargo build | |||
| run: | |||
| clear | |||
| cargo run | |||
| @@ -6,12 +6,18 @@ use structopt::StructOpt; | |||
| struct Opt { | |||
| /// times the screen should be scaled by | |||
| #[structopt(short = "s", long = "scale", default_value = "4")] | |||
| scale_factor: usize, | |||
| _scale_factor: usize, | |||
| } | |||
| fn wrapper() -> Result<()> { | |||
| let opt = Opt::from_args(); | |||
| // load options | |||
| let _opt = Opt::from_args(); | |||
| // initialize the system | |||
| let _sys = ceres_sys::System::init(); | |||
| /* | |||
| // create a new graphics context | |||
| let mut ctx = offbrand::Context::new( | |||
| ceres_sys::SCREEN_WIDTH, | |||
| ceres_sys::SCREEN_HEIGHT, | |||
| @@ -19,25 +25,12 @@ fn wrapper() -> Result<()> { | |||
| Some(opt.scale_factor), | |||
| )?; | |||
| let mut sys = ceres_sys::System::init(); | |||
| let mut colors: Vec<u16> = Vec::new(); | |||
| for r in 0x0..0xf { | |||
| for g in 0x0..0xf { | |||
| for b in 0x0..0xf { | |||
| colors.push((r << 8) | (g << 4) | b); | |||
| } | |||
| } | |||
| } | |||
| colors.iter().enumerate().for_each(|(addr, color)| { | |||
| sys.video_memory[addr as u16] = *color; | |||
| }); | |||
| // loop while the context window is open | |||
| while ctx.is_open() { | |||
| // clear the context | |||
| ctx.clear(None); | |||
| // print a pixel for ever word in the video memory buffer | |||
| for x in 0..ceres_sys::SCREEN_WIDTH { | |||
| for y in 0..ceres_sys::SCREEN_HEIGHT { | |||
| let color = | |||
| @@ -46,19 +39,14 @@ fn wrapper() -> Result<()> { | |||
| } | |||
| } | |||
| // render the context | |||
| ctx.present()?; | |||
| } | |||
| */ | |||
| Ok(()) | |||
| } | |||
| fn color_from_u16(color: u16) -> Color { | |||
| let r = (((color >> 8) & 0x000f) as u8) * 17; | |||
| let g = (((color >> 4) & 0x000f) as u8) * 17; | |||
| let b = ((color & 0x000f) as u8) * 17; | |||
| Color { r, g, b } | |||
| } | |||
| fn main() { | |||
| if let Err(err) = wrapper() { | |||
| eprintln!("error: {:?}", err); | |||
| @@ -1,5 +1,2 @@ | |||
| ; a simple test application | |||
| main: | |||
| load $t0 0xcccc | |||
| load $t1 0xaaaa | |||
| sub $t0 $t1 $t3 | |||
| ; a simple test | |||
| ld:immd $t0 0x5a5a | |||