assembler changes & brainf##k compiler lmao

This commit is contained in:
2025-06-22 03:51:16 +01:00
parent 528ceddade
commit 9c56258c48
10 changed files with 731 additions and 154 deletions
+34
View File
@@ -0,0 +1,34 @@
++++++++++++++++++++++++++++++++++++++++++++ c1v44 : ASCII code of comma
>++++++++++++++++++++++++++++++++ c2v32 : ASCII code of space
>++++++++++++++++ c3v11 : quantity of numbers to be calculated
> c4v0 : zeroth Fibonacci number (will not be printed)
>+ c5v1 : first Fibonacci number
<< c3 : loop counter
[ block : loop to print (i)th number and calculate next one
>> c5 : the number to be printed
block : divide c5 by 10 (preserve c5)
> c6v0 : service zero
>++++++++++ c7v10 : divisor
<< c5 : back to dividend
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<] c5v0 : divmod algo; results in 0 n d_n%d n%d n/d
>[<+>-] c5 : move dividend back to c5 and clear c6
>[-] c7v0 : clear c7
>> block : c9 can have two digits; divide it by ten again
>++++++++++ c10v10: divisor
< c9 : back to dividend
[->-[>+>>]>[+[-<+>]>+>>]<<<<<] c9v0 : another divmod algo; results in 0 d_n%d n%d n/d
>[-] c10v0 : clear c10
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]c12v0 : print nonzero n/d (first digit) and clear c12
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] c11v0 : print nonzero n%d (second digit) and clear c11
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-] c8v0 : print any n%d (last digit) and clear c8
<<<<<<<.>. c1c2 : print comma and space
block : actually calculate next Fibonacci in c6
>>[>>+<<-] c4v0 : move c4 to c6 (don't need to preserve it)
>[>+<<+>-] c5v0 : move c5 to c6 and c4 (need to preserve it)
>[<+>-] c6v0 : move c6 with sum to c5
<<<- c3 : decrement loop counter
]
<<++... c1 : output three dots
Binary file not shown.
+205
View File
@@ -0,0 +1,205 @@
use std::{
collections::HashSet,
fs,
path::{self, Path, PathBuf},
sync::{Arc, Mutex},
thread::{self, JoinHandle},
};
use crate::assembler::{AssembleError, Token, expand_pseudo_ops, lexer, quick_hash};
use crate::assembler::{Node, Parser, resolve_dependencies};
use crate::util::logging::Logger;
// pub fn new_assemble(path: &Path) {
// let program = Program::new();
// let program_ref = ProgramRef::new(program);
// let task = Module::build(path.to_path_buf(), program_ref.clone());
// program_ref.add_task(task);
// // wait on all tasks to finish
// for task in program_ref.get_tasks() {
// let module = task.module.join().unwrap();
// program_ref.add_module(module);
// }
// }
pub struct Program {
pub main_path: PathBuf,
registry: HashSet<u64>,
modules: Vec<Module>,
tasks: Vec<Task>,
logger: Logger,
}
impl Program {
pub fn new() -> Self {
Self {
registry: HashSet::new(),
modules: Vec::new(),
tasks: Vec::new(),
main_path: PathBuf::new(),
logger: Logger::new(),
}
}
pub fn add_task(&mut self, task: Task) {
self.tasks.push(task);
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}
struct ProgramRef {
program: Arc<Mutex<Program>>,
}
impl ProgramRef {
pub fn new(program: Program) -> Self {
Self {
program: Arc::new(Mutex::new(program)),
}
}
pub fn register(&self, path: &Path) {
self.program
.lock()
.unwrap()
.registry
.insert(quick_hash(path));
}
pub fn is_registered(&self, path: &Path) -> bool {
self.program
.lock()
.unwrap()
.registry
.contains(&quick_hash(path))
}
// pub fn get_tasks(&self) -> Vec<&Task> {
// self.program.lock().unwrap().tasks.iter().collect()
// }
pub fn add_task(&self, task: Task) {
self.program.lock().unwrap().add_task(task);
}
pub fn add_module(&self, module: Module) {
self.program.lock().unwrap().modules.push(module);
}
pub fn log(&self, message: &str) {
self.program.lock().unwrap().logger.log(message);
}
}
impl Clone for ProgramRef {
fn clone(&self) -> Self {
Self {
program: self.program.clone(),
}
}
}
pub struct Module {
pub path: PathBuf,
pub hash: u64,
pub nodes: Vec<Node>,
program: ProgramRef,
}
impl Module {
pub fn new(path: PathBuf, hash: u64, nodes: Vec<Node>, program: ProgramRef) -> Self {
Self {
path,
hash,
nodes,
program,
}
}
pub fn build(path: PathBuf, program: ProgramRef) -> Task {
// spawn a thread that creates the main function and executes the lexer and parser.
let handle = thread::spawn(move || {
let mut module =
Module::new(path.clone(), quick_hash(&path), Vec::new(), program.clone());
let tokens = module.lex();
module.parse(tokens);
module.expand();
module.prepare_dependencies();
module
});
Task { module: handle }
}
fn lex(&mut self) -> Vec<Token> {
if let Ok(path) = self.path.canonicalize() {
self.program.log(&format!(
"{:20} {:20} [{}]",
"Building",
self.get_filename(),
path.display()
));
}
let src = fs::read_to_string(&self.path)
.map_err(|_| AssembleError::InvalidFile(self.path.to_path_buf()))
.unwrap();
let file_hash = quick_hash(&self.path);
self.program
.log(&format!("{:20} {:20}", "Tokenising", self.get_filename()));
lexer::lexer(src, file_hash).unwrap()
}
fn parse(&mut self, tokens: Vec<Token>) {
self.program
.log(&format!("{:20} {:20}", "Parsing", self.get_filename()));
let parsed = Parser::parse_nodes(tokens).unwrap();
self.nodes = parsed;
}
fn expand(&mut self) {
self.program
.log(&format!("{:20} {:20}", "Expanding", self.get_filename()));
let expanded = expand_pseudo_ops(self.nodes.clone(), self.hash).unwrap();
self.nodes = expanded;
}
fn prepare_dependencies(&mut self) {
let nodes = resolve_dependencies(self.nodes.clone(), self.path.parent().unwrap())
.unwrap();
let dependencies = Parser::get_dependencies(&nodes, &self.path).unwrap();
for dep in dependencies {
if self.program.is_registered(&dep) {
// we have already built this module!
continue;
}
self.program.register(&dep);
// create new module
// add the task to the program
let task = Module::build(dep, self.program.clone());
self.program.add_task(task);
}
}
// helper functions
fn get_filename(&self) -> &str {
self.path.file_name().unwrap().to_str().unwrap()
}
}
pub struct Task {
module: JoinHandle<Module>,
}
+11 -2
View File
@@ -3,14 +3,23 @@ use common::{args, prelude::*};
use crate::assembler::model::{Node, Opcode};
use crate::{assembler::AssembleError, expect_token};
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![];
for node in nodes {
println!("node {node}");
instructions.push(build_instruction(node)?);
instructions.push(
build_instruction(node.clone())
.expect(format!("Failed to build instruction: {:?}", node).as_str()),
);
}
println!("------------------------");
log("Compilation Success ✅");
Ok(instructions)
}
+8
View File
@@ -45,6 +45,14 @@ macro_rules! node {
)
};
($symbol: expr, $opcode: expr) => {
$crate::assembler::model::Node::new(
$symbol.clone(),
$opcode.clone(),
Vec::new()
)
};
(@convert_token $token: literal) => {
$crate::assembler::model::Token::Immediate($token)
};
+79 -67
View File
@@ -5,7 +5,8 @@ use std::{
fmt, fs,
hash::{DefaultHasher, Hash, Hasher},
path::{Path, PathBuf},
sync::mpsc,
sync::{Arc, Mutex, mpsc},
thread,
};
use common::prelude::Instruction;
@@ -19,6 +20,7 @@ fn log(message: &str) {
#[macro_use]
pub mod macros;
pub mod assembler;
pub mod codegen;
pub mod expand;
pub mod lexer;
@@ -39,72 +41,95 @@ pub use self::{
use crate::util::logging::{Entry, Logger};
pub struct CompilerEngine {
modules: HashSet<u64>,
program: Program,
logger: Option<Logger>,
receiver: Option<mpsc::Receiver<Entry>>,
result: Option<Result<Vec<Instruction>, AssembleError>>,
result_tx: mpsc::Sender<Result<Vec<Instruction>, AssembleError>>,
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, AssembleError>>>,
is_running: bool,
}
impl CompilerEngine {
pub fn new() -> CompilerEngine {
let (tx, rx) = mpsc::channel::<Entry>();
CompilerEngine {
program: Program::new(),
modules: HashSet::new(),
logger: Some(Logger::new(tx)),
receiver: Some(rx),
result: None,
pub fn new() -> Self {
let (tx, rx) = mpsc::channel();
Self {
result_tx: tx,
result_rx: Some(rx),
is_running: false,
}
}
pub fn get_log() -> Option<Entry> {
None
}
pub fn get_logs() -> Vec<Entry> {
vec![]
}
pub fn is_ready(&self) -> bool {
self.result.is_some()
}
pub fn result(&self) -> Option<Result<Vec<Instruction>, AssembleError>> {
self.result.clone()
}
pub fn assemble(&mut self, src: &Path) -> Result<(), AssembleError> {
let hash = quick_hash(src);
if self.modules.contains(&hash) {
return Ok(());
/// Start the compilation process in a separate thread
pub fn start_compilation(&mut self, src: &Path) {
if self.is_running {
return;
}
prepare_dependency(src, &mut self.modules, &mut self.program)?;
let src = src.to_path_buf();
let tx = self.result_tx.clone();
self.result = Some(self.build());
thread::spawn(move || {
let result = assemble(&src);
tx.send(result).unwrap();
});
Ok(())
self.is_running = true;
}
fn load_module(&mut self, path: &Path) -> Result<(), AssembleError> {
Ok(())
}
fn build(&self) -> Result<Vec<Instruction>, AssembleError> {
let mut nodes = self.program.nodes.clone();
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
let instructions = codegen(nodes)?;
for inst in instructions.iter() {
println!("{inst}");
/// Check if compilation is complete and get the result
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, AssembleError>> {
if !self.is_running {
return None;
}
Ok(instructions)
match self.result_rx.as_ref().unwrap().try_recv() {
Ok(result) => {
self.is_running = false;
Some(result)
}
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
self.is_running = false;
Some(Err(AssembleError::Generic))
}
}
}
/// Block until compilation is complete and return the result
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, AssembleError> {
if !self.is_running {
return Err(AssembleError::Generic);
}
match self.result_rx.take().unwrap().recv() {
Ok(result) => {
self.is_running = false;
result
}
Err(_) => {
self.is_running = false;
Err(AssembleError::Generic)
}
}
}
}
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
let mut modules = HashSet::new();
let mut program = Program::new();
let hash = quick_hash(src);
if modules.contains(&hash) {
return Ok(vec![]);
}
prepare_dependency(src, &mut modules, &mut program)?;
let mut nodes = program.nodes.clone();
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
let instructions = codegen(nodes)?;
Ok(instructions)
}
impl Default for CompilerEngine {
@@ -143,7 +168,8 @@ fn prepare_dependency(
let base_dir = path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
let nodes = resolve_dependencies(parsed, base_dir)?;
let mut nodes = expand_pseudo_ops(parsed, file_hash)?;
nodes = resolve_dependencies(nodes, base_dir)?;
let deps = Parser::get_dependencies(&nodes, path)?;
@@ -151,7 +177,6 @@ fn prepare_dependency(
"{:20} {:20}",
"Expanding PseudoInstructions", filename
));
let mut nodes = expand_pseudo_ops(nodes, file_hash)?;
// add a section instruction
nodes.insert(
@@ -181,19 +206,6 @@ fn prepare_dependency(
Ok(())
}
fn _build(_src: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
Ok(vec![])
}
/// TODO: disassembling functionality
/// - We probably don't need to implement this for a while yet.
/// - This method should recover symbols such as labels and variables from the human
/// written assembly, recognising sequences that are expansions of pseudo-instructions
/// and reversing this to produce near enough the original source code.
pub fn disassemble(_: Vec<Instruction>) -> String {
todo!()
}
#[derive(Debug, Clone)]
pub enum AssembleError {
Generic,
+298
View File
@@ -0,0 +1,298 @@
use std::{
fs,
path::{self, Path},
};
use common::prelude::{Instruction, Register};
use crate::{
assembler::{
Module, Node, Opcode, Symbol, Token, codegen, create_sections, expand_pseudo_ops,
resolve_symbols,
},
node,
};
pub fn build(src: &Path) -> Vec<Instruction> {
let src = fs::read_to_string(src).unwrap();
let mut nodes = parse(src);
// we need to expand pseudoinstructions etc now
nodes = expand_pseudo_ops(nodes, 0).unwrap();
create_sections(&mut nodes).unwrap();
for n in nodes.iter() {
println!("{}", n);
}
resolve_symbols(&mut nodes).unwrap();
codegen(nodes).unwrap()
}
pub fn parse(src: String) -> Vec<Node> {
let stack = Token::Immediate(0x10000);
let acc = Token::Register(Register::Acc);
let rga = Token::Register(Register::Rga);
let bpr = Token::Register(Register::Bpr);
let spr = Token::Register(Register::Spr);
let mut nodes = Vec::<Node>::new();
// Define symbols
let print_start = Symbol {
name: "print".to_string(),
module: Module::Resolved(0),
};
let tokens = lex(src);
let mut id = 0;
let mut idstack = Vec::<u32>::new();
nodes.extend(vec![
// set up a stack
node!(None, Opcode::Lwi, stack, bpr),
node!(None, Opcode::Mov, bpr, spr),
// set up the data pointer
node!(
Some(Symbol {
name: "main".to_string(),
module: Module::Resolved(0)
}),
Opcode::Lwi,
Token::Immediate(0x30000),
rga
),
]);
for (id, tok) in tokens.iter().enumerate() {
match tok {
BfToken::Inc => {
// inc acc
nodes.extend(vec![node!(None, Opcode::Inc, acc)]);
}
BfToken::Dec => {
// dec acc
nodes.extend(vec![node!(None, Opcode::Dec, acc)]);
}
BfToken::IncPtr => {
// stb acc, rga
// add rga, 4
// ldb rga, acc
nodes.extend(vec![
node!(None, Opcode::Stw, acc, rga, 0),
node!(None, Opcode::AddI, rga, 4, rga),
node!(None, Opcode::Ldw, rga, acc, 0),
]);
}
BfToken::DecPtr => {
// stb acc, rga
// sub rga, 4
// ldb rga, acc
nodes.extend(vec![
node!(None, Opcode::Stw, acc, rga, 0),
node!(None, Opcode::SubI, rga, 4, rga),
node!(None, Opcode::Ldw, rga, acc, 0),
]);
}
BfToken::Out => {
// push rga
// call print
// pop zero
nodes.extend(vec![
node!(None, Opcode::Push, acc),
node!(None, Opcode::Call, Token::Symbol(print_start.clone())),
node!(None, Opcode::Pop, Token::Register(Register::Zero)),
]);
}
BfToken::In => {
// Read a byte from input and store it at the current data pointer
// Assuming we have an input function mapped to a specific memory location or I/O port
nodes.extend(vec![
// Read input (assuming input is mapped to memory address 0x40000)
node!(None, Opcode::Ldw, Token::Immediate(0x40000), acc, 0),
// Store the input byte at the current data pointer
]);
}
BfToken::Forward => {
// Start of loop [
let loop_start = format!("loop_start_{}", id);
let loop_end = format!("loop_end_{}", id);
// Push the current position for the matching ]
idstack.push(id as u32);
// Load current cell value and check if zero
nodes.extend(vec![
// Compare with zero
node!(None, Opcode::Cmp, acc, Token::Register(Register::Zero)),
// If zero, jump to end of loop
node!(
None,
Opcode::Jeq,
Token::Symbol(Symbol {
name: loop_end,
module: Module::Resolved(0),
}),
Token::Register(Register::Zero)
),
]);
// Add label for loop start
nodes.push(node!(
Some(Symbol {
name: loop_start,
module: Module::Resolved(0),
}),
Opcode::Nop
));
}
BfToken::Back => {
// End of loop ]
if let Some(start_id) = idstack.pop() {
let loop_start = format!("loop_start_{}", start_id);
let loop_end = format!("loop_end_{}", start_id);
// Jump back to the start of the loop
nodes.extend(vec![
// Compare with zero
node!(None, Opcode::Cmp, acc, Token::Register(Register::Zero)),
// If not zero, jump back to start of loop
node!(
None,
Opcode::Jne,
Token::Symbol(Symbol {
name: loop_start,
module: Module::Resolved(0),
}),
Token::Register(Register::Zero)
),
// Add label for loop end
node!(
Some(Symbol {
name: loop_end,
module: Module::Resolved(0),
}),
Opcode::Nop
),
]);
} else {
// Unmatched closing bracket - could add error handling here
eprintln!("Warning: Unmatched ']' at position {}", id);
}
}
}
}
nodes.push(node!(None, Opcode::Hlt));
insert_lib(&mut nodes);
nodes
}
fn insert_lib(nodes: &mut Vec<Node>) {
let bpr = Token::Register(Register::Bpr);
let spr = Token::Register(Register::Spr);
let rg0 = Token::Register(Register::Rg0);
let rg1 = Token::Register(Register::Rg1);
let print_start = Symbol {
name: "print".to_string(),
module: Module::Resolved(0),
};
let current = Symbol {
name: "current".to_string(),
module: Module::Resolved(0),
};
// set up the program framework.
nodes.extend(vec![
// set display to 0x20000
node!(
None,
Opcode::Dw,
Token::Symbol(current.clone()),
Token::Immediate(0x20000)
),
// print function
// initialisation
node!(Some(print_start.clone()), Opcode::Push, bpr.clone()),
node!(None, Opcode::Mov, spr.clone(), bpr.clone()),
// function body
node!(
None,
Opcode::Ldw,
bpr.clone(),
rg0.clone(),
Token::Immediate(8)
),
node!(
None,
Opcode::Ldw,
Token::Symbol(current.clone()), // Load address of current
rg1.clone(),
Token::Immediate(0)
),
node!(
None,
Opcode::Stb,
rg0.clone(),
rg1.clone(),
Token::Immediate(0)
),
node!(
None,
Opcode::AddI,
rg1.clone(),
Token::Immediate(1),
rg1.clone()
),
// function return according to spec.
node!(
None,
Opcode::Stw,
rg1.clone(),
Token::Symbol(current.clone()), // Store back to current
Token::Immediate(0)
),
node!(None, Opcode::Mov, bpr.clone(), spr.clone()),
node!(None, Opcode::Pop, bpr.clone()),
node!(None, Opcode::Return),
]);
}
enum BfToken {
Inc,
Dec,
IncPtr,
DecPtr,
Out,
In,
Forward,
Back,
}
fn lex(src: String) -> Vec<BfToken> {
src.chars()
.filter_map(|c| match c {
'+' => Some(BfToken::Inc),
'-' => Some(BfToken::Dec),
'>' => Some(BfToken::IncPtr),
'<' => Some(BfToken::DecPtr),
'.' => Some(BfToken::Out),
',' => Some(BfToken::In),
'[' => Some(BfToken::Forward),
']' => Some(BfToken::Back),
_ => None,
})
.collect()
}
fn create_symbol(id: u32) -> Symbol {
Symbol {
name: format!("label_{}", id),
module: Module::Resolved(0),
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
pub mod assembler;
pub mod brainf;
pub mod tooling;
mod util;
pub mod prelude {
pub use crate::assembler::CompilerEngine;
pub use crate::assembler::disassemble;
pub use crate::tooling::project;
}
+31 -34
View File
@@ -1,4 +1,4 @@
use assembler::prelude::*;
use assembler::{brainf, prelude::*};
use std::{fs, io::Write, path::PathBuf};
fn main() {
@@ -6,7 +6,29 @@ fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() == 2 && args[1] == "init" {
assembler::tooling::project::tool_libcreate();
project::tool_libcreate();
std::process::exit(0);
}
if args.len() == 2 && args[1] == "brainf" {
let src = PathBuf::from("brainf.bf");
let result = brainf::build(&src);
let mut file = match fs::File::create("brainf.dsb") {
Err(e) => {
eprintln!("Failed to create output file: {}", e);
std::process::exit(1);
}
Ok(file) => file,
};
for instruction in result {
if let Err(e) = file.write(&instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e);
std::process::exit(1);
}
}
std::process::exit(0);
}
@@ -19,41 +41,16 @@ fn main() {
let output_path = &args[4];
let src = PathBuf::from(input_path);
// Create the output file
let mut output_file = match fs::File::create(output_path) {
Ok(file) => file,
Err(e) => {
eprintln!("Failed to create output file: {}", e);
std::process::exit(1);
}
};
// Initialize the compiler engine
let mut engine = CompilerEngine::new();
let mut compiler = CompilerEngine::new();
compiler.start_compilation(&src);
// Assemble the source file
if let Err(e) = engine.assemble(&src) {
eprintln!("Assembly error: {}", e);
std::process::exit(1);
}
// Or block until done
let result = compiler.wait_for_result().unwrap();
// Build and write the output
match engine.result() {
Some(Ok(instructions)) => {
for instruction in instructions {
if let Err(e) = output_file.write_all(&instruction.encode().to_le_bytes())
{
eprintln!("Failed to write to output file: {}", e);
std::process::exit(1);
}
}
}
Some(Err(e)) => {
eprintln!("Build error: {}", e);
std::process::exit(1);
}
None => {
eprintln!("Build error: No result available");
for instruction in result {
if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e);
std::process::exit(1);
}
}
+63 -50
View File
@@ -1,62 +1,75 @@
#![allow(dead_code)]
#![allow(unused)]
use std::{fmt, sync::mpsc::Sender};
#[allow(dead_code)]
#[derive(Debug)]
pub struct Logger {
pub sender: Sender<Entry>,
}
pub struct Logger {}
impl Logger {
pub fn new(sender: Sender<Entry>) -> Self {
Self { sender }
pub fn new() -> Self {
Self {}
}
pub fn debug<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Debug,
message: message.to_string(),
})
.unwrap();
}
pub fn info<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Info,
message: message.to_string(),
})
.unwrap();
}
pub fn warn<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Warn,
message: message.to_string(),
})
.unwrap();
}
pub fn error<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Error,
message: message.to_string(),
})
.unwrap();
}
pub fn fatal<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Fatal,
message: message.to_string(),
})
.unwrap();
pub fn log(&self, message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
}
// #[derive(Debug)]=
// pub struct Logger {
// pub sender: Sender<Entry>,
// }
// impl Logger {
// pub fn new(sender: Sender<Entry>) -> Self {
// Self { sender }
// }
// pub fn debug<T: fmt::Display>(&self, message: T) {
// self.sender
// .send(Entry {
// etype: EntryType::Debug,
// message: message.to_string(),
// })
// .unwrap();
// }
// pub fn info<T: fmt::Display>(&self, message: T) {
// self.sender
// .send(Entry {
// etype: EntryType::Info,
// message: message.to_string(),
// })
// .unwrap();
// }
// pub fn warn<T: fmt::Display>(&self, message: T) {
// self.sender
// .send(Entry {
// etype: EntryType::Warn,
// message: message.to_string(),
// })
// .unwrap();
// }
// pub fn error<T: fmt::Display>(&self, message: T) {
// self.sender
// .send(Entry {
// etype: EntryType::Error,
// message: message.to_string(),
// })
// .unwrap();
// }
// pub fn fatal<T: fmt::Display>(&self, message: T) {
// self.sender
// .send(Entry {
// etype: EntryType::Fatal,
// message: message.to_string(),
// })
// .unwrap();
// }
// }
pub struct Entry {
etype: EntryType,
pub message: String,