Compare commits
No commits in common. "61a3a305b6339baca5c0252e344809cdb1f35ae6" and "03668a71e88e45c4123305283de17cbd4f3f1773" have entirely different histories.
61a3a305b6
...
03668a71e8
@ -6,9 +6,6 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4.8", features = ["derive"] }
|
clap = { version = "4.2.5", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4.17"
|
||||||
simple_logger = "4.2.0"
|
simple_logger = "4.1.0"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "dekejit"
|
|
||||||
|
29
README.md
29
README.md
@ -1,29 +0,0 @@
|
|||||||
# 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.
|
The gravejit virtual machine sports 16 16-bit registers (plus the program counter!) and 16 operations.
|
||||||
|
|
||||||
Here is the list of registers together with memonics.
|
Here is the list of registers togheter with memonics.
|
||||||
```
|
|
||||||
0 : zero // register 0 is always 0.
|
0 : zero // register 0 is always 0.
|
||||||
1 : ra // return address
|
1 : ra // return address
|
||||||
2 : sp // stack pointer
|
2 : sp // stack pointer
|
||||||
@ -30,33 +30,32 @@ Here is the list of registers together with memonics.
|
|||||||
15: t4 // don't know what to do with this
|
15: t4 // don't know what to do with this
|
||||||
|
|
||||||
pc: program counter.
|
pc: program counter.
|
||||||
```
|
|
||||||
|
|
||||||
## ISA
|
## ISA
|
||||||
|
|
||||||
| opcode | memonic | format | description |
|
opcode | memonic | format | description
|
||||||
| ------ | -------------- | ------- | --------------------------------------- |
|
|
||||||
| 0000 | NOP | just 0s'| Does nothing. |
|
0000 | NOP | just 0s'| Does nothing.
|
||||||
| 0001 | ADD s0 s1 s2 | R | s0 = s1 + s2 |
|
0001 | ADD s0 s1 s2 | R | s0 = s1 + s2
|
||||||
| 0010 | SUB 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 |
|
0011 | AND s0 s1 s2 | R | s0 = s1 && s2
|
||||||
| 0100 | XOR s0 s1 s2 | R | s0 = s1 xor s2 |
|
0100 | XOR s0 s1 s2 | R | s0 = s1 xor s2
|
||||||
| 0101 | SLL s0 s1 s2 | R | s0 = s1 << s2 |
|
0101 | SLL s0 s1 s2 | R | s0 = s1 << s2
|
||||||
| 0110 | SLI s0 c | I | s0 = s0 << c |
|
0110 | SLI s0 c | I | s0 = s0 << c
|
||||||
| 0111 | ADDI 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 |
|
1000 | BEQ s0 s1 s2 | R | if (s1 == s2) -> pc = s0
|
||||||
| 1001 | BGT 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; |
|
1010 | JAL s0 s1 c | J | s0 = pc+1; pc += s1 + c;
|
||||||
| 1011 | | | #TODO? |
|
1011 |
|
||||||
| 1100 | LOAD s0 s1 s2 | R | loads s1 + shift by s2 in s0 |
|
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 |
|
1101 | STORE s0 s1 s2| R | stores s0 in address s1 + shift by s2
|
||||||
| 1110 | CALL s0 c | I | performs system call |
|
1110 | CALL s0 c | I | performs system call
|
||||||
| 1111 | HALT | just 1s'| halt, and possibly catch fire. |
|
1111 | HALT | just 1s'| halt, and possibly catch fire.
|
||||||
|
|
||||||
|
|
||||||
### Operation formats:
|
### Operation formats:
|
||||||
|
|
||||||
Each instruction is 16 bits long.
|
Each istruction is 16 bits long.
|
||||||
The first 4 most-significant bits are the opcode.
|
The first 4 most-significant bits are the opcode.
|
||||||
Constants (c in the above table) are always considered signed, and written in
|
Constants (c in the above table) are always considered signed, and written in
|
||||||
two's compliment. Sign extension also takes place whenever needed.
|
two's compliment. Sign extension also takes place whenever needed.
|
||||||
@ -91,7 +90,9 @@ The constant is added to the value of the second register argument.
|
|||||||
|
|
||||||
### JIT's system calls:
|
### JIT's system calls:
|
||||||
|
|
||||||
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.
|
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...
|
||||||
|
|
||||||
#### io\_vec: first systemcall environment
|
#### io\_vec: first systemcall environment
|
||||||
|
|
||||||
|
40
src/assembler/AST.rs
Normal file
40
src/assembler/AST.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/// 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),
|
||||||
|
}
|
@ -1,72 +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),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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,8 +1,9 @@
|
|||||||
use super::ast::*;
|
use super::AST::*;
|
||||||
use crate::cpu::get_num;
|
use crate::cpu::get_num;
|
||||||
|
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
|
|
||||||
|
|
||||||
/// Trait to represent a format we can translate our assembly to.
|
/// Trait to represent a format we can translate our assembly to.
|
||||||
pub trait CodeFormat {
|
pub trait CodeFormat {
|
||||||
fn encode_op(op: &Operation, sy: &SymbolTable, current_pc: u16) -> Option<Self>
|
fn encode_op(op: &Operation, sy: &SymbolTable, current_pc: u16) -> Option<Self>
|
||||||
@ -35,7 +36,7 @@ impl SymbolTable {
|
|||||||
// query, sy
|
// query, sy
|
||||||
// );
|
// );
|
||||||
warn!("Symbol {} not found in symbol table.", query);
|
warn!("Symbol {} not found in symbol table.", query);
|
||||||
return None;
|
return None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
mod ast;
|
mod AST;
|
||||||
pub mod encoder;
|
pub mod encoder;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use encoder::CodeFormat;
|
use encoder::CodeFormat;
|
||||||
use encoder::SymbolTable;
|
use encoder::SymbolTable;
|
||||||
|
|
||||||
pub use ast::print_op;
|
use log::{trace, debug};
|
||||||
|
|
||||||
use log::{debug, trace};
|
|
||||||
|
|
||||||
use parser::Section;
|
use parser::Section;
|
||||||
|
|
||||||
@ -25,7 +24,7 @@ impl Section {
|
|||||||
// UTF-8 strings are collections of 8-bit chunks, but they're
|
// UTF-8 strings are collections of 8-bit chunks, but they're
|
||||||
// packed into words of 16 bits + 16 bit NULL.
|
// 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.
|
// we add a 8 bit empty padding and then the NULL byte.
|
||||||
SectionContent::CString(s) => {
|
SectionContent::CString(s) => {
|
||||||
let c = s.len();
|
let c = s.len();
|
||||||
@ -40,7 +39,7 @@ impl Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Converts this section to binary. Needs symbol table to
|
/// 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.
|
/// this own section.
|
||||||
fn to_binary(&self, sy: &SymbolTable) -> Option<crate::loader::Section> {
|
fn to_binary(&self, sy: &SymbolTable) -> Option<crate::loader::Section> {
|
||||||
let own_address = sy.lookup(&self.name)?;
|
let own_address = sy.lookup(&self.name)?;
|
||||||
@ -62,19 +61,17 @@ impl Section {
|
|||||||
return Some(crate::loader::Section::new(self.name.clone(), &res));
|
return Some(crate::loader::Section::new(self.name.clone(), &res));
|
||||||
}
|
}
|
||||||
SectionContent::CString(s) => {
|
SectionContent::CString(s) => {
|
||||||
return Some(crate::loader::Section::new(
|
return Some(crate::loader::Section::new(self.name.clone(), &make_string(s)));
|
||||||
self.name.clone(),
|
|
||||||
&make_string(s),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
SectionContent::CVec() => todo!(),
|
SectionContent::CVec() => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Sorts a list of sections.
|
/// Sorts a list of sections.
|
||||||
/// All .text sections containing code are
|
/// 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,
|
/// appear in the assembly file, except the "main" section,
|
||||||
/// which is the entrypoint of our program and must be put
|
/// which is the entrypoint of our program and must be put
|
||||||
/// at the very beginning.
|
/// at the very beginning.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::ast::{Const, Operation};
|
use super::AST::{Const, Operation};
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
@ -16,12 +16,14 @@ pub enum ParseError {
|
|||||||
/// represents the state of our parser.
|
/// represents the state of our parser.
|
||||||
/// Sadly parsing is stateless,
|
/// Sadly parsing is stateless,
|
||||||
pub struct Parser {
|
pub struct Parser {
|
||||||
input: Vec<String>, // input file
|
input: Vec<String>, // input file
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
pub fn new(i: String) -> Self {
|
pub fn new(i: String) -> Self {
|
||||||
Parser { input: sanitize(i) }
|
Parser {
|
||||||
|
input: sanitize(i),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,20 +89,19 @@ fn take_between(i: &str, start: &str, stop: &str) -> Option<(String, String)> {
|
|||||||
return take_alpha_till(&s1, stop);
|
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]
|
#[test]
|
||||||
fn take_between_test1() {
|
fn take_between_test() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
take_between("\"wow\" etc", "\"", "\""),
|
take_between("\"wow\" etc", "\"", "\""),
|
||||||
Some(("wow".to_string(), " etc".to_string()))
|
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
|
//// SECTION PARSING
|
||||||
|
|
||||||
/// Enum to represent possible section content.
|
/// Enum to represent possible section content.
|
||||||
@ -120,6 +121,7 @@ pub struct Section {
|
|||||||
pub content: SectionContent,
|
pub content: SectionContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
pub fn parse_sections(&self) -> Result<Vec<Section>, ParseError> {
|
pub fn parse_sections(&self) -> Result<Vec<Section>, ParseError> {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
@ -149,12 +151,8 @@ impl Parser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"asciiz" => {
|
"asciiz" => {
|
||||||
let Some(s) = lines.next() else {
|
let Some(s) = lines.next() else {return Err(ParseError::UnexpectedEOF)};
|
||||||
return Err(ParseError::UnexpectedEOF);
|
let Some((s, _)) = take_between(s.trim(), "\"", "\"") else {return Err(ParseError::BadSectionContent)};
|
||||||
};
|
|
||||||
let Some((s, _)) = take_between(s.trim(), "\"", "\"") else {
|
|
||||||
return Err(ParseError::BadSectionContent);
|
|
||||||
};
|
|
||||||
res.push(Section {
|
res.push(Section {
|
||||||
name: name.trim().to_owned(),
|
name: name.trim().to_owned(),
|
||||||
content: CString(s),
|
content: CString(s),
|
||||||
@ -202,9 +200,7 @@ fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
|||||||
// every operation has at most 3 arguments
|
// every operation has at most 3 arguments
|
||||||
let mut bits = i.split_whitespace();
|
let mut bits = i.split_whitespace();
|
||||||
trace!("current parse code line: {}", i);
|
trace!("current parse code line: {}", i);
|
||||||
let Some(op) = bits.next() else {
|
let Some(op) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||||
return Err(ParseError::BadInstruction);
|
|
||||||
};
|
|
||||||
|
|
||||||
// no type
|
// no type
|
||||||
match op {
|
match op {
|
||||||
@ -218,12 +214,8 @@ fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// I-type
|
// I-type
|
||||||
let Some(r1) = bits.next() else {
|
let Some(r1) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||||
return Err(ParseError::BadInstruction);
|
let Some(r2) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||||
};
|
|
||||||
let Some(r2) = bits.next() else {
|
|
||||||
return Err(ParseError::BadInstruction);
|
|
||||||
};
|
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
"addi" => {
|
"addi" => {
|
||||||
@ -238,9 +230,7 @@ fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(r3) = bits.next() else {
|
let Some(r3) = bits.next() else {return Err(ParseError::BadInstruction)};
|
||||||
return Err(ParseError::BadInstruction);
|
|
||||||
};
|
|
||||||
|
|
||||||
// R-type
|
// R-type
|
||||||
match op {
|
match op {
|
||||||
@ -287,17 +277,3 @@ fn parse_const(i: &str) -> Result<Const, ParseError> {
|
|||||||
};
|
};
|
||||||
return Ok(Const::C(num));
|
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![]));
|
|
||||||
}
|
|
||||||
|
18
src/assembler/tests.rs
Normal file
18
src/assembler/tests.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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,10 +1,13 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand, ValueEnum};
|
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None, arg_required_else_help = true)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub comm: Option<Subc>,
|
pub comm: Option<Subc>,
|
||||||
|
|
||||||
@ -15,37 +18,9 @@ pub struct Cli {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum Subc {
|
pub enum Subc {
|
||||||
Run {
|
|
||||||
filename: String,
|
|
||||||
},
|
|
||||||
Build {
|
|
||||||
filename: String,
|
|
||||||
output: String,
|
|
||||||
},
|
|
||||||
View {
|
|
||||||
filename: String,
|
|
||||||
|
|
||||||
#[arg(short, long, default_value_t = FormatKind::FancyTable)]
|
Run {filename: String},
|
||||||
format: FormatKind,
|
Build {filename: String, output: String},
|
||||||
},
|
View {filename: String},
|
||||||
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;
|
use std::mem::transmute;
|
||||||
|
|
||||||
pub mod assembler;
|
|
||||||
pub mod cli;
|
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
pub mod jit;
|
pub mod jit;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
pub mod pretty_printers;
|
pub mod cli;
|
||||||
|
pub mod assembler;
|
||||||
|
//
|
||||||
|
|
||||||
pub fn interpret_as_signed(x: u16) -> i16 {
|
pub fn interpret_as_signed(x: u16) -> i16 {
|
||||||
// the two types have the same size.
|
// the two types have the same size.
|
||||||
@ -22,13 +22,14 @@ pub fn interpret_as_unsigned(x: i16) -> u16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn transmute_to_vecu16_as_is(x: Vec<u8>) -> Vec<u16> {
|
pub fn transmute_to_vecu16_as_is(x: Vec<u8>) -> Vec<u16> {
|
||||||
|
|
||||||
let mut rb = x.iter();
|
let mut rb = x.iter();
|
||||||
// raw_bytes must be converted to u16.
|
// raw_bytes must be converted to u16.
|
||||||
//
|
//
|
||||||
let bytes: Vec<u16> = {
|
let bytes: Vec<u16> = {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
// Depends on the order in which the arguments of a tuple are
|
// highly cursed: depends on the order in which the arguments of a tuple are
|
||||||
// evaluated. Rust guarantees to be left-to-right.
|
// evaluated. Does its job!
|
||||||
while let (Some(word0), Some(word1)) = (rb.next(), rb.next()) {
|
while let (Some(word0), Some(word1)) = (rb.next(), rb.next()) {
|
||||||
// println!("Pair: {}, {}, word: {:?}", word0, word1, raw_bytes);
|
// println!("Pair: {}, {}, word: {:?}", word0, word1, raw_bytes);
|
||||||
res.push(((*word0 as u16) << 8) + (*word1 as u16));
|
res.push(((*word0 as u16) << 8) + (*word1 as u16));
|
||||||
@ -54,6 +55,7 @@ pub fn transmute_to_vecu16_as_is(x: Vec<u8>) -> Vec<u16> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn transmute_to_vecu8_as_is(x: Vec<u16>) -> Vec<u8> {
|
pub fn transmute_to_vecu8_as_is(x: Vec<u16>) -> Vec<u8> {
|
||||||
|
|
||||||
let mut bytes = vec![];
|
let mut bytes = vec![];
|
||||||
|
|
||||||
for b in x.iter() {
|
for b in x.iter() {
|
||||||
|
72
src/main.rs
72
src/main.rs
@ -1,14 +1,13 @@
|
|||||||
use dekejit::assembler::parser;
|
|
||||||
use dekejit::cli::{Cli, Subc::*};
|
use dekejit::cli::{Cli, Subc::*};
|
||||||
use dekejit::cpu::IOBuffer;
|
use dekejit::cpu::IOBuffer;
|
||||||
use dekejit::cpu::CPU;
|
use dekejit::cpu::CPU;
|
||||||
use dekejit::pretty_printers::*;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use dekejit::loader::loader::prepare_memory;
|
||||||
use dekejit::loader::loader::read_binary;
|
use dekejit::loader::loader::read_binary;
|
||||||
use dekejit::loader::loader::{self, prepare_memory};
|
|
||||||
use dekejit::transmute_to_vecu8_as_is;
|
use dekejit::transmute_to_vecu8_as_is;
|
||||||
use log::{debug, info};
|
use log::{info, debug};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli: Cli = Cli::parse();
|
let cli: Cli = Cli::parse();
|
||||||
@ -26,6 +25,7 @@ fn main() {
|
|||||||
|
|
||||||
match cli.comm {
|
match cli.comm {
|
||||||
Some(Run { filename }) => {
|
Some(Run { filename }) => {
|
||||||
|
|
||||||
info!("Trying to read {}", filename);
|
info!("Trying to read {}", filename);
|
||||||
|
|
||||||
let Ok(content) = std::fs::read(&filename) else {
|
let Ok(content) = std::fs::read(&filename) else {
|
||||||
@ -33,6 +33,7 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
||||||
|
|
||||||
info!("Begin parsing file {}", &filename);
|
info!("Begin parsing file {}", &filename);
|
||||||
@ -46,7 +47,7 @@ fn main() {
|
|||||||
|
|
||||||
let bin = prepare_memory(sections);
|
let bin = prepare_memory(sections);
|
||||||
|
|
||||||
// trace!("{:?}", bin);
|
info!("{:?}", bin);
|
||||||
|
|
||||||
let mut env = IOBuffer::default();
|
let mut env = IOBuffer::default();
|
||||||
//
|
//
|
||||||
@ -60,21 +61,23 @@ fn main() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
Some(Build { filename, output }) => {
|
Some(Build { filename, output }) => {
|
||||||
|
|
||||||
let Ok(inp_content) = std::fs::read_to_string(filename.clone()) else {
|
let Ok(inp_content) = std::fs::read_to_string(filename.clone()) else {
|
||||||
println!("Could not read file {}", &filename);
|
println!("Could not read file {}", &filename);
|
||||||
return;
|
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() {
|
let sections = match prs.parse_sections() {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(p_err) => {
|
Err(p_err) => {
|
||||||
println!("Parser error: {:?}", p_err);
|
println!("Parser error: {:?}", p_err);
|
||||||
return;
|
return;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let Some(bin) = dekejit::assembler::to_binary(sections) else {
|
let Some(bin) = dekejit::assembler::to_binary(sections) else {
|
||||||
println!("Unspecified error while converting file to binary. Must fix.");
|
println!("Unspecified error while converting file to binary. Must fix.");
|
||||||
return;
|
return;
|
||||||
@ -88,57 +91,46 @@ fn main() {
|
|||||||
|
|
||||||
let out_bin = transmute_to_vecu8_as_is(out_bin);
|
let out_bin = transmute_to_vecu8_as_is(out_bin);
|
||||||
|
|
||||||
|
|
||||||
info!("{:?}", out_bin);
|
info!("{:?}", out_bin);
|
||||||
|
|
||||||
match std::fs::write(output.clone(), out_bin) {
|
match std::fs::write(output.clone(), out_bin) {
|
||||||
Ok(_) => {}
|
Ok(_) => {},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
println!("could not write file {}", output);
|
println!("could not write file {}", output);},
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
Some(View { filename, format }) => {
|
Some(View {filename}) => {
|
||||||
|
|
||||||
info!("Trying to read {}", filename);
|
info!("Trying to read {}", filename);
|
||||||
|
|
||||||
let Ok(content) = std::fs::read_to_string(&filename) else {
|
let Ok(content) = std::fs::read(&filename) else {
|
||||||
println!("File {} does not exist or cannot be read", &filename);
|
println!("File {} does not exist or cannot be read.", &filename);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let p = parser::Parser::new(content);
|
|
||||||
|
|
||||||
let s = p.parse_sections().unwrap();
|
let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
||||||
|
|
||||||
println!(
|
info!("{:?}", bytes);
|
||||||
"{}",
|
|
||||||
format_code(dekejit::pretty_printers::CodeWrapper::Asm(s), format).unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
// let Ok(content) = std::fs::read(&filename) else {
|
info!("Begin parsing file {}", &filename);
|
||||||
// println!("File {} does not exist or cannot be read.", &filename);
|
let sections = match read_binary(&bytes) {
|
||||||
// return;
|
Ok(k) => k,
|
||||||
// };
|
Err(p) => {
|
||||||
//
|
println!("Parsing error: {:?}", p);
|
||||||
//
|
return;
|
||||||
// let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
|
}
|
||||||
//
|
};
|
||||||
// info!("{:?}", bytes);
|
|
||||||
//
|
println!("{:?}", sections);
|
||||||
// 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 => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// let parser = parser::Parser::new(code);
|
// let parser = parser::Parser::new(code);
|
||||||
//
|
//
|
||||||
// let r = parser.parse_sections().unwrap();
|
// let r = parser.parse_sections().unwrap();
|
||||||
|
@ -1,133 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
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