Compare commits

...

10 Commits

Author SHA1 Message Date
61a3a305b6
print help with no arguments 2024-02-12 21:31:47 +01:00
579b5ed75b
readme correction 2024-02-12 21:13:39 +01:00
b95c01116a
added readme, made program installable 2024-02-12 21:10:36 +01:00
522043e376
removed cackle
Not really used, not that many dependencies either
2024-02-12 21:10:05 +01:00
8f894174d9 preparing to refactor
renamed AST to ast, following rust naming convention, removed some useless test files and moved the tests to the appropriate files.
2023-11-13 00:06:16 +01:00
9358bfa77f updated deps 2023-11-12 08:34:35 +01:00
724cc66faa fixed typos 2023-11-12 07:00:44 +01:00
f64747dc93 added cackle???
Cackle is a acl for rust crates, to prevent dependency injection attacks. Maybe??
2023-10-20 00:25:59 +02:00
40daf685ff updated documentation, a bit 2023-06-12 16:23:30 +02:00
e2b7ec3e43 added rudimentary pretty printer. Right now utterly useless. 2023-05-08 12:37:27 +02:00
15 changed files with 447 additions and 163 deletions

View File

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

View File

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

View File

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

View File

@ -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;
@ -61,14 +62,16 @@ 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

View File

@ -1,4 +1,4 @@
use super::AST::{Const, Operation};
use super::ast::{Const, Operation};
use Operation::*;
use log::*;
@ -21,9 +21,7 @@ pub struct Parser {
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![]));
}

View File

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

View File

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

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 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() {

View File

@ -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,7 +60,6 @@ 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;
@ -74,10 +72,9 @@ fn main() {
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
View 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;
}

View File

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