Skip to content

Commit

Permalink
.pad and .fillvalue added. Binary generation added.
Browse files Browse the repository at this point in the history
  • Loading branch information
erhanbaris committed Aug 18, 2024
1 parent b3458cd commit cc23df5
Show file tree
Hide file tree
Showing 16 changed files with 350 additions and 69 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.5.16", features = ["derive"] }
log = "0.4.22"
simplelog = "^0.12.2"
strum = "0.26.3"
Expand Down
49 changes: 40 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ Expected output:
0610: fb 60 00
```

## Building
timu6502 builded with latest Rust Language. You have to install Rust Language. After installetion execute ```cargo build --release``` command. The executable will be located under _target/release/_ folder.
Compiler tested under Windows and MacOS operating system. It should work under Linux OS but not yet tested.


## Usage
timu6502 is terminal based compiler and there is no UI yet available. So, basic usage is:
```bash
timu6502asm test.asm --target test.bin
timu6502asm test.asm --binary-dump
timu6502asm test.asm --token-dump
timu6502asm test.asm --token-dump --slient
timu6502asm --help
```
If the compilation operation failed, process exit code will be **1**.

## Data types
Compiler works with primative date types.

Expand Down Expand Up @@ -75,7 +91,7 @@ It takes up different sizes of space depending on the definition. The text must
### .org
Change reference locations. It is not changing where the codes are stored, it is changing jump and branch references.
```assembly
.ORG $0600
.org $0600
.byte $11
```
```
Expand Down Expand Up @@ -136,31 +152,46 @@ Include a file as binary data.
### .warning
Print warning message on compilation time.
```assembly
.warning "timu6502asm compiler works partial"
.warning "timu6502asm compiler works"
```
```
22:05:16 [WARN] timu6502asm compiler works partial
22:05:16 [WARN] timu6502asm compiler works
```

### .fail
The compilation process stops with an error message.
```assembly
.fail "Unsupported platform"
```

### .include
Import another file.
Import another assembly file. All variable defitions will be imported and could be accessible from other files.
```assembly
.include "header.asm"
.include "body.asm"
.include "footer.asm"
```

### .pad
Fill memory from the current address to a specified address. A fill value may also be specified.
```assembly
.pad $0600
```
22:05:16 [WARN] timu6502asm compiler works partial

### .fillvalue
Change the default filler for **.pad**.
```assembly
.fillvalue $ff
```

There are many things to do. Here are the some todos:
- [ ] Case insensitivity
- [ ] Rom file generation
- [X] Case insensitivity
- [X] Binary file generation
- [ ] Decompiler
- [ ] Human friendly prints
- [X] Human friendly prints
- [X] Import different asm files
- [ ] Performance measurement
- [ ] Documentation
- [ ] Deploy on real hardware
- [ ] Deploy on real hardware/emulator
- [ ] (stretch goal) Basic high-level programming language
- [ ] (stretch goal) Basic emulator
30 changes: 22 additions & 8 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::{cell::{Cell, RefCell}, fs::File, io::Read};
use std::{cell::{Cell, RefCell}, fs::File, io::Read, path::PathBuf};

use log::info;
#[cfg(not(test))]
use log::{info, warn}; // Use log crate when building application

#[cfg(test)]
use std::{println as info, println as warn}; // Workaround to use prinltn! for logs.
use thiserror::Error;

