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
[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 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
/// R type
ADD(RegisterMem, RegisterMem, RegisterMem),
SUB(RegisterMem, RegisterMem, RegisterMem),
AND(RegisterMem, RegisterMem, RegisterMem),
@ -23,11 +30,11 @@ pub enum Operation {
LOAD(RegisterMem, RegisterMem, RegisterMem),
STORE(RegisterMem, RegisterMem, RegisterMem),
// I Type
/// I Type
SLI(RegisterMem, Const),
ADDI(RegisterMem, Const),
CALL(RegisterMem, Const),
// J Type
/// J Type
JAL(RegisterMem, RegisterMem, Const),
}

View File

@ -1,34 +1,48 @@
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>
where
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)]
pub struct SymbolTable(pub Vec<(String, u16)>);
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;
for (name, loc) in sy.into_iter() {
if query == (*name) {
return *loc;
return Some(*loc);
}
}
panic!(
"Symbol {} not found in symbol table. \nCurrent symbol table:{:?}",
query, sy
);
// panic!(
// "Symbol {} not found in symbol table. \nCurrent symbol table:{:?}",
// query, sy
// );
warn!("Symbol {} not found in symbol table.", query);
return None
}
}
impl CodeFormat for u16 {
fn encode_op(op: &Operation, sy: &SymbolTable, current_pc: u16) -> Option<Self> {
println!("encoding {:?}", op);
trace!("encoding {:?}", op);
match op {
Operation::NOP => Some(0b0000000000000000),
Operation::HALT => Some(0b1111111111111111),
@ -107,7 +121,7 @@ impl CodeFormat for u16 {
Operation::SLI(r1, c) => {
let r1b = get_num(&r1)? as u16;
let cb = match c {
Const::CS(label) => sy.lookup(&label),
Const::CS(label) => sy.lookup(&label)?,
Const::C(n) => (*n) as u16,
};
return Some((0b0110 << 12) + (r1b << 8) + cb);
@ -115,7 +129,7 @@ impl CodeFormat for u16 {
Operation::ADDI(r1, c) => {
let r1b = get_num(&r1)? as u16;
let cb = match c {
Const::CS(label) => sy.lookup(&label),
Const::CS(label) => sy.lookup(&label)?,
Const::C(n) => (*n) as u16,
};
return Some((0b0111 << 12) + (r1b << 8) + cb);
@ -123,7 +137,7 @@ impl CodeFormat for u16 {
Operation::CALL(r1, c) => {
let r1b = get_num(&r1)? as u16;
let cb = match c {
Const::CS(label) => sy.lookup(&label),
Const::CS(label) => sy.lookup(&label)?,
Const::C(n) => (*n) as u16,
};
return Some((0b1110 << 12) + (r1b << 8) + cb);
@ -132,7 +146,7 @@ impl CodeFormat for u16 {
let r1b = get_num(&r1)? as u16;
let r2b = get_num(&r2)? as u16;
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,
};
return Some((0b1010 << 12) + (r1b << 8) + (r2b << 4) + cb);

View File

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

View File

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

View File

@ -2,6 +2,7 @@ use super::registers::Register;
type Constant = i8; // 8 bits max, so it works.
// TODO: Use macros and a single reference for the ops.
#[derive(Debug)]
pub enum OP {
NOP,
@ -26,6 +27,7 @@ pub enum OP {
pub use OP::*;
/// Decodes a single binary operation.
pub fn decode(op: u16) -> OP {
let opcode = op >> 12;
let dest = ((op & 0x0F00) >> 8) as Register;
@ -55,6 +57,7 @@ pub fn decode(op: u16) -> OP {
0b1101 => STORE(dest, r1, r2),
0b1110 => CALL(dest, c),
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;
pub use sysenv::*;
pub use sysenv::{IOBuffer, Sys};
pub use registers::*;
pub use decoder::OP;
use ram::Ram;
use log::{debug};
#[derive(Debug)]
pub enum ExecErr {
InvalidRegister,
@ -36,7 +38,6 @@ pub struct CPU<'a, T> {
pub regs: Registers,
pub ram: Ram,
pub env: &'a mut T,
// should execution be halted? not sure if to include this or nah
halt: bool,
}
@ -160,20 +161,26 @@ where
return Ok(());
}
/// fetch tne next operation from memory.
/// called by step.
fn fetch(&self) -> CPUResult<OP> {
let binop = self.ram.get(self.regs.pc).ok_or(ExecErr::InvalidPC)?;
println!("binop: {:#018b}", binop);
// debug!("fetched binop: {:#018b}", binop);
Ok(decode(binop))
}
/// fetches an operation and runs one clock cycle.
fn step(&mut self) -> CPUResult<()> {
let op = self.fetch()?;
println!("fetched op: {:?}, pc: {} ", op, self.regs.pc);
debug!("fetched op: {:?}, pc: {} ", op, self.regs.pc);
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<()> {
self.halt = false;
// put the code in memory:
@ -186,6 +193,7 @@ where
Ok(())
}
/// Creates a new CPU from an exising environment.
pub fn new(env: &'a mut T) -> Self {
CPU {
regs: Registers::default(),

View File

@ -1,10 +1,13 @@
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.
/// The maximum adressable memory is, right now, just 65kbit of memory.
// pub const MAX_MEM: usize = 65536;
pub const MAX_MEM: usize = 40;
pub const MAX_MEM: usize = 65535;
// pub const MAX_MEM: usize = 40;
#[derive(Debug)]
pub struct Ram {

View File

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

View File

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

View File

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