refactored logging / added clap for cli

This commit is contained in:
raphy 2023-05-03 08:40:47 +02:00
parent d17d946fec
commit f66bdb9b75
11 changed files with 130 additions and 47 deletions

View File

@ -6,3 +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.2.5", features = ["derive"] }
log = "0.4.17"
simple_logger = "4.1.0"

View File

@ -1,18 +1,25 @@
/// Type alias to represent a register.
type RegisterMem = String; type RegisterMem = String;
/// Type alias to represent a label used in place of a const.
pub type ConstId = String; pub type ConstId = String;
/// A const can either be a number or a label of some section.
#[derive(Debug)] #[derive(Debug)]
pub enum Const { pub enum Const {
CS(ConstId), CS(ConstId),
C(u8), 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)] #[derive(Debug)]
pub enum Operation { pub enum Operation {
/// No type.
NOP, NOP,
HALT, HALT,
// R type /// R type
ADD(RegisterMem, RegisterMem, RegisterMem), ADD(RegisterMem, RegisterMem, RegisterMem),
SUB(RegisterMem, RegisterMem, RegisterMem), SUB(RegisterMem, RegisterMem, RegisterMem),
AND(RegisterMem, RegisterMem, RegisterMem), AND(RegisterMem, RegisterMem, RegisterMem),
@ -23,11 +30,11 @@ pub enum Operation {
LOAD(RegisterMem, RegisterMem, RegisterMem), LOAD(RegisterMem, RegisterMem, RegisterMem),
STORE(RegisterMem, RegisterMem, RegisterMem), STORE(RegisterMem, RegisterMem, RegisterMem),
// I Type /// I Type
SLI(RegisterMem, Const), SLI(RegisterMem, Const),
ADDI(RegisterMem, Const), ADDI(RegisterMem, Const),
CALL(RegisterMem, Const), CALL(RegisterMem, Const),
// J Type /// J Type
JAL(RegisterMem, RegisterMem, Const), JAL(RegisterMem, RegisterMem, Const),
} }

View File

@ -1,34 +1,48 @@
use super::AST::*; use super::AST::*;
use crate::cpu::get_num; use crate::cpu::get_num;
use log::{trace, warn};
/// 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>
where where
Self: Sized; Self: Sized;
} }
/// Symbol table implemented as a vector.
/// This is a zero-size struct used to implement
/// lookup as an impl.
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct SymbolTable(pub Vec<(String, u16)>); pub struct SymbolTable(pub Vec<(String, u16)>);
impl SymbolTable { impl SymbolTable {
pub fn lookup(&self, query: &str) -> u16 { /// Finds a symbol in the symbol table.
/// Fails if the symbol is not in the symbol table.
pub fn lookup(&self, query: &str) -> Option<u16> {
trace!("Looking up {} in the symbol table.", query);
let SymbolTable(sy) = self; let SymbolTable(sy) = self;
for (name, loc) in sy.into_iter() { for (name, loc) in sy.into_iter() {
if query == (*name) { if query == (*name) {
return *loc; return Some(*loc);
} }
} }
panic!( // panic!(
"Symbol {} not found in symbol table. \nCurrent symbol table:{:?}", // "Symbol {} not found in symbol table. \nCurrent symbol table:{:?}",
query, sy // query, sy
); // );
warn!("Symbol {} not found in symbol table.", query);
return None
} }
} }
impl CodeFormat for u16 { impl CodeFormat for u16 {
fn encode_op(op: &Operation, sy: &SymbolTable, current_pc: u16) -> Option<Self> { fn encode_op(op: &Operation, sy: &SymbolTable, current_pc: u16) -> Option<Self> {
println!("encoding {:?}", op); trace!("encoding {:?}", op);
match op { match op {
Operation::NOP => Some(0b0000000000000000), Operation::NOP => Some(0b0000000000000000),
Operation::HALT => Some(0b1111111111111111), Operation::HALT => Some(0b1111111111111111),
@ -107,7 +121,7 @@ impl CodeFormat for u16 {
Operation::SLI(r1, c) => { Operation::SLI(r1, c) => {
let r1b = get_num(&r1)? as u16; let r1b = get_num(&r1)? as u16;
let cb = match c { let cb = match c {
Const::CS(label) => sy.lookup(&label), Const::CS(label) => sy.lookup(&label)?,
Const::C(n) => (*n) as u16, Const::C(n) => (*n) as u16,
}; };
return Some((0b0110 << 12) + (r1b << 8) + cb); return Some((0b0110 << 12) + (r1b << 8) + cb);
@ -115,7 +129,7 @@ impl CodeFormat for u16 {
Operation::ADDI(r1, c) => { Operation::ADDI(r1, c) => {
let r1b = get_num(&r1)? as u16; let r1b = get_num(&r1)? as u16;
let cb = match c { let cb = match c {
Const::CS(label) => sy.lookup(&label), Const::CS(label) => sy.lookup(&label)?,
Const::C(n) => (*n) as u16, Const::C(n) => (*n) as u16,
}; };
return Some((0b0111 << 12) + (r1b << 8) + cb); return Some((0b0111 << 12) + (r1b << 8) + cb);
@ -123,7 +137,7 @@ impl CodeFormat for u16 {
Operation::CALL(r1, c) => { Operation::CALL(r1, c) => {
let r1b = get_num(&r1)? as u16; let r1b = get_num(&r1)? as u16;
let cb = match c { let cb = match c {
Const::CS(label) => sy.lookup(&label), Const::CS(label) => sy.lookup(&label)?,
Const::C(n) => (*n) as u16, Const::C(n) => (*n) as u16,
}; };
return Some((0b1110 << 12) + (r1b << 8) + cb); return Some((0b1110 << 12) + (r1b << 8) + cb);
@ -132,7 +146,7 @@ impl CodeFormat for u16 {
let r1b = get_num(&r1)? as u16; let r1b = get_num(&r1)? as u16;
let r2b = get_num(&r2)? as u16; let r2b = get_num(&r2)? as u16;
let cb = match c { let cb = match c {
Const::CS(label) => current_pc - sy.lookup(&label), Const::CS(label) => current_pc - sy.lookup(&label)?,
Const::C(n) => (*n) as u16, Const::C(n) => (*n) as u16,
}; };
return Some((0b1010 << 12) + (r1b << 8) + (r2b << 4) + cb); return Some((0b1010 << 12) + (r1b << 8) + (r2b << 4) + cb);

View File

@ -6,16 +6,26 @@ mod tests;
use encoder::CodeFormat; use encoder::CodeFormat;
use encoder::SymbolTable; use encoder::SymbolTable;
use log::{trace, debug};
use parser::Section; use parser::Section;
use crate::loader::unloader::make_string; use crate::loader::unloader::make_string;
use self::parser::SectionContent; use parser::SectionContent;
impl Section { impl Section {
/// Calculates the size, in binary, of a section.
fn get_size(&self) -> usize { fn get_size(&self) -> usize {
match &self.content { match &self.content {
// code is 1 word for instruction.
SectionContent::Code(c) => c.len(), SectionContent::Code(c) => c.len(),
// 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,
// 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();
if c % 2 != 0 { if c % 2 != 0 {
@ -28,15 +38,22 @@ impl Section {
} }
} }
/// Converts a section to binary. Needs symbol table to
/// resolve labels, and to quickly get the address of
/// this own section.
fn to_binary(&self, sy: &SymbolTable) -> Option<Vec<u16>> { fn to_binary(&self, sy: &SymbolTable) -> Option<Vec<u16>> {
let own_address = sy.lookup(&self.name); let own_address = sy.lookup(&self.name)?;
match &self.content { match &self.content {
SectionContent::Code(c) => { SectionContent::Code(c) => {
let mut res = vec![]; let mut res = vec![];
// we keep track of the program counter because we
// need to calculate relative jumps.
let mut pc = own_address; let mut pc = own_address;
for op in c.iter() { for op in c.iter() {
println!("converting {:?}", op); trace!("converting {:?}", op);
res.push(CodeFormat::encode_op(op, sy, pc)?); res.push(CodeFormat::encode_op(op, sy, pc)?);
// pc simply increases by one after each operation.
pc += 1; pc += 1;
} }
return Some(res); return Some(res);
@ -49,6 +66,13 @@ impl Section {
} }
} }
/// Sorts a list of sections.
/// All .text sections containing code are
/// 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.
fn sort_sections(sections: Vec<Section>) -> Option<Vec<Section>> { fn sort_sections(sections: Vec<Section>) -> Option<Vec<Section>> {
// we start with a mock section that we'll just replace. // we start with a mock section that we'll just replace.
let mut res: Vec<Section> = vec![Section { let mut res: Vec<Section> = vec![Section {
@ -73,9 +97,14 @@ fn sort_sections(sections: Vec<Section>) -> Option<Vec<Section>> {
res.append(&mut nocode); res.append(&mut nocode);
// TODO: PANIC WHEN NO MAIN;
return Some(res); return Some(res);
} }
/// Creates symbol from a list of sorted sections.
/// Assumes the sections are already sorted in the
/// desired order.
fn make_symbol_table<'a>(sections: &'a Vec<Section>) -> Option<SymbolTable> { fn make_symbol_table<'a>(sections: &'a Vec<Section>) -> Option<SymbolTable> {
let mut res = vec![]; let mut res = vec![];
let mut pos: u16 = 0; let mut pos: u16 = 0;
@ -88,16 +117,17 @@ fn make_symbol_table<'a>(sections: &'a Vec<Section>) -> Option<SymbolTable> {
return Some(SymbolTable(res)); return Some(SymbolTable(res));
} }
/// Converts a vector of sections into binary.
pub fn to_binary(sections: Vec<Section>) -> Option<Vec<u16>> { pub fn to_binary(sections: Vec<Section>) -> Option<Vec<u16>> {
let sorted = sort_sections(sections)?; let sorted = sort_sections(sections)?;
println!("sorted sections: {:?}", sorted); trace!("sorted sections: {:?}", sorted);
let sy = make_symbol_table(&sorted)?; let sy = make_symbol_table(&sorted)?;
println!("symbol table: {:?}", sy); debug!("symbol table: {:?}", sy);
let k: Vec<Vec<u16>> = sorted let k: Vec<Vec<u16>> = sorted
.iter() .iter()
.map(|x| x.to_binary(&sy)) .map(|x| x.to_binary(&sy))
.collect::<Option<Vec<Vec<u16>>>>()?; .collect::<Option<Vec<Vec<u16>>>>()?;
println!("binary sections: {:?}", k); trace!("binary sections: {:?}", k);
return Some(k.into_iter().flatten().collect()); return Some(k.into_iter().flatten().collect());
} }

View File

@ -1,8 +1,9 @@
use super::AST::{Const, Operation}; use super::AST::{Const, Operation};
use Operation::*; use Operation::*;
type Loc = u16; use log::*;
/// Represents a parsing failure.
#[derive(Debug)] #[derive(Debug)]
pub enum ParseError { pub enum ParseError {
BadSectionHeader, BadSectionHeader,
@ -13,23 +14,20 @@ pub enum ParseError {
} }
/// represents the state of our parser. /// represents the state of our parser.
/// Sadly parsing is stateless,
pub struct Parser { pub struct Parser {
loc: u16, // current number of operations parsed. input: Vec<String>, // input file
symtable: Vec<(String, u16)>, // symbols encountered, position.
pub input: Vec<String>, // input file
} }
impl Parser { impl Parser {
pub fn new(i: String) -> Self { pub fn new(i: String) -> Self {
Parser { Parser {
loc: 0,
symtable: vec![],
input: sanitize(i), input: sanitize(i),
} }
} }
} }
// removes comments and whitespaces, and splits the input in lines. /// removes comments and whitespaces, and splits the input in lines.
fn sanitize(i: String) -> Vec<String> { fn sanitize(i: String) -> Vec<String> {
i.lines() i.lines()
.map(|x| remove_comments(x)) .map(|x| remove_comments(x))
@ -39,6 +37,7 @@ fn sanitize(i: String) -> Vec<String> {
.collect() .collect()
} }
/// Removes comments.
fn remove_comments(i: &str) -> &str { fn remove_comments(i: &str) -> &str {
if let Some(end) = i.find(';') { if let Some(end) = i.find(';') {
return &i[0..end]; return &i[0..end];
@ -105,6 +104,7 @@ fn take_between_test() {
//// SECTION PARSING //// SECTION PARSING
/// Enum to represent possible section content.
#[derive(Debug)] #[derive(Debug)]
pub enum SectionContent { pub enum SectionContent {
Code(Vec<Operation>), Code(Vec<Operation>),
@ -114,13 +114,14 @@ pub enum SectionContent {
use SectionContent::*; use SectionContent::*;
/// Binary file section, as parsed from a .grasm file.
#[derive(Debug)] #[derive(Debug)]
pub struct Section { pub struct Section {
pub name: String, pub name: String,
pub content: SectionContent, pub content: SectionContent,
} }
// A .section has a name and variable content.
impl Parser { impl Parser {
pub fn parse_sections(&mut self) -> Result<Vec<Section>, ParseError> { pub fn parse_sections(&mut self) -> Result<Vec<Section>, ParseError> {
let mut res = vec![]; let mut res = vec![];
@ -128,12 +129,15 @@ impl Parser {
let mut lines = self.input.iter().map(|x| x.as_str()).into_iter(); let mut lines = self.input.iter().map(|x| x.as_str()).into_iter();
while let Some(l) = lines.next() { while let Some(l) = lines.next() {
println!("Examing line: {}", l); debug!("Examining line {}", l);
// are we looking at a section header?
if l.starts_with(".") { if l.starts_with(".") {
let Some((kind, name)) = take_alpha_till(&l[1..], " ") else { let Some((kind, name)) = take_alpha_till(&l[1..], " ") else {
return Err(ParseError::BadSectionHeader); return Err(ParseError::BadSectionHeader);
}; };
// what kind of section?
match kind.as_str() { match kind.as_str() {
"text" => { "text" => {
let s: Vec<&str> = lines let s: Vec<&str> = lines
@ -156,15 +160,19 @@ impl Parser {
} }
"i16" => { "i16" => {
let _s = lines.next(); let _s = lines.next();
todo!();
} }
"u16" => { "u16" => {
let _s = lines.next(); let _s = lines.next();
todo!();
} }
"vi16" => { "vi16" => {
let _s = lines.next(); let _s = lines.next();
todo!();
} }
"vu16" => { "vu16" => {
let _s = lines.next(); let _s = lines.next();
todo!();
} }
_ => { _ => {
return Err(ParseError::UnknownSectionKind); return Err(ParseError::UnknownSectionKind);
@ -187,10 +195,11 @@ fn parse_code(i: &[&str]) -> Result<Vec<Operation>, ParseError> {
return Ok(res); return Ok(res);
} }
/// Parses a single line of code.
fn parse_code_line(i: &str) -> Result<Operation, ParseError> { 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();
println!("current parse code line: {}", i); 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 // no type

View File

@ -2,6 +2,7 @@ use super::registers::Register;
type Constant = i8; // 8 bits max, so it works. type Constant = i8; // 8 bits max, so it works.
// TODO: Use macros and a single reference for the ops.
#[derive(Debug)] #[derive(Debug)]
pub enum OP { pub enum OP {
NOP, NOP,
@ -26,6 +27,7 @@ pub enum OP {
pub use OP::*; pub use OP::*;
/// Decodes a single binary operation.
pub fn decode(op: u16) -> OP { pub fn decode(op: u16) -> OP {
let opcode = op >> 12; let opcode = op >> 12;
let dest = ((op & 0x0F00) >> 8) as Register; let dest = ((op & 0x0F00) >> 8) as Register;
@ -55,6 +57,7 @@ pub fn decode(op: u16) -> OP {
0b1101 => STORE(dest, r1, r2), 0b1101 => STORE(dest, r1, r2),
0b1110 => CALL(dest, c), 0b1110 => CALL(dest, c),
0b1111 => HALT, 0b1111 => HALT,
_ => panic!("Not an operation."), // opcode, by construction, is a binary 4 bits number.
_ => unreachable!(),
}; };
} }

View File

@ -5,13 +5,15 @@ mod sysenv;
mod tests; mod tests;
pub use sysenv::*; pub use sysenv::{IOBuffer, Sys};
pub use registers::*; pub use registers::*;
pub use decoder::OP; pub use decoder::OP;
use ram::Ram; use ram::Ram;
use log::{debug};
#[derive(Debug)] #[derive(Debug)]
pub enum ExecErr { pub enum ExecErr {
InvalidRegister, InvalidRegister,
@ -36,7 +38,6 @@ pub struct CPU<'a, T> {
pub regs: Registers, pub regs: Registers,
pub ram: Ram, pub ram: Ram,
pub env: &'a mut T, pub env: &'a mut T,
// should execution be halted? not sure if to include this or nah
halt: bool, halt: bool,
} }
@ -160,20 +161,26 @@ where
return Ok(()); return Ok(());
} }
/// fetch tne next operation from memory.
/// called by step.
fn fetch(&self) -> CPUResult<OP> { fn fetch(&self) -> CPUResult<OP> {
let binop = self.ram.get(self.regs.pc).ok_or(ExecErr::InvalidPC)?; let binop = self.ram.get(self.regs.pc).ok_or(ExecErr::InvalidPC)?;
println!("binop: {:#018b}", binop); // debug!("fetched binop: {:#018b}", binop);
Ok(decode(binop)) Ok(decode(binop))
} }
/// fetches an operation and runs one clock cycle.
fn step(&mut self) -> CPUResult<()> { fn step(&mut self) -> CPUResult<()> {
let op = self.fetch()?; let op = self.fetch()?;
println!("fetched op: {:?}, pc: {} ", op, self.regs.pc); debug!("fetched op: {:?}, pc: {} ", op, self.regs.pc);
self.execute_op(op) self.execute_op(op)
} }
/// takes binary code as input, puts it at the start of memory, and
/// executes the code. Mainly for testing purposes.
pub fn run_code_raw(&mut self, bin_code: &[Word]) -> CPUResult<()> { pub fn run_code_raw(&mut self, bin_code: &[Word]) -> CPUResult<()> {
self.halt = false; self.halt = false;
// put the code in memory: // put the code in memory:
@ -186,6 +193,7 @@ where
Ok(()) Ok(())
} }
/// Creates a new CPU from an exising environment.
pub fn new(env: &'a mut T) -> Self { pub fn new(env: &'a mut T) -> Self {
CPU { CPU {
regs: Registers::default(), regs: Registers::default(),

View File

@ -1,10 +1,13 @@
use crate::cpu::registers::Word; use crate::cpu::registers::Word;
// TODO: abstract this into a trait and write different implementation
// of RAM, such as a growable cointainer/tree.
/// We'll define our RAM as a static array. /// We'll define our RAM as a static array.
/// The maximum adressable memory is, right now, just 65kbit of memory. /// The maximum adressable memory is, right now, just 65kbit of memory.
// pub const MAX_MEM: usize = 65536; pub const MAX_MEM: usize = 65535;
pub const MAX_MEM: usize = 40; // pub const MAX_MEM: usize = 40;
#[derive(Debug)] #[derive(Debug)]
pub struct Ram { pub struct Ram {

View File

@ -1,5 +1,7 @@
use std::io::stdin; use std::io::stdin;
use log::{debug, info};
use crate::{ use crate::{
cpu::{ram::MAX_MEM, registers::Register}, cpu::{ram::MAX_MEM, registers::Register},
loader::{loader::find_and_read_string, unloader::make_string}, loader::{loader::find_and_read_string, unloader::make_string},
@ -7,13 +9,11 @@ use crate::{
use super::*; use super::*;
// first working environment, we get input from stdin and we write output
// to a string.
//
// using strings to singal errors kinda sucks. // using strings to singal errors kinda sucks.
// TODO: Fix this // TODO: Fix this
/// Rudimentary environment, good for testing.
/// Gets input from stdin and writes output to a string.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct IOBuffer { pub struct IOBuffer {
pub output: String, pub output: String,
@ -21,7 +21,7 @@ pub struct IOBuffer {
impl Sys for IOBuffer { impl Sys for IOBuffer {
fn call(cpu: &mut CPU<IOBuffer>, r: Register, c: Word) -> CPUResult<()> { fn call(cpu: &mut CPU<IOBuffer>, r: Register, c: Word) -> CPUResult<()> {
println!("called: {}", c); debug!("called syscall: {}", c);
match c { match c {
// 0: write an integer to output // 0: write an integer to output
0 => { 0 => {

View File

@ -3,7 +3,7 @@ use std::mem::transmute;
pub mod cpu; pub mod cpu;
pub mod jit; pub mod jit;
pub mod loader; pub mod loader;
// pub mod ; pub mod cli;
pub mod assembler; pub mod assembler;
// //

View File

@ -5,7 +5,13 @@ use dekejit::assembler::to_binary;
use dekejit::cpu::IOBuffer; use dekejit::cpu::IOBuffer;
use dekejit::cpu::CPU; use dekejit::cpu::CPU;
// use simple_logger::SimpleLogger;
fn main() { fn main() {
simple_logger::init_with_level(log::Level::Warn).unwrap();
let args: Vec<String> = args().collect(); let args: Vec<String> = args().collect();
if args.len() < 2 { if args.len() < 2 {
@ -26,7 +32,7 @@ fn main() {
let r = parser.parse_sections().unwrap(); let r = parser.parse_sections().unwrap();
println!("Parsed sections: {:?}", r); // println!("Parsed sections: {:?}", r);
let code = to_binary(r).unwrap(); let code = to_binary(r).unwrap();
@ -44,9 +50,9 @@ fn main() {
// //
let mut cpu = CPU::new(&mut env); let mut cpu = CPU::new(&mut env);
// //
for c in &code[..] { // for c in &code[..] {
println!("{:#018b}", c); // // println!("{:#018b}", c);
} // }
// //
match cpu.run_code_raw(&code) { match cpu.run_code_raw(&code) {
Ok(_) => { Ok(_) => {