Compare commits
10 Commits
03668a71e8
...
61a3a305b6
Author | SHA1 | Date | |
---|---|---|---|
61a3a305b6 | |||
579b5ed75b | |||
b95c01116a | |||
522043e376 | |||
8f894174d9 | |||
9358bfa77f | |||
724cc66faa | |||
f64747dc93 | |||
40daf685ff | |||
e2b7ec3e43 |
@ -6,6 +6,9 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.2.5", features = ["derive"] }
|
||||
log = "0.4.17"
|
||||
simple_logger = "4.1.0"
|
||||
clap = { version = "4.4.8", features = ["derive"] }
|
||||
log = "0.4"
|
||||
simple_logger = "4.2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "dekejit"
|
||||
|
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# DEKEJIT
|
||||
|
||||
## EXPERIMENTAL!! NOTHING WORKS AND MAKES SENSE!!!
|
||||
|
||||
This is an experimental 16-bit virtual machine.
|
||||
|
||||
A lenghty description can be found [here.](spec.md)
|
||||
|
||||
If you want to have a great time, you can try experimenting with it:
|
||||
|
||||
### Installation
|
||||
|
||||
To install the program, run `cargo install --path .` .
|
||||
|
||||
This currently builds the virtual machine, which can execute binary files, and an assembler which can turn text files into binary files.
|
||||
|
||||
### Usage
|
||||
|
||||
`dekejit build <assemby_file> <output_file>` will read the provided `<assembly_file>` and write a binary file `<output_file>` which can be executed using `dekejit run <binary_file>`
|
||||
|
||||
A few example files can be found in `tests/assembly/`.
|
||||
|
||||
|
||||
### Future
|
||||
|
||||
Right now the only possible way to use the virtual machine is to manually write the assembly.
|
||||
In the future, small compilers for toy languages will be built that target my toy assembly.
|
||||
|
||||
|
47
spec.md
47
spec.md
@ -10,8 +10,8 @@ Since I'm studying riscV, this will be a lot riscv inspired.
|
||||
|
||||
The gravejit virtual machine sports 16 16-bit registers (plus the program counter!) and 16 operations.
|
||||
|
||||
Here is the list of registers togheter with memonics.
|
||||
|
||||
Here is the list of registers together with memonics.
|
||||
```
|
||||
0 : zero // register 0 is always 0.
|
||||
1 : ra // return address
|
||||
2 : sp // stack pointer
|
||||
@ -30,32 +30,33 @@ Here is the list of registers togheter with memonics.
|
||||
15: t4 // don't know what to do with this
|
||||
|
||||
pc: program counter.
|
||||
```
|
||||
|
||||
## ISA
|
||||
|
||||
opcode | memonic | format | description
|
||||
|
||||
0000 | NOP | just 0s'| Does nothing.
|
||||
0001 | ADD s0 s1 s2 | R | s0 = s1 + s2
|
||||
0010 | SUB s0 s1 s2 | R | s0 = s1 - s2
|
||||
0011 | AND s0 s1 s2 | R | s0 = s1 && s2
|
||||
0100 | XOR s0 s1 s2 | R | s0 = s1 xor s2
|
||||
0101 | SLL s0 s1 s2 | R | s0 = s1 << s2
|
||||
0110 | SLI s0 c | I | s0 = s0 << c
|
||||
0111 | ADDI s0 c | I | s0 = s0 + c
|
||||
1000 | BEQ s0 s1 s2 | R | if (s1 == s2) -> pc = s0
|
||||
1001 | BGT s0 s1 s2 | R | if (s1 > s2) -> pc = s0
|
||||
1010 | JAL s0 s1 c | J | s0 = pc+1; pc += s1 + c;
|
||||
1011 |
|
||||
1100 | LOAD s0 s1 s2 | R | loads s1 + shift by s2 in s0
|
||||
1101 | STORE s0 s1 s2| R | stores s0 in address s1 + shift by s2
|
||||
1110 | CALL s0 c | I | performs system call
|
||||
1111 | HALT | just 1s'| halt, and possibly catch fire.
|
||||
| opcode | memonic | format | description |
|
||||
| ------ | -------------- | ------- | --------------------------------------- |
|
||||
| 0000 | NOP | just 0s'| Does nothing. |
|
||||
| 0001 | ADD s0 s1 s2 | R | s0 = s1 + s2 |
|
||||
| 0010 | SUB s0 s1 s2 | R | s0 = s1 - s2 |
|
||||
| 0011 | AND s0 s1 s2 | R | s0 = s1 && s2 |
|
||||
| 0100 | XOR s0 s1 s2 | R | s0 = s1 xor s2 |
|
||||
| 0101 | SLL s0 s1 s2 | R | s0 = s1 << s2 |
|
||||
| 0110 | SLI s0 c | I | s0 = s0 << c |
|
||||
| 0111 | ADDI s0 c | I | s0 = s0 + c |
|
||||
| 1000 | BEQ s0 s1 s2 | R | if (s1 == s2) -> pc = s0 |
|
||||
| 1001 | BGT s0 s1 s2 | R | if (s1 > s2) -> pc = s0 |
|
||||
| 1010 | JAL s0 s1 c | J | s0 = pc+1; pc += s1 + c; |
|
||||
| 1011 | | | #TODO? |
|
||||
| 1100 | LOAD s0 s1 s2 | R | loads s1 + shift by s2 in s0 |
|
||||
| 1101 | STORE s0 s1 s2 | R | stores s0 in address s1 + shift by s2 |
|
||||
| 1110 | CALL s0 c | I | performs system call |
|
||||
| 1111 | HALT | just 1s'| halt, and possibly catch fire. |
|
||||
|
||||
|
||||
### Operation formats:
|
||||
|
||||
Each istruction is 16 bits long.
|
||||
Each instruction is 16 bits long.
|
||||
The first 4 most-significant bits are the opcode.
|
||||
Constants (c in the above table) are always considered signed, and written in
|
||||
two's compliment. Sign extension also takes place whenever needed.
|
||||
@ -90,9 +91,7 @@ The constant is added to the value of the second register argument.
|
||||
|
||||
### JIT's system calls:
|
||||
|
||||
the `CALL` instruction is a bit of a hack because I want to load more functionality into the thing.
|
||||
The JIT can decide what to do with the register s0 and the number c.
|
||||
It should be possible to open files, write files, read stdin, write to stdout, etc...
|
||||
What the `CALL` instruction does is up to implementations. The JIT can decide what to do with the register s0 and the number c. It could provide mechanisms to perform I/O on a true filesystem, on an emulated filesystem, or it could do something else entirely, i.e, something web related.
|
||||
|
||||
#### io\_vec: first systemcall environment
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
/// Type alias to represent a register.
|
||||
type RegisterMem = String;
|
||||
|
||||
/// Type alias to represent a label used in place of a const.
|
||||
pub type ConstId = String;
|
||||
|
||||
/// A const can either be a number or a label of some section.
|
||||
#[derive(Debug)]
|
||||
pub enum Const {
|
||||
CS(ConstId),
|
||||
C(u8),
|
||||
}
|
||||
|
||||
/// Operations as they are parsed, before translating them to binary.
|
||||
/// This type is used internally by the parser and
|
||||
/// differs from cpu::Operation.
|
||||
#[derive(Debug)]
|
||||
pub enum Operation {
|
||||
/// No type.
|
||||
NOP,
|
||||
HALT,
|
||||
/// R type
|
||||
ADD(RegisterMem, RegisterMem, RegisterMem),
|
||||
SUB(RegisterMem, RegisterMem, RegisterMem),
|
||||
AND(RegisterMem, RegisterMem, RegisterMem),
|
||||
XOR(RegisterMem, RegisterMem, RegisterMem),
|
||||
SLL(RegisterMem, RegisterMem, RegisterMem),
|
||||
BEQ(RegisterMem, RegisterMem, RegisterMem),
|
||||
BGT(RegisterMem, RegisterMem, RegisterMem),
|
||||
LOAD(RegisterMem, RegisterMem, RegisterMem),
|
||||
STORE(RegisterMem, RegisterMem, RegisterMem),
|
||||
|
||||
/// I Type
|
||||
SLI(RegisterMem, Const),
|
||||
ADDI(RegisterMem, Const),
|
||||
CALL(RegisterMem, Const),
|
||||
|
||||
/// J Type
|
||||
JAL(RegisterMem, RegisterMem, Const),
|
||||
}
|
72
src/assembler/ast.rs
Normal file
72
src/assembler/ast.rs
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
/// Type alias to represent a register.
|
||||
type RegisterMem = String;
|
||||
|
||||
/// Type alias to represent a label used in place of a const.
|
||||
pub type ConstId = String;
|
||||
|
||||
/// A const can either be a number or a label of some section.
|
||||
#[derive(Debug)]
|
||||
pub enum Const {
|
||||
CS(ConstId),
|
||||
C(u8),
|
||||
}
|
||||
|
||||
/// Operations as they are parsed, before translating them to binary.
|
||||
/// This type is used internally by the parser and
|
||||
/// differs from cpu::Operation.
|
||||
#[derive(Debug)]
|
||||
pub enum Operation {
|
||||
/// No type.
|
||||
NOP,
|
||||
HALT,
|
||||
/// R type
|
||||
ADD(RegisterMem, RegisterMem, RegisterMem),
|
||||
SUB(RegisterMem, RegisterMem, RegisterMem),
|
||||
AND(RegisterMem, RegisterMem, RegisterMem),
|
||||
XOR(RegisterMem, RegisterMem, RegisterMem),
|
||||
SLL(RegisterMem, RegisterMem, RegisterMem),
|
||||
BEQ(RegisterMem, RegisterMem, RegisterMem),
|
||||
BGT(RegisterMem, RegisterMem, RegisterMem),
|
||||
LOAD(RegisterMem, RegisterMem, RegisterMem),
|
||||
STORE(RegisterMem, RegisterMem, RegisterMem),
|
||||
|
||||
/// I Type
|
||||
SLI(RegisterMem, Const),
|
||||
ADDI(RegisterMem, Const),
|
||||
CALL(RegisterMem, Const),
|
||||
|
||||
/// J Type
|
||||
JAL(RegisterMem, RegisterMem, Const),
|
||||
}
|
||||
|
||||
|
||||
pub fn print_op(op: Operation) -> String {
|
||||
|
||||
let print_const = |x : Const| {
|
||||
match x {
|
||||
Const::CS(i) => i,
|
||||
Const::C(n) => format!("{}", n),
|
||||
}
|
||||
};
|
||||
|
||||
match op {
|
||||
Operation::NOP => String::from("nop"),
|
||||
Operation::HALT => String::from("halt"),
|
||||
Operation::ADD(a, b, c) => format!("add {} {} {}", a, b, c),
|
||||
Operation::SUB(a, b, c) => format!("sub {} {} {}", a, b, c),
|
||||
Operation::AND(a, b, c) => format!("and {} {} {}", a, b, c),
|
||||
Operation::XOR(a, b, c) => format!("xor {} {} {}", a, b, c),
|
||||
Operation::SLL(a, b, c) => format!("sll {} {} {}", a, b, c),
|
||||
Operation::BEQ(a, b, c) => format!("beq {} {} {}", a, b, c),
|
||||
Operation::BGT(a, b, c) => format!("bgq {} {} {}", a, b, c),
|
||||
Operation::LOAD(a, b, c) => format!("load {} {} {}", a, b, c),
|
||||
Operation::STORE(a, b, c) => format!("store {} {} {}", a, b, c),
|
||||
Operation::SLI(a, c) => format!("sli {} {}", a, print_const(c)),
|
||||
Operation::ADDI(a, c) => format!("addi {} {}", a, print_const(c)),
|
||||
Operation::CALL(a, c) => format!("call {} {}", a, print_const(c)),
|
||||
Operation::JAL(a, b, c) => format!("jal {} {} {}", a, b, print_const(c)),
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use super::AST::*;
|
||||
use super::ast::*;
|
||||
use crate::cpu::get_num;
|
||||
|
||||
use log::{trace, warn};
|
||||
|
||||
|
||||
/// Trait to represent a format we can translate our assembly to.
|
||||
pub trait CodeFormat {
|
||||
fn encode_op(op: &Operation, sy: &SymbolTable, current_pc: u16) -> Option<Self>
|
||||
@ -36,7 +35,7 @@ impl SymbolTable {
|
||||
// query, sy
|
||||
// );
|
||||
warn!("Symbol {} not found in symbol table.", query);
|
||||
return None
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
mod AST;
|
||||
mod ast;
|
||||
pub mod encoder;
|
||||
pub mod parser;
|
||||
mod tests;
|
||||
|
||||
use encoder::CodeFormat;
|
||||
use encoder::SymbolTable;
|
||||
|
||||
use log::{trace, debug};
|
||||
pub use ast::print_op;
|
||||
|
||||
use log::{debug, trace};
|
||||
|
||||
use parser::Section;
|
||||
|
||||
@ -24,7 +25,7 @@ impl Section {
|
||||
// UTF-8 strings are collections of 8-bit chunks, but they're
|
||||
// packed into words of 16 bits + 16 bit NULL.
|
||||
//
|
||||
// If there's an uneven number of bytes in a string,
|
||||
// If there's an uneven number of bytes in a string,
|
||||
// we add a 8 bit empty padding and then the NULL byte.
|
||||
SectionContent::CString(s) => {
|
||||
let c = s.len();
|
||||
@ -39,7 +40,7 @@ impl Section {
|
||||
}
|
||||
|
||||
/// Converts this section to binary. Needs symbol table to
|
||||
/// resolve labels, and to quickly get the address of
|
||||
/// resolve labels, and to quickly get the address of
|
||||
/// this own section.
|
||||
fn to_binary(&self, sy: &SymbolTable) -> Option<crate::loader::Section> {
|
||||
let own_address = sy.lookup(&self.name)?;
|
||||
@ -61,17 +62,19 @@ impl Section {
|
||||
return Some(crate::loader::Section::new(self.name.clone(), &res));
|
||||
}
|
||||
SectionContent::CString(s) => {
|
||||
return Some(crate::loader::Section::new(self.name.clone(), &make_string(s)));
|
||||
return Some(crate::loader::Section::new(
|
||||
self.name.clone(),
|
||||
&make_string(s),
|
||||
));
|
||||
}
|
||||
SectionContent::CVec() => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Sorts a list of sections.
|
||||
/// All .text sections containing code are
|
||||
/// put at the beginning of the binary file, in the order they
|
||||
/// put at the beginning of the binary file, in the order they
|
||||
/// appear in the assembly file, except the "main" section,
|
||||
/// which is the entrypoint of our program and must be put
|
||||
/// at the very beginning.
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::AST::{Const, Operation};
|
||||
use super::ast::{Const, Operation};
|
||||
use Operation::*;
|
||||
|
||||
use log::*;
|
||||
@ -16,14 +16,12 @@ pub enum ParseError {
|
||||
/// represents the state of our parser.
|
||||
/// Sadly parsing is stateless,
|
||||
pub struct Parser {
|
||||
input: Vec<String>, // input file
|
||||
input: Vec<String>, // input file
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(i: String) -> Self {
|
||||
Parser {
|
||||
input: sanitize(i),
|
||||
}
|
||||
Parser { input: sanitize(i) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,19 +87,20 @@ fn take_between(i: &str, start: &str, stop: &str) -> Option<(String, String)> {
|
||||
return take_alpha_till(&s1, stop);
|
||||
}
|
||||
|
||||
/// finds special escaped characters in a string
|
||||
/// (such as \n) and replaces them with the actual special
|
||||
/// character
|
||||
fn escaped_codes() {}
|
||||
|
||||
#[test]
|
||||
fn take_between_test() {
|
||||
fn take_between_test1() {
|
||||
assert_eq!(
|
||||
take_between("\"wow\" etc", "\"", "\""),
|
||||
Some(("wow".to_string(), " etc".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
/// finds special escaped characters in a string
|
||||
/// (such as \n) and replaces them with the actual special
|
||||
/// character
|
||||
/// #TODO: do we need this? I forgot.
|
||||
fn _escaped_codes() {}
|
||||
|
||||
//// SECTION PARSING
|
||||
|
||||
/// Enum to represent possible section content.
|
||||
@ -121,7 +120,6 @@ pub struct Section {
|
||||
pub content: SectionContent,
|
||||
}
|
||||
|
||||
|
||||
impl Parser {
|
||||
pub fn parse_sections(&self) -> Result<Vec<Section>, ParseError> {
|
||||
let mut res = vec![];
|
||||
@ -151,8 +149,12 @@ impl Parser {
|
||||
})
|
||||
}
|
||||
"asciiz" => {
|
||||
let Some(s) = lines.next() else {return Err(ParseError::UnexpectedEOF)};
|
||||
let Some((s, _)) = take_between(s.trim(), "\"", "\"") else {return Err(ParseError::BadSectionContent)};
|
||||
let Some(s) = lines.next() else {
|
||||
return Err(ParseError::UnexpectedEOF);
|
||||
};
|
||||
let Some((s, _)) = take_between(s.trim(), "\"", "\"") else {
|
||||
return Err(ParseError::BadSectionContent);
|
||||
};
|
||||
res.push(Section {
|
||||
name: name.trim().to_owned(),
|
||||
content: CString(s),
|
||||
@ -200,7 +202,9 @@ fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
||||
// every operation has at most 3 arguments
|
||||
let mut bits = i.split_whitespace();
|
||||
trace!("current parse code line: {}", i);
|
||||
let Some(op) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||
let Some(op) = bits.next() else {
|
||||
return Err(ParseError::BadInstruction);
|
||||
};
|
||||
|
||||
// no type
|
||||
match op {
|
||||
@ -214,8 +218,12 @@ fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
||||
};
|
||||
|
||||
// I-type
|
||||
let Some(r1) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||
let Some(r2) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||
let Some(r1) = bits.next() else {
|
||||
return Err(ParseError::BadInstruction);
|
||||
};
|
||||
let Some(r2) = bits.next() else {
|
||||
return Err(ParseError::BadInstruction);
|
||||
};
|
||||
|
||||
match op {
|
||||
"addi" => {
|
||||
@ -230,7 +238,9 @@ fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let Some(r3) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||
let Some(r3) = bits.next() else {
|
||||
return Err(ParseError::BadInstruction);
|
||||
};
|
||||
|
||||
// R-type
|
||||
match op {
|
||||
@ -277,3 +287,17 @@ fn parse_const(i: &str) -> Result<Const, ParseError> {
|
||||
};
|
||||
return Ok(Const::C(num));
|
||||
}
|
||||
|
||||
/// TESTS
|
||||
|
||||
#[test]
|
||||
fn parser_test() {
|
||||
let code = std::fs::read_to_string("./tests/assembly/hello_world.grasm").unwrap();
|
||||
|
||||
let parser = Parser::new(code);
|
||||
|
||||
let _r = parser.parse_sections();
|
||||
|
||||
// #TODO: WRITE PARSER TEST SUITE!
|
||||
//assert_eq!(r, Ok(vec![]));
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
// use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::assembler::parser;
|
||||
|
||||
#[test]
|
||||
fn parser_test() {
|
||||
println!("Parser test begins");
|
||||
let code = std::fs::read_to_string("./tests/assembly/hello_world.grasm").unwrap();
|
||||
|
||||
let parser = parser::Parser::new(code);
|
||||
|
||||
let r = parser.parse_sections().unwrap();
|
||||
|
||||
println!("Parsed sections: {:?}", r);
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(author, version, about, long_about = None, arg_required_else_help = true)]
|
||||
pub struct Cli {
|
||||
|
||||
|
||||
#[command(subcommand)]
|
||||
pub comm: Option<Subc>,
|
||||
|
||||
@ -18,9 +15,37 @@ pub struct Cli {
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Subc {
|
||||
Run {
|
||||
filename: String,
|
||||
},
|
||||
Build {
|
||||
filename: String,
|
||||
output: String,
|
||||
},
|
||||
View {
|
||||
filename: String,
|
||||
|
||||
Run {filename: String},
|
||||
Build {filename: String, output: String},
|
||||
View {filename: String},
|
||||
|
||||
#[arg(short, long, default_value_t = FormatKind::FancyTable)]
|
||||
format: FormatKind,
|
||||
},
|
||||
Debug {},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
|
||||
pub enum FormatKind {
|
||||
FancyTable,
|
||||
Raw,
|
||||
BinSections,
|
||||
Serializable,
|
||||
}
|
||||
|
||||
impl Display for FormatKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FormatKind::FancyTable => write!(f, "fancy-table"),
|
||||
FormatKind::Raw => write!(f, "raw"),
|
||||
FormatKind::BinSections => write!(f, "memory"),
|
||||
FormatKind::Serializable => write!(f, "rust"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/lib.rs
12
src/lib.rs
@ -1,11 +1,11 @@
|
||||
use std::mem::transmute;
|
||||
|
||||
pub mod assembler;
|
||||
pub mod cli;
|
||||
pub mod cpu;
|
||||
pub mod jit;
|
||||
pub mod loader;
|
||||
pub mod cli;
|
||||
pub mod assembler;
|
||||
//
|
||||
pub mod pretty_printers;
|
||||
|
||||
pub fn interpret_as_signed(x: u16) -> i16 {
|
||||
// the two types have the same size.
|
||||
@ -22,14 +22,13 @@ pub fn interpret_as_unsigned(x: i16) -> u16 {
|
||||
}
|
||||
|
||||
pub fn transmute_to_vecu16_as_is(x: Vec<u8>) -> Vec<u16> {
|
||||
|
||||
let mut rb = x.iter();
|
||||
// raw_bytes must be converted to u16.
|
||||
//
|
||||
let bytes: Vec<u16> = {
|
||||
let mut res = vec![];
|
||||
// highly cursed: depends on the order in which the arguments of a tuple are
|
||||
// evaluated. Does its job!
|
||||
// Depends on the order in which the arguments of a tuple are
|
||||
// evaluated. Rust guarantees to be left-to-right.
|
||||
while let (Some(word0), Some(word1)) = (rb.next(), rb.next()) {
|
||||
// println!("Pair: {}, {}, word: {:?}", word0, word1, raw_bytes);
|
||||
res.push(((*word0 as u16) << 8) + (*word1 as u16));
|
||||
@ -55,7 +54,6 @@ pub fn transmute_to_vecu16_as_is(x: Vec<u8>) -> Vec<u16> {
|
||||
}
|
||||
|
||||
pub fn transmute_to_vecu8_as_is(x: Vec<u16>) -> Vec<u8> {
|
||||
|
||||
let mut bytes = vec![];
|
||||
|
||||
for b in x.iter() {
|
||||
|
72
src/main.rs
72
src/main.rs
@ -1,13 +1,14 @@
|
||||
|
||||
use dekejit::assembler::parser;
|
||||
use dekejit::cli::{Cli, Subc::*};
|
||||
use dekejit::cpu::IOBuffer;
|
||||
use dekejit::cpu::CPU;
|
||||
use dekejit::pretty_printers::*;
|
||||
|
||||
use clap::Parser;
|
||||
use dekejit::loader::loader::prepare_memory;
|
||||
use dekejit::loader::loader::read_binary;
|
||||
use dekejit::loader::loader::{self, prepare_memory};
|
||||
use dekejit::transmute_to_vecu8_as_is;
|
||||
use log::{info, debug};
|
||||
use log::{debug, info};
|
||||
|
||||
fn main() {
|
||||
let cli: Cli = Cli::parse();
|
||||
@ -25,7 +26,6 @@ fn main() {
|
||||
|
||||
match cli.comm {
|
||||
Some(Run { filename }) => {
|
||||
|
||||
info!("Trying to read {}", filename);
|
||||
|
||||
let Ok(content) = std::fs::read(&filename) else {
|
||||
@ -33,7 +33,6 @@ fn main() {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
||||
|
||||
info!("Begin parsing file {}", &filename);
|
||||
@ -47,7 +46,7 @@ fn main() {
|
||||
|
||||
let bin = prepare_memory(sections);
|
||||
|
||||
info!("{:?}", bin);
|
||||
// trace!("{:?}", bin);
|
||||
|
||||
let mut env = IOBuffer::default();
|
||||
//
|
||||
@ -61,23 +60,21 @@ fn main() {
|
||||
};
|
||||
}
|
||||
Some(Build { filename, output }) => {
|
||||
|
||||
let Ok(inp_content) = std::fs::read_to_string(filename.clone()) else {
|
||||
println!("Could not read file {}", &filename);
|
||||
return;
|
||||
};
|
||||
|
||||
let prs = dekejit::assembler::parser::Parser::new(inp_content);
|
||||
let prs = dekejit::assembler::parser::Parser::new(inp_content);
|
||||
|
||||
let sections = match prs.parse_sections() {
|
||||
Ok(s) => s,
|
||||
Err(p_err) => {
|
||||
println!("Parser error: {:?}", p_err);
|
||||
return;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let Some(bin) = dekejit::assembler::to_binary(sections) else {
|
||||
println!("Unspecified error while converting file to binary. Must fix.");
|
||||
return;
|
||||
@ -91,46 +88,57 @@ fn main() {
|
||||
|
||||
let out_bin = transmute_to_vecu8_as_is(out_bin);
|
||||
|
||||
|
||||
info!("{:?}", out_bin);
|
||||
|
||||
match std::fs::write(output.clone(), out_bin) {
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
println!("could not write file {}", output);},
|
||||
println!("could not write file {}", output);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Some(View {filename}) => {
|
||||
|
||||
Some(View { filename, format }) => {
|
||||
info!("Trying to read {}", filename);
|
||||
|
||||
let Ok(content) = std::fs::read(&filename) else {
|
||||
println!("File {} does not exist or cannot be read.", &filename);
|
||||
let Ok(content) = std::fs::read_to_string(&filename) else {
|
||||
println!("File {} does not exist or cannot be read", &filename);
|
||||
return;
|
||||
};
|
||||
|
||||
let p = parser::Parser::new(content);
|
||||
|
||||
let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
||||
let s = p.parse_sections().unwrap();
|
||||
|
||||
info!("{:?}", bytes);
|
||||
println!(
|
||||
"{}",
|
||||
format_code(dekejit::pretty_printers::CodeWrapper::Asm(s), format).unwrap()
|
||||
);
|
||||
|
||||
info!("Begin parsing file {}", &filename);
|
||||
let sections = match read_binary(&bytes) {
|
||||
Ok(k) => k,
|
||||
Err(p) => {
|
||||
println!("Parsing error: {:?}", p);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("{:?}", sections);
|
||||
// let Ok(content) = std::fs::read(&filename) else {
|
||||
// println!("File {} does not exist or cannot be read.", &filename);
|
||||
// return;
|
||||
// };
|
||||
//
|
||||
//
|
||||
// let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
||||
//
|
||||
// info!("{:?}", bytes);
|
||||
//
|
||||
// info!("Begin parsing file {}", &filename);
|
||||
// let sections = match read_binary(&bytes) {
|
||||
// Ok(k) => k,
|
||||
// Err(p) => {
|
||||
// println!("Parsing error: {:?}", p);
|
||||
// return;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// println!("{:?}", sections);
|
||||
}
|
||||
Some(Debug) => {}
|
||||
None => {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// let parser = parser::Parser::new(code);
|
||||
//
|
||||
// let r = parser.parse_sections().unwrap();
|
||||
|
133
src/pretty_printers/asm.rs
Normal file
133
src/pretty_printers/asm.rs
Normal file
@ -0,0 +1,133 @@
|
||||
|
||||
|
||||
use crate::assembler::{self, print_op};
|
||||
|
||||
use super::{Section, SectionContent};
|
||||
|
||||
/// Aaaand we don't know the size of the terminal.
|
||||
/// We'll just pretend it is 80 characters.
|
||||
/// It would be wise to be able to change it with
|
||||
/// a parameter. Later! #TODO
|
||||
|
||||
pub const TERMSIZE : u8 = 80;
|
||||
|
||||
|
||||
pub fn to_table(secs: Vec<Section>) -> String {
|
||||
|
||||
let mut res = String::new();
|
||||
|
||||
let mut header = String::from("┌");
|
||||
|
||||
for _ in 2..TERMSIZE {
|
||||
header.push('─');
|
||||
}
|
||||
|
||||
header.push('┐');
|
||||
header.push('\n');
|
||||
// ┏
|
||||
// ┐
|
||||
//
|
||||
res.push_str(&header);
|
||||
|
||||
let sections_strings : Vec<String> = secs.into_iter().map(|x| draw_section(x)).collect();
|
||||
|
||||
let cont = sections_strings.join(&draw_separator(TERMSIZE));
|
||||
|
||||
res.push_str(&cont);
|
||||
|
||||
let mut footer = String::from("└");
|
||||
|
||||
for _ in 2..TERMSIZE {
|
||||
footer.push('─');
|
||||
}
|
||||
|
||||
footer.push('┘');
|
||||
|
||||
res.push_str(&footer);
|
||||
|
||||
|
||||
|
||||
// for i in -40..40 {
|
||||
// let p : i32 = (c as u32).try_into().unwrap();
|
||||
// res.push_str(&format!("{}\n", char::from_u32((p + i).try_into().unwrap()).unwrap()));
|
||||
// }
|
||||
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn draw_section(sec: Section) -> String {
|
||||
|
||||
let mut res = String::new();
|
||||
|
||||
res.push_str(&format_line(&format!("{}", sec.name), 4, TERMSIZE));
|
||||
|
||||
res.push_str(&draw_separator(TERMSIZE));
|
||||
|
||||
match sec.content {
|
||||
SectionContent::Code(ops) => {
|
||||
// we just print ops again.
|
||||
// we could make this prettier.
|
||||
|
||||
for op in ops {
|
||||
res.push_str(&format_line(&print_op(op), 8, TERMSIZE));
|
||||
|
||||
}
|
||||
},
|
||||
SectionContent::CString(s) => res.push_str(&format_line(&s, 8, TERMSIZE)),
|
||||
SectionContent::CVec() => todo!(),
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// Formats a line in a box, with said indendation.
|
||||
fn format_line(s: &str, indentation: u8, len: u8) -> String {
|
||||
let mut res = String::from("│");
|
||||
|
||||
// TODO: UTF8 GRAPHEMES???;
|
||||
|
||||
for _ in 1..indentation {
|
||||
res.push(' ');
|
||||
}
|
||||
|
||||
res.push_str(s);
|
||||
|
||||
// if ASCII, or even extended ascii are concerned,
|
||||
// this works. But we need to get the number of graphemes, which can be tricky
|
||||
// in utf-8.
|
||||
|
||||
for _ in res.len()..(len + 1) as usize {
|
||||
res.push(' ');
|
||||
}
|
||||
|
||||
res.push_str("│\n");
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/// Draws a horizontal line of specified length.
|
||||
///
|
||||
fn draw_separator(len: u8) -> String {
|
||||
|
||||
let mut res = String::from("├");
|
||||
|
||||
for _ in 2..len {
|
||||
res.push('─');
|
||||
}
|
||||
|
||||
res.push('┤');
|
||||
res.push('\n');
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
0
src/pretty_printers/bin.rs
Normal file
0
src/pretty_printers/bin.rs
Normal file
49
src/pretty_printers/mod.rs
Normal file
49
src/pretty_printers/mod.rs
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
mod asm;
|
||||
mod bin;
|
||||
|
||||
use asm::*;
|
||||
use bin::*;
|
||||
|
||||
use crate::loader::loader::read_binary;
|
||||
|
||||
// We'll use the Cli to dictate which format kinds are available.
|
||||
use super::cli::FormatKind;
|
||||
|
||||
|
||||
use super::assembler::parser::{*};
|
||||
|
||||
|
||||
pub enum CodeWrapper {
|
||||
Bin(Vec<u16>),
|
||||
Asm(Vec<Section>)
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn format_code(c: CodeWrapper, k: FormatKind) -> Result<String, crate::loader::loader::ParseError> {
|
||||
|
||||
|
||||
match k {
|
||||
FormatKind::FancyTable => match c {
|
||||
CodeWrapper::Bin(_) => todo!(),
|
||||
CodeWrapper::Asm(sections) => {
|
||||
return Ok(to_table(sections))},
|
||||
},
|
||||
FormatKind::Raw => match c {
|
||||
CodeWrapper::Bin(_) => todo!(),
|
||||
CodeWrapper::Asm(_) => todo!(),
|
||||
},
|
||||
FormatKind::BinSections => match c {
|
||||
CodeWrapper::Bin(_) => todo!(),
|
||||
CodeWrapper::Asm(_) => todo!(),
|
||||
},
|
||||
FormatKind::Serializable => match c {
|
||||
CodeWrapper::Bin(_) => todo!(),
|
||||
CodeWrapper::Asm(_) => todo!(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user