use crate::{context::Context, directive::{DirectiveEnum, DirectiveType, DirectiveValue, SYSTEM_DIRECTIVES}, opcode::{ModeType, BRANCH_INSTS, INSTS_SIZE, JUMP_INSTS}, parser::{Parser, Token, TokenType}, tool::print_error};
Expand Down Expand Up @@ -297,23 +301,28 @@ impl AstGenerator {

fn process_include(&self, context: &Context, token_index: usize) -> Result<(), AstGeneratorError> {
let include_asm = self.include_asm.replace(None);
let mut file_path = PathBuf::new();

if let Some(item) = include_asm {
let file_path = match item {
DirectiveValue::String(name) => name,
match item {
DirectiveValue::String(name) => file_path.push(name),
_ => return Err(AstGeneratorError::syntax_issue(context, token_index, "Path expected as a string".to_string()))
};

let mut tokens = context.tokens.borrow_mut();
let token = &tokens[token_index];
let path = context.add_file(token.file_id, file_path.to_string());
let path = context.add_file(token.file_id, file_path);

info!("Importing {:?}", &path.as_os_str());
if !context.silent {
info!("Importing {:?}", &path.as_os_str());
}

let mut file = File::open(&path)?;


let mut code = Vec::new();
file.read_to_end(&mut code)?;
context.code_files.borrow_mut()[context.last_file_id()].data = code.clone();

code.push(b'\n'); // Add new lines to end of the code file

Expand Down Expand Up @@ -458,13 +467,14 @@ impl AstGenerator {
// Branch inst
self.eat_space(context)?;
let text = self.eat_text(context)?;
context.add_ast(token_index,Ast::InstrBranch(positon, text));
context.add_ast(token_index, Ast::InstrBranch(positon, text));
}

else if JUMP_INSTS.contains(&positon) {
// Jump inst
self.eat_space(context)?;
let index = self.index.get();

if let Ok((number, mode)) = self.try_parse_number(context) {
context.add_ast(token_index, Ast::Instr(positon, number, mode));
return Ok(())
Expand Down Expand Up @@ -534,7 +544,11 @@ impl AstGenerator {
Err(error) => {
let tokens = context.tokens.borrow();
let token = &tokens[self.index.get() - 1];
print_error(&context.target, &error, token.line, token.column, token.end);

if !context.silent {
let code_file = &context.code_files.borrow()[token.file_id];
print_error(&code_file.data, &error, token.line, token.column, token.end);
}
Err(error)
}
}
Expand Down
58 changes: 54 additions & 4 deletions src/code_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use std::{collections::HashMap, str::Utf8Error};
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use log::{info, warn};
#[cfg(not(test))]
use log::{info, warn}; // Use log crate when building application

#[cfg(test)]
use std::{println as info, println as warn}; // Workaround to use prinltn! for logs.
use thiserror::Error;

use crate::context::Context;
Expand All @@ -28,15 +32,19 @@ pub enum CodeGeneratorError {
#[error("Text convertion issue ({0})")]
Utf8Error(#[from] Utf8Error),
#[error("Expected {0}")]
ExpectedThis(&'static str)
ExpectedThis(&'static str),
#[error("{0}")]
ProgramFailed(String)
}

#[derive(Debug)]
pub struct CodeGenerator {
pub index: usize,
pub size: usize,
pub silent: bool,

pub start_point: u16,
pub fillvalue : u8,
pub branches: HashMap<String, usize>,
pub unresolved_branches: Vec<(String, usize, usize)>,
pub unresolved_jumps: Vec<(String, usize, usize)>
Expand All @@ -47,7 +55,9 @@ impl CodeGenerator {
Self {
index: 0,
size: 0,
silent: false,
start_point: Default::default(),
fillvalue: 0x00,
branches: Default::default(),
unresolved_branches: Default::default(),
unresolved_jumps: Default::default(),
Expand Down Expand Up @@ -258,8 +268,42 @@ impl CodeGenerator {
_ => return Err(CodeGeneratorError::ExpectedThis("string"))
};
}

if !self.silent {
warn!("{}", message);
}
Ok(())
}

fn directive_fail(&mut self, values: &[DirectiveValue]) -> Result<(), CodeGeneratorError> {
let mut message = String::new();

for value in values.iter() {
match value {
DirectiveValue::String(string) => message += &string[..],
DirectiveValue::Word(word) => message += &format!("0x{:02X}", word),
DirectiveValue::Byte(byte) => message += &format!("0x{:02X}", byte),
_ => return Err(CodeGeneratorError::ExpectedThis("string"))
};
}
Err(CodeGeneratorError::ProgramFailed(message))
}

fn directive_pad(&mut self, target: &mut Vec<u8>, values: &[DirectiveValue]) -> Result<(), CodeGeneratorError> {
let address = match &values[0] {
DirectiveValue::Word(address) => *address,
_ => return Err(CodeGeneratorError::ExpectedThis("word"))
};

warn!("{}", message);
for _ in 0..(address as usize-target.len()) {
target.push(self.fillvalue);
}

Ok(())
}

fn directive_fillvalue(&mut self, values: &[DirectiveValue]) -> Result<(), CodeGeneratorError> {
self.fillvalue = values[0].get_byte()?;
Ok(())
}

Expand All @@ -272,7 +316,10 @@ impl CodeGenerator {
DirectiveEnum::Ascii => self.directive_ascii(target, values, false)?,
DirectiveEnum::Asciiz => self.directive_ascii(target, values, true)?,
DirectiveEnum::Warning => self.directive_warning(values)?,
DirectiveEnum::Fail => self.directive_fail(values)?,
DirectiveEnum::Include => (),
DirectiveEnum::Pad => self.directive_pad(target, values)?,
DirectiveEnum::Fillvalue => self.directive_fillvalue(values)?,
};
Ok(())
}
Expand Down Expand Up @@ -309,7 +356,10 @@ impl CodeGenerator {
Err(error) => {
let asts = context.asts.borrow();
let ast = &asts[self.index - 1];
print_error(&context.target, &error, ast.line, ast.column, ast.end);
if !context.silent {
let code_file = &context.code_files.borrow()[0];
print_error(&code_file.data, &error, ast.line, ast.column, ast.end);
}
Err(error)
}
}
Expand Down
21 changes: 17 additions & 4 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{cell::RefCell, collections::HashMap, path::PathBuf};
use std::{cell::RefCell, collections::HashMap, default, path::PathBuf};

use crate::{ast::{Ast, AstInfo}, directive::DirectiveValue, parser::TokenInfo};

Expand All @@ -9,7 +9,16 @@ pub struct Context {
pub asts: RefCell<Vec<AstInfo>>,
pub references: RefCell<HashMap<String, Vec<DirectiveValue>>>,
pub files: RefCell<Vec<PathBuf>>,
pub work_directory: PathBuf
pub work_directory: PathBuf,
pub silent: bool,
pub code_files: RefCell<Vec<CodeFile>>
}

#[derive(Debug)]
pub struct CodeFile {
pub path: PathBuf,
pub includes: Vec<PathBuf>,
pub data: Vec<u8>
}

impl Context {
Expand All @@ -26,8 +35,9 @@ impl Context {
self.asts.borrow_mut().push(info);
}

pub fn add_file(&self, base_file_id: usize, file: String) -> PathBuf {
pub fn add_file(&self, base_file_id: usize, file: PathBuf) -> PathBuf {
let mut files = self.files.borrow_mut();
let mut code_files = self.code_files.borrow_mut();

let path = match files.get(base_file_id) {
Some(path) => path.parent().map(|parent| parent.to_owned()),
Expand All @@ -40,6 +50,7 @@ impl Context {
};

files.push(full_file_path.clone());
code_files.push(CodeFile { path: full_file_path.clone(), includes: Vec::new(), data: Vec::new() });
full_file_path
}

Expand Down Expand Up @@ -79,7 +90,9 @@ impl Default for Context {
tokens: Default::default(),
asts: Default::default(),
references: Default::default(),
files: Default::default()
files: Default::default(),
silent: false,
code_files: Default::default()
}
}
}
36 changes: 25 additions & 11 deletions src/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ pub enum DirectiveEnum {
Ascii,
Asciiz,
Warning,
Include
Fail,
Include,
Pad,
Fillvalue
}

#[derive(Debug, PartialEq, Clone)]
Expand All @@ -32,6 +35,14 @@ impl DirectiveValue {
_ => Err(CodeGeneratorError::ExpectedThis("Word information"))
}
}

pub fn get_byte(&self) -> Result<u8, CodeGeneratorError> {

match self {
DirectiveValue::Byte(number) => Ok(*number),
_ => Err(CodeGeneratorError::ExpectedThis("Byte information"))
}
}
}

#[derive(Debug, PartialEq, Copy, Clone)]
Expand All @@ -50,14 +61,17 @@ pub struct DirectiveInfo {
}

pub const SYSTEM_DIRECTIVES: &[DirectiveInfo] = &[
DirectiveInfo { name: "BYTE", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] },
DirectiveInfo { name: "DB", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] },
DirectiveInfo { name: "WORD", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] },
DirectiveInfo { name: "DW", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] },
DirectiveInfo { name: "ORG", directive: DirectiveEnum::Org, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Word] },
DirectiveInfo { name: "INCBIN", directive: DirectiveEnum::Incbin, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "ASCII", directive: DirectiveEnum::Ascii, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "ASCIIZ", directive: DirectiveEnum::Asciiz, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "WARNING", directive: DirectiveEnum::Warning, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String, DirectiveType::Word, DirectiveType::Byte] },
DirectiveInfo { name: "INCLUDE", directive: DirectiveEnum::Include, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "BYTE", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] },
DirectiveInfo { name: "DB", directive: DirectiveEnum::Byte, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::String] },
DirectiveInfo { name: "WORD", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] },
DirectiveInfo { name: "DW", directive: DirectiveEnum::Word, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::Byte, DirectiveType::Word] },
DirectiveInfo { name: "ORG", directive: DirectiveEnum::Org, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Word] },
DirectiveInfo { name: "INCBIN", directive: DirectiveEnum::Incbin, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "ASCII", directive: DirectiveEnum::Ascii, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "ASCIIZ", directive: DirectiveEnum::Asciiz, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "WARNING", directive: DirectiveEnum::Warning, size: DirectiveVariableSize::Min(1), values: &[DirectiveType::String, DirectiveType::Word, DirectiveType::Byte] },
DirectiveInfo { name: "FAIL", directive: DirectiveEnum::Fail , size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String, DirectiveType::Word, DirectiveType::Byte] },
DirectiveInfo { name: "INCLUDE", directive: DirectiveEnum::Include, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::String] },
DirectiveInfo { name: "PAD", directive: DirectiveEnum::Pad, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Word] },
DirectiveInfo { name: "FILLVALUE", directive: DirectiveEnum::Fillvalue, size: DirectiveVariableSize::Length(1), values: &[DirectiveType::Byte] },
];
Loading

0 comments on commit cc23df5

Please sign in to comment.