Compare commits

..

No commits in common. "61a3a305b6339baca5c0252e344809cdb1f35ae6" and "03668a71e88e45c4123305283de17cbd4f3f1773" have entirely different histories.

15 changed files with 162 additions and 446 deletions

View File

@ -6,9 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.4.8", features = ["derive"] }
log = "0.4"
simple_logger = "4.2.0"
[[bin]]
name = "dekejit"
clap = { version = "4.2.5", features = ["derive"] }
log = "0.4.17"
simple_logger = "4.1.0"

View File

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

@ -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 together with memonics.
```
Here is the list of registers togheter with memonics.
0 : zero // register 0 is always 0.
1 : ra // return address
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
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 | | | #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. |
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.
### Operation formats:
Each instruction is 16 bits long.
Each istruction 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.
@ -91,7 +90,9 @@ The constant is added to the value of the second register argument.
### 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

40
src/assembler/AST.rs Normal file
View 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),
}

View File

@ -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)),
}
}

View File

@ -1,8 +1,9 @@
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>
@ -35,7 +36,7 @@ impl SymbolTable {
// query, sy
// );
warn!("Symbol {} not found in symbol table.", query);
return None;
return None
}
}

View File

@ -1,13 +1,12 @@
mod ast;
mod AST;
pub mod encoder;
pub mod parser;
mod tests;
use encoder::CodeFormat;
use encoder::SymbolTable;
pub use ast::print_op;
use log::{debug, trace};
use log::{trace, debug};
use parser::Section;
@ -25,7 +24,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();
@ -40,7 +39,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)?;
@ -62,19 +61,17 @@ 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.

View File

@ -1,4 +1,4 @@
use super::ast::{Const, Operation};
use super::AST::{Const, Operation};
use Operation::*;
use log::*;
@ -16,12 +16,14 @@ 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),
}
}
}
@ -87,20 +89,19 @@ 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_test1() {
fn take_between_test() {
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.
@ -120,6 +121,7 @@ pub struct Section {
pub content: SectionContent,
}
impl Parser {
pub fn parse_sections(&self) -> Result<Vec<Section>, ParseError> {
let mut res = vec![];
@ -149,12 +151,8 @@ 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),
@ -202,9 +200,7 @@ 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 {
@ -218,12 +214,8 @@ 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" => {
@ -238,9 +230,7 @@ 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 {
@ -287,17 +277,3 @@ 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![]));
}

18
src/assembler/tests.rs Normal file
View 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);
}
}

View File

@ -1,10 +1,13 @@
use std::fmt::Display;
use clap::{Parser, Subcommand, ValueEnum};
use clap::{Parser, Subcommand};
#[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 {
#[command(subcommand)]
pub comm: Option<Subc>,
@ -15,37 +18,9 @@ pub struct Cli {
#[derive(Subcommand, Debug)]
pub enum Subc {
Run {
filename: String,
},
Build {
filename: String,
output: String,
},
View {
filename: String,
#[arg(short, long, default_value_t = FormatKind::FancyTable)]
format: FormatKind,
},
Debug {},
}
Run {filename: String},
Build {filename: String, output: String},
View {filename: String},
#[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"),
}
}
}

View File

@ -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 pretty_printers;
pub mod cli;
pub mod assembler;
//
pub fn interpret_as_signed(x: u16) -> i16 {
// 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> {
let mut rb = x.iter();
// raw_bytes must be converted to u16.
//
let bytes: Vec<u16> = {
let mut res = vec![];
// Depends on the order in which the arguments of a tuple are
// evaluated. Rust guarantees to be left-to-right.
// highly cursed: depends on the order in which the arguments of a tuple are
// evaluated. Does its job!
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));
@ -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> {
let mut bytes = vec![];
for b in x.iter() {

View File

@ -1,14 +1,13 @@
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::{debug, info};
use log::{info, debug};
fn main() {
let cli: Cli = Cli::parse();
@ -26,6 +25,7 @@ fn main() {
match cli.comm {
Some(Run { filename }) => {
info!("Trying to read {}", filename);
let Ok(content) = std::fs::read(&filename) else {
@ -33,6 +33,7 @@ fn main() {
return;
};
let bytes = dekejit::transmute_to_vecu16_as_is(content.to_vec());
info!("Begin parsing file {}", &filename);
@ -46,7 +47,7 @@ fn main() {
let bin = prepare_memory(sections);
// trace!("{:?}", bin);
info!("{:?}", bin);
let mut env = IOBuffer::default();
//
@ -60,21 +61,23 @@ 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;
@ -88,57 +91,46 @@ 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, format }) => {
Some(View {filename}) => {
info!("Trying to read {}", filename);
let Ok(content) = std::fs::read_to_string(&filename) else {
println!("File {} does not exist or cannot be read", &filename);
let Ok(content) = std::fs::read(&filename) else {
println!("File {} does not exist or cannot be read.", &filename);
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!(
"{}",
format_code(dekejit::pretty_printers::CodeWrapper::Asm(s), format).unwrap()
);
info!("{:?}", bytes);
// 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);
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();

View File

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

View File

@ -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!(),
},
}
}