@@ -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 |