Update after a long time!

This commit is contained in:
Dominic Grimm 2023-03-23 17:59:10 +01:00
parent 2bc7ee5f42
commit d6f7a51e11
Signed by: dergrimm
GPG key ID: 12EFFCAEA9E620BF
32 changed files with 3005 additions and 117 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
examples/*.bin

43
Cargo.lock generated
View file

@ -231,10 +231,22 @@ dependencies = [
"rand",
"rhexdump",
"rust-embed",
"sdl2",
"unescape",
]
[[package]]
name = "henceforth"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"hence",
"indexmap",
"itertools",
"lazy_static",
"parse_int",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -468,29 +480,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "sdl2"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
dependencies = [
"bitflags",
"lazy_static",
"libc",
"sdl2-sys",
]
[[package]]
name = "sdl2-sys"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
dependencies = [
"cfg-if",
"libc",
"version-compare",
]
[[package]]
name = "sha2"
version = "0.10.6"
@ -568,12 +557,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.4"

View file

@ -1,2 +1,2 @@
[workspace]
members = ["hence"]
members = ["hence", "henceforth"]

56
examples/henceforth.asm Normal file
View file

@ -0,0 +1,56 @@
.include "$lib/core.asm"
.include "$lib/std.asm"
.include "$lib/main.asm"
.define HENCEFORTH_SOURCE_MAX_LEN, 128
.define HENCEFORTH_SOURCE_START, CORE_MEM_MEM
.define HENCEFORTH_SOURCE_END, (HENCEFORTH_SOURCE_START + HENCEFORTH_SOURCE_MAX_LEN)
.define HENCEFORTH_SOURCE_LEN, (HENCEFORTH_SOURCE_END + 1)
.jump_main
.def_main
ts HENCEFORTH_SOURCE_START
tlr CORE_REG_B
loop:
; get key press
ts CORE_MEM_KEY
get
tlr CORE_REG_A
; echo
ts CORE_MEM_CHR
set
; store in source
tsr CORE_REG_B
set
; increment pointer
ts 1
tlr CORE_REG_A
ts CORE_ALU_ADD
alu
tlr CORE_REG_B
; jump to loop start or not
; backup
tsr CORE_REG_B
tlr CORE_REG_C
; check
ts HENCEFORTH_SOURCE_END
tlr CORE_REG_A
ts CORE_ALU_GT
alu
tlr CORE_REG_A
; restore
tsr CORE_REG_C
tlr CORE_REG_B
; jump
ts loop
tlrc CORE_REG_PC
.std_stop

View file

@ -5,5 +5,4 @@
.jump_main
main: .main main
.std_stop

Binary file not shown.

68
examples/testforth.asm Normal file
View file

@ -0,0 +1,68 @@
.include "$lib/core.asm"
.include "$lib/std.asm"
.include "$lib/main.asm"
.define MEM_LOOP_I, CORE_MEM_MEM
.define MEM_LOOP_J, (MEM_LOOP_I + 1)
.define MEM_CALL_STACK_LEN, 16
.define MEM_CALL_STACK_PTR, (MEM_LOOP_J + 1)
.define MEM_CALL_STACK_END, (MEM_CALL_STACK_PTR + MEM_CALL_STACK_LEN)
.define MEM_ALLOC_PTR, MEM_CALL_STACK_END
.macro stack_transfer_alu
.std_ld
tlr CORE_REG_B
.std_ld
tlr CORE_REG_A
.endmacro
.macro call_stack_jump, call_stack_jump_arg_0_label, call_stack_jump_arg_1_offset
.std_rset CORE_REG_C, call_stack_jump_arg_0_label
.std_rset CORE_REG_D, (call_stack_jump_arg_1_offset + 7)
ts call_stack_jump
tlr CORE_REG_PC
.endmacro
.macro return_call_stack_jump
.std_jump return_call_stack_jump
.endmacro
.std_rset CORE_REG_A, MEM_CALL_STACK_PTR
.std_set MEM_CALL_STACK_PTR
.std_rset CORE_REG_A, (MEM_ALLOC_PTR + 1)
.std_set MEM_ALLOC_PTR
.jump_main
call_stack_jump:
.std_get MEM_CALL_STACK_PTR
tlr CORE_REG_A
.std_rset CORE_REG_B, 1
.std_alu CORE_ALU_ADD
tlr CORE_REG_A
tlr CORE_REG_B
.std_set MEM_CALL_STACK_PTR
tsr CORE_REG_D
tlr CORE_REG_A
tsr CORE_REG_B
set
tsr CORE_REG_C
tlr CORE_REG_PC
return_call_stack_jump:
.std_get MEM_CALL_STACK_PTR
tlr CORE_REG_A
tlr CORE_REG_C
.std_rset CORE_REG_B, 1
.std_alu CORE_ALU_SUB
tlr CORE_REG_A
.std_set MEM_CALL_STACK_PTR
tsr CORE_REG_C
get
tlr CORE_REG_PC
words_0:
; word: "test"
push 40
push 2
.stack_transfer_alu
.std_alu CORE_ALU_ADD
tls
.return_call_stack_jump
main:
.main main
.call_stack_jump words_0, OFFSET
dbg
.call_stack_jump words_0, OFFSET
.std_stop

3
examples/testforth.fth Normal file
View file

@ -0,0 +1,3 @@
: test 40 2 + ;
test debug test

View file

@ -18,4 +18,3 @@ anyhow = { version = "1.0.62", features = ["backtrace"] }
rust-embed = "6.4.0"
unescape = "0.1.0"
parse_int = "0.6.0"
sdl2 = "0.35"

View file

@ -1,52 +1,52 @@
; hence core lib
.define NULL, 0x0000
.define VOID, NULL
.define NULL, 0
.define VOID, NULL
.define FALSE, NULL
.define TRUE, 0x0001
.define FALSE, NULL
.define TRUE, 1
.define CORE_U8_MAX, 0x00ff
.define CORE_U16_MAX, 0xffff
.define CORE_KB, 1024
.define CORE_U8_MAX, 0x00ff
.define CORE_U16_MAX, 0xffff
.define CORE_MEM_PRG, (0 * CORE_KB)
.define CORE_MEM_PRG_END, (32 * CORE_KB)
.define CORE_MEM_ST, CORE_MEM_PRG_END
.define CORE_MEM_MEM, (40 * CORE_KB)
.define CORE_MEM_MEM_END, (48 * CORE_KB)
.define CORE_MEM_OUT, CORE_MEM_MEM_END
.define CORE_MEM_CHR, (CORE_MEM_MEM_END + 1)
.define CORE_MEM_KEY, (CORE_MEM_MEM_END + 2)
.define CORE_KB, 1024
.define CORE_REG_PC, 0x0
.define CORE_REG_OPC, 0x1
.define CORE_REG_ARG, 0x2
.define CORE_REG_S, 0x3
.define CORE_REG_SP, 0x4
.define CORE_REG_A, 0x5
.define CORE_REG_B, 0x6
.define CORE_REG_C, 0x7
.define CORE_REG_D, 0x8
.define CORE_MEM_PRG, (0 * CORE_KB)
.define CORE_MEM_PRG_END, (32 * CORE_KB)
.define CORE_MEM_ST, CORE_MEM_PRG_END
.define CORE_MEM_MEM, (40 * CORE_KB)
.define CORE_MEM_MEM_END, (48 * CORE_KB)
.define CORE_MEM_OUT, CORE_MEM_MEM_END
.define CORE_MEM_CHR, (CORE_MEM_MEM_END + 1)
.define CORE_MEM_KEY, (CORE_MEM_MEM_END + 2)
.define CORE_ALU_NOT, 0x00
.define CORE_ALU_AND, 0x01
.define CORE_ALU_OR, 0x02
.define CORE_ALU_XOR, 0x03
.define CORE_ALU_LSH, 0x04
.define CORE_ALU_RSH, 0x05
.define CORE_ALU_ADD, 0x06
.define CORE_ALU_SUB, 0x07
.define CORE_ALU_MUL, 0x08
.define CORE_ALU_DIV, 0x09
.define CORE_ALU_CMP, 0x0a
.define CORE_ALU_EQ, 0x0b
.define CORE_ALU_LT, 0x0c
.define CORE_ALU_GT, 0x0d
.define CORE_ALU_LEQ, 0x0e
.define CORE_ALU_GEQ, 0x0f
.define CORE_ALU_BOL, 0x10
.define CORE_ALU_INV, 0x11
.define CORE_ALU_RND, 0x12
.define CORE_ALU_TME, 0x13
.define CORE_REG_PC, 0x0
.define CORE_REG_OPC, 0x1
.define CORE_REG_ARG, 0x2
.define CORE_REG_S, 0x3
.define CORE_REG_SP, 0x4
.define CORE_REG_A, 0x5
.define CORE_REG_B, 0x6
.define CORE_REG_C, 0x7
.define CORE_REG_D, 0x8
.define CORE_ALU_NOT, 0x00
.define CORE_ALU_AND, 0x01
.define CORE_ALU_OR, 0x02
.define CORE_ALU_XOR, 0x03
.define CORE_ALU_LSH, 0x04
.define CORE_ALU_RSH, 0x05
.define CORE_ALU_ADD, 0x06
.define CORE_ALU_SUB, 0x07
.define CORE_ALU_MUL, 0x08
.define CORE_ALU_DIV, 0x09
.define CORE_ALU_CMP, 0x0a
.define CORE_ALU_EQ, 0x0b
.define CORE_ALU_LT, 0x0c
.define CORE_ALU_GT, 0x0d
.define CORE_ALU_LEQ, 0x0e
.define CORE_ALU_GEQ, 0x0f
.define CORE_ALU_BOL, 0x10
.define CORE_ALU_INV, 0x11
.define CORE_ALU_RND, 0x12
.define CORE_ALU_TME, 0x13

View file

@ -15,3 +15,7 @@
tlr CORE_REG_PC
.org main
.endmacro
.macro def_main
main: .main main
.endmacro

View file

@ -23,7 +23,9 @@ pub trait ByteResolvable<T> {
fn resolve_string(&self, data: &mut T) -> Result<String>;
}
pub const PROGRAM_SIZE: u16 = 32 * 1024;
pub const KB: u16 = 1024;
pub const PROGRAM_SIZE: u16 = 32 * KB;
pub type Program = [u8; PROGRAM_SIZE as usize];
#[derive(RustEmbed)]

View file

@ -6,7 +6,7 @@ use std::io::{self, Write};
use std::time::Instant;
#[derive(Debug)]
pub struct Data {
pub struct Emulator {
program: [u8; 32 * 1024],
pub tmp: u16,
pub reg_pc: u16,
@ -24,7 +24,7 @@ pub struct Data {
term: console::Term,
}
impl Data {
impl Emulator {
pub fn new(program: [u8; 32 * 1024]) -> Self {
Self {
program,
@ -135,7 +135,7 @@ impl Data {
}
}
fn alu(&mut self, operation: u8) -> Result<()> {
pub fn alu(&mut self, operation: u8) -> Result<()> {
match operation {
0x00 => {
self.tmp = !self.reg_a;
@ -224,76 +224,86 @@ impl Data {
Ok(())
}
}
pub fn emulate(data: &mut Data) -> Result<()> {
while data.reg_pc != 0xffff {
data.reg_opc = data.get_memory(data.reg_pc)? as u8;
data.reg_pc = data.reg_pc.wrapping_add(1);
pub fn cycle(&mut self) -> Result<()> {
self.reg_opc = self.get_memory(self.reg_pc)? as u8;
self.reg_pc = self.reg_pc.wrapping_add(1);
if data.reg_opc >> 7 == 1 {
data.reg_opc &= 0b0111111;
data.reg_arg = 0;
if self.reg_opc >> 7 == 1 {
self.reg_opc &= 0b0111111;
self.reg_arg = 0;
} else {
data.reg_arg = data.get_memory(data.reg_pc)? << 8;
data.reg_pc = data.reg_pc.wrapping_add(1);
self.reg_arg = self.get_memory(self.reg_pc)? << 8;
self.reg_pc = self.reg_pc.wrapping_add(1);
data.reg_arg |= data.get_memory(data.reg_pc)?;
data.reg_pc = data.reg_pc.wrapping_add(1);
self.reg_arg |= self.get_memory(self.reg_pc)?;
self.reg_pc = self.reg_pc.wrapping_add(1);
}
match data.reg_opc {
match self.reg_opc {
0x00 => {}
0x01 => {
data.tmp = data.reg_arg;
data.memory[data.reg_sp as usize] = data.reg_arg;
data.reg_sp = data.reg_sp.wrapping_add(1);
self.tmp = self.reg_arg;
self.memory[self.reg_sp as usize] = self.reg_arg;
self.reg_sp = self.reg_sp.wrapping_add(1);
}
0x02 => {
data.reg_sp = data.reg_sp.wrapping_sub(1);
self.reg_sp = self.reg_sp.wrapping_sub(1);
}
0x03 => {
data.tmp = data.reg_arg;
self.tmp = self.reg_arg;
}
0x04 => {
data.tmp = data.get_register(data.reg_arg as u8);
self.tmp = self.get_register(self.reg_arg as u8);
}
0x05 => {
data.tmp = data.memory[data.reg_sp.wrapping_sub(1) as usize];
self.tmp = self.memory[self.reg_sp.wrapping_sub(1) as usize];
}
0x06 => {
data.set_register(data.reg_arg as u8, data.tmp);
self.set_register(self.reg_arg as u8, self.tmp);
}
0x07 => {
if data.reg_a & 1 == 1 {
data.set_register(data.reg_arg as u8, data.tmp);
if self.reg_a & 1 == 1 {
self.set_register(self.reg_arg as u8, self.tmp);
}
}
0x08 => {
data.memory[data.reg_sp as usize] = data.tmp;
data.reg_sp = data.reg_sp.wrapping_add(1);
self.memory[self.reg_sp as usize] = self.tmp;
self.reg_sp = self.reg_sp.wrapping_add(1);
}
0x09 => {
println!(
"[DEBUG]: [{}]",
data.memory.iter().take(data.reg_sp as usize).join(", ")
self.memory.iter().take(self.reg_sp as usize).join(", ")
);
print!("Press enter to continue execution...",);
io::stdout().flush()?;
data.term.read_line()?;
self.term.read_line()?;
}
0x0a => {
data.alu(data.tmp as u8)?;
self.alu(self.tmp as u8)?;
}
0x0b => {
data.tmp = data.get_memory(data.tmp)?;
self.tmp = self.get_memory(self.tmp)?;
}
0x0c => {
data.set_memory(data.tmp, data.reg_a)?;
self.set_memory(self.tmp, self.reg_a)?;
}
_ => bail!("Invalid opcode: 0x{}", radix(data.reg_opc, 16)),
_ => bail!("Invalid opcode: 0x{}", radix(self.reg_opc, 16)),
}
Ok(())
}
Ok(())
pub fn is_stopped(&self) -> bool {
self.reg_pc == 0xffff
}
pub fn run(&mut self) -> Result<()> {
while !self.is_stopped() {
self.cycle()?;
}
Ok(())
}
}

View file

@ -95,9 +95,7 @@ fn main() -> Result<()> {
}
let program: [u8; 32 * 1024] = (&program_buf[0..(32 * 1024)]).try_into()?;
let mut data = emulator::Data::new(program);
emulator::emulate(&mut data)?;
emulator::Emulator::new(program).run()?;
Ok(())
}

2
henceforth/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
examples/*.asm
examples/*.bin

17
henceforth/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "henceforth"
version = "0.1.0"
edition = "2021"
authors = ["Dominic Grimm <dominic@dergrimm.net>"]
repository = "https://git.dergrimm.net/dergrimm/hence.git"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hence = { path = "../hence" }
clap = { version = "3.2.16", features = ["derive"] }
anyhow = { version = "1.0.62", features = ["backtrace"] }
itertools = "0.10.2"
parse_int = "0.6.0"
indexmap = "1.9.1"
lazy_static = "1.4.0"

259
henceforth/src/compiler.rs Normal file
View file

@ -0,0 +1,259 @@
use anyhow::{bail, Context, Result};
use indexmap::IndexSet;
use itertools::Itertools;
use lazy_static::lazy_static;
use std::collections::HashMap;
use crate::parser;
mod instruction;
pub use instruction::Instruction;
pub const TEMPLATE_ASM: &str = include_str!("compiler/templates/default.asm");
lazy_static! {
#[derive(Debug)]
pub static ref TEMPLATE: hence::parser::ast::Body = hence::parser::parse(
hence::lexer::lex(TEMPLATE_ASM).unwrap()
)
.unwrap()
.body;
}
pub trait Compilable<T, U> {
fn compile(&self, data: &T) -> Result<U>;
}
#[derive(Debug)]
pub struct Word {
pub id: usize,
pub instructions: Vec<Instruction>,
pub times_used: usize,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Condition {
pub if_instructions: Vec<Instruction>,
pub else_instructions: Vec<Instruction>,
}
#[derive(Debug)]
pub enum CallableId {
Word(String),
Condition(usize),
}
#[derive(Debug)]
pub struct Compiler {
pub strings: IndexSet<String>,
pub words: HashMap<String, Word>,
pub conditions: Vec<Condition>,
}
impl Compiler {
pub fn default() -> Self {
Self {
// words: HashMap::new(),
// conditions: IndexSet::new(),
strings: IndexSet::new(),
words: HashMap::new(),
conditions: vec![],
}
}
pub fn generate_instructions(
&mut self,
body: parser::ast::Body,
optimize: bool,
) -> Result<Vec<Instruction>> {
let mut instructions: Vec<Instruction> = vec![];
let mut iter = body.into_iter().peekable();
while let Some(node) = iter.next() {
match node {
_ if optimize && iter.next_if_eq(&node).is_some() => {
let count = iter.by_ref().peeking_take_while(|n| *n == node).count() + 2;
instructions.push(Instruction::Multiple {
instruction: Box::new(
self.generate_instructions(vec![node], optimize)?
.into_iter()
.next()
.unwrap(),
),
count,
});
}
parser::ast::Node::Comment(_) => {}
parser::ast::Node::String { mode, string } => {
instructions.push(match mode.as_str() {
"." => {
let id = self.strings.insert_full(string).0;
Instruction::StringPrint(id)
}
"r" => {
let id = self.strings.insert_full(string).0;
Instruction::StringReference(id)
}
"asm" => Instruction::AsmQuote(string),
_ => bail!("Unknown string mode: {}", mode),
});
}
parser::ast::Node::Number(x) => {
instructions.push(instruction::Instruction::Push(x));
}
parser::ast::Node::WordDefinition {
name,
stack: _,
body,
} => {
if Instruction::from_word(&name).is_some() {
bail!("Word already exists as compiler instruction: {}", name);
} else if self.words.contains_key(&name) {
bail!("Word already exists as user word definition: {}", name);
}
let instructions = self.generate_instructions(body, optimize)?;
self.words.insert(
name.to_string(),
Word {
id: self.words.len(),
instructions,
times_used: 0,
},
);
}
parser::ast::Node::Condition { if_body, else_body } => {
// let if_instructions = self.generate_instructions(if_body, optimize)?;
// let else_instructions = self.generate_instructions(else_body, optimize)?;
// let id = self.conditions.len();
// let origin = self.callable_graph.add_node(CallableId::Condition(id));
// self.conditions.push(Condition {
// if_instructions: if_instructions.clone(),
// else_instructions: else_instructions.clone(),
// callable_graph_node: origin,
// });
// instructions.push(Instruction::Condition(id));
// self.add_graph_edges(origin, if_instructions)?;
// self.add_graph_edges(origin, else_instructions)?;
// dbg!(&self);
}
parser::ast::Node::Word(x) => {
dbg!(&self.words, &x);
if let Some(ins) = Instruction::from_word(&x) {
instructions.push(ins);
} else if let Some(w) = self.words.get_mut(&x) {
w.times_used += 1;
instructions.push(Instruction::Call(x));
} else {
bail!("Word does not exist: {}", x);
}
}
}
}
Ok(instructions)
}
pub fn embed(&self, body: hence::parser::ast::Body) -> Result<hence::parser::ast::Body> {
let mut x = TEMPLATE.to_vec();
// strings
for (id, s) in self.strings.iter().enumerate() {
x.extend([
hence::parser::ast::Node::Label(format!("data_strings_{}", id)),
hence::parser::ast::Node::MacroCall {
name: "bytes".to_string(),
args: vec![hence::arg::Arg::String(s.to_string())],
},
hence::parser::ast::Node::Label(format!("data_strings_end_{}", id)),
]);
}
// conditions
for (id, c) in self.conditions.iter().enumerate() {
x.push(hence::parser::ast::Node::Label(format!(
"conditions_if_{}",
id
)));
x.extend(
c.if_instructions
.iter()
.map(|ins| ins.compile(self))
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten(),
);
x.push(hence::parser::ast::Node::Label(format!(
"conditions_else_{}",
id
)));
x.extend(
c.else_instructions
.iter()
.map(|ins| ins.compile(self))
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten(),
);
}
// words
for (name, word) in &self
.words
.iter()
.filter(|(_, w)| w.times_used > 1)
.sorted_by(|a, b| Ord::cmp(&a.1.id, &b.1.id))
.collect::<Vec<_>>()
{
x.extend(vec![
hence::parser::ast::Node::Label(format!("words_{}", word.id)),
hence::parser::ast::Node::Comment(format!("word: \"{}\"", name)),
]);
x.extend(
word.instructions
.iter()
.map(|ins| ins.compile(self))
.collect::<Result<Vec<hence::parser::ast::Body>>>()
.unwrap()
.into_iter()
.flatten(),
);
x.push(hence::parser::ast::Node::MacroCall {
name: "return_call_stack_jump".to_string(),
args: vec![],
});
}
x.extend([
hence::parser::ast::Node::Label("main".to_string()),
hence::parser::ast::Node::MacroCall {
name: "main".to_string(),
args: vec![hence::arg::Arg::Variable("main".to_string())],
},
]);
x.extend(body);
x.push(hence::parser::ast::Node::MacroCall {
name: "std_stop".to_string(),
args: vec![],
});
Ok(x)
}
}
pub fn compile(ast: parser::ast::AST, optimize: bool) -> Result<hence::parser::ast::AST> {
let mut data = Compiler::default();
let instructions = data.generate_instructions(ast.body, optimize)?;
Ok(hence::parser::ast::AST {
body: data.embed(
instructions
.iter()
.map(|ins| ins.compile(&data))
.collect::<Result<Vec<hence::parser::ast::Body>>>()?
.into_iter()
.flatten()
.collect(),
)?,
})
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
.include "$lib/core.asm"
.include "$lib/std.asm"
.include "$lib/main.asm"
.define MEM_LOOP_I, CORE_MEM_MEM
.define MEM_LOOP_J, (MEM_LOOP_I + 1)
.define MEM_CALL_STACK_LEN, 16
.define MEM_CALL_STACK_PTR, (MEM_LOOP_J + 1)
.define MEM_CALL_STACK_END, (MEM_CALL_STACK_PTR + MEM_CALL_STACK_LEN)
.define MEM_ALLOC_PTR, MEM_CALL_STACK_END
.macro stack_transfer_alu
.std_ld
tlr CORE_REG_B
.std_ld
tlr CORE_REG_A
.endmacro
.macro call_stack_jump, call_stack_jump_arg_0_label, call_stack_jump_arg_1_offset
.std_rset CORE_REG_C, call_stack_jump_arg_0_label
.std_rset CORE_REG_D, (call_stack_jump_arg_1_offset + 7)
ts call_stack_jump
tlr CORE_REG_PC
.endmacro
.macro return_call_stack_jump
.std_jump return_call_stack_jump
.endmacro
.std_rset CORE_REG_A, MEM_CALL_STACK_PTR
.std_set MEM_CALL_STACK_PTR
.std_rset CORE_REG_A, (MEM_ALLOC_PTR + 1)
.std_set MEM_ALLOC_PTR
.jump_main
call_stack_jump:
.std_get MEM_CALL_STACK_PTR
tlr CORE_REG_A
.std_rset CORE_REG_B, 1
.std_alu CORE_ALU_ADD
tlr CORE_REG_A
tlr CORE_REG_B
.std_set MEM_CALL_STACK_PTR
tsr CORE_REG_D
tlr CORE_REG_A
tsr CORE_REG_B
set
tsr CORE_REG_C
tlr CORE_REG_PC
return_call_stack_jump:
.std_get MEM_CALL_STACK_PTR
tlr CORE_REG_A
tlr CORE_REG_C
.std_rset CORE_REG_B, 1
.std_alu CORE_ALU_SUB
tlr CORE_REG_A
.std_set MEM_CALL_STACK_PTR
tsr CORE_REG_C
get
tlr CORE_REG_PC

84
henceforth/src/lexer.rs Normal file
View file

@ -0,0 +1,84 @@
use anyhow::Result;
use hence::assembler::ToCode;
use itertools::Itertools;
#[derive(Debug)]
pub enum Token {
Newline(usize),
Whitespace(usize),
ParenComment(String),
BackslashComment(String),
DoubleDashComment(String),
StringLiteral { mode: String, string: String },
Number(String),
Word(String),
}
impl ToCode for Token {
fn to_code(&self) -> String {
match self {
Token::Newline(x) => ["\n"].into_iter().cycle().take(*x).join(""),
Token::Whitespace(x) => [" "].into_iter().cycle().take(*x).join(""),
Token::ParenComment(x) => format!("( {})", x),
Token::BackslashComment(x) => format!("\\{}", x),
Token::DoubleDashComment(x) => format!("-- {}", x),
Token::StringLiteral { mode, string } => format!("{}\" {}\"", mode, string),
Token::Number(x) | Token::Word(x) => x.clone(),
}
}
}
pub fn is_space(c: char) -> bool {
c.is_whitespace() || c == '\n'
}
pub fn lex(source: &str) -> Result<Vec<Token>> {
let mut chars = source.chars().peekable();
let mut tokens: Vec<Token> = vec![];
while let Some(c) = chars.peek() {
tokens.push(match c {
'\n' => Token::Newline(chars.peeking_take_while(|&c| c == '\n').count()),
_ if c.is_whitespace() => {
Token::Whitespace(chars.peeking_take_while(|&c| c.is_whitespace()).count())
}
'\\' => Token::BackslashComment(chars.peeking_take_while(|&c| c != '\n').collect()),
_ if c.is_numeric() => {
Token::Number(chars.peeking_take_while(|&c| !is_space(c)).collect())
}
_ => {
let x: String = chars.peeking_take_while(|&c| !is_space(c)).collect();
let mut iter = x.chars();
match x.as_str() {
"(" => Token::ParenComment(
chars.by_ref().skip(1).take_while(|&c| c != ')').collect(),
),
"--" => Token::DoubleDashComment(
chars.by_ref().take_while(|&c| c != '\n').collect(),
),
_ if x.ends_with('"') => Token::StringLiteral {
mode: x.chars().take(x.len() - 1).collect(),
string: chars.by_ref().skip(1).take_while(|&c| c != '"').collect(),
},
_ if iter.next() == Some('-') => {
if let Some(c) = iter.next() {
if c.is_numeric() {
Token::Number(x)
} else {
Token::Word(x)
}
} else {
Token::Word(x)
}
}
_ => Token::Word(x),
}
}
});
}
Ok(tokens)
}

3
henceforth/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod compiler;
pub mod lexer;
pub mod parser;

80
henceforth/src/main.rs Normal file
View file

@ -0,0 +1,80 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use hence::assembler::ToCode;
use std::fs;
use henceforth::*;
#[derive(Debug, Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
#[clap(subcommand)]
commands: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
#[clap(about = "Lexes source code and outputs tokens")]
Lex {
#[clap(value_parser)]
src: String,
},
#[clap(about = "Parses source code and outputs AST")]
Parse {
#[clap(value_parser)]
src: String,
},
#[clap(about = "Compiles assembly from source code")]
Compile {
#[clap(value_parser)]
src: String,
#[clap(value_parser)]
out: Option<String>,
#[clap(short, long, action)]
optimize: Option<bool>,
#[clap(long, action)]
dump: bool,
},
}
fn main() -> Result<()> {
let args = Cli::parse();
match args.commands {
Commands::Lex { src } => {
let source = fs::read_to_string(src)?;
let tokens = lexer::lex(&source)?;
println!("{:#?}", tokens);
Ok(())
}
Commands::Parse { src } => {
let source = fs::read_to_string(src)?;
let tokens = lexer::lex(&source)?;
let body = parser::parse(tokens)?;
println!("{:#?}", body);
Ok(())
}
Commands::Compile {
src,
out,
optimize,
dump,
} => {
let source = fs::read_to_string(&src)?;
let tokens = lexer::lex(&source)?;
let ast = parser::parse(tokens)?;
let ast = compiler::compile(ast, optimize.unwrap_or(true))?;
let assembly = format!("{}\n", ast.to_code());
if dump {
print!("{}", assembly);
}
if let Some(x) = out {
fs::write(x, &assembly)?;
}
Ok(())
}
}
}

168
henceforth/src/parser.rs Normal file
View file

@ -0,0 +1,168 @@
use anyhow::{bail, Result};
use parse_int;
use crate::lexer;
pub mod ast;
pub fn parse_stack_state(s: Option<&str>) -> Vec<String> {
match s {
Some(x) if !x.trim().is_empty() => {
x.split_whitespace().map(|x| x.trim().to_string()).collect()
}
_ => vec![],
}
}
pub fn parse_stack_result(s: &str) -> ast::StackResult {
let mut splitter = s.splitn(2, "--");
ast::StackResult {
before: parse_stack_state(splitter.next()),
after: parse_stack_state(splitter.next()),
}
}
pub fn parse(tokens: Vec<lexer::Token>) -> Result<ast::AST> {
let mut iter = tokens.into_iter().peekable();
let mut body: ast::Body = vec![];
while let Some(token) = iter.next() {
match token {
lexer::Token::Newline(_) | lexer::Token::Whitespace(_) => {}
lexer::Token::ParenComment(x)
| lexer::Token::BackslashComment(x)
| lexer::Token::DoubleDashComment(x) => {
body.push(ast::Node::Comment(x.trim().to_string()));
}
lexer::Token::StringLiteral { mode, string } => {
body.push(ast::Node::String { mode, string });
}
lexer::Token::Number(x) => body.push(ast::Node::Number(parse_int::parse(&x)?)),
lexer::Token::Word(x) => match x.as_str() {
":" => {
let mut depth: usize = 1;
let mut content = iter
.by_ref()
.take_while(|t| match t {
lexer::Token::Word(x) => match x.as_str() {
":" => {
depth += 1;
true
}
";" => {
depth -= 1;
depth != 0
}
_ => true,
},
_ => true,
})
.collect::<Vec<_>>()
.into_iter()
.peekable();
if depth != 0 {
bail!("Unbalanced word definitions");
}
let name = match content.find(|t| {
!matches!(t, lexer::Token::Newline(_) | lexer::Token::Whitespace(_))
}) {
Some(t) => match t {
lexer::Token::Word(x) => x.clone(),
_ => bail!("Word definition name must be a word itself: {:?}", t),
},
None => bail!("Word definition can not be empty"),
};
let stack = loop {
if let Some(t) = content.peek() {
match t {
lexer::Token::Newline(_) | lexer::Token::Whitespace(_) => {
content.next();
}
lexer::Token::ParenComment(x)
| lexer::Token::BackslashComment(x)
| lexer::Token::DoubleDashComment(x) => {
let y = &x.to_string();
content.next();
break Some(parse_stack_result(y));
}
_ => break None,
}
} else {
break None;
}
};
body.push(ast::Node::WordDefinition {
name,
stack,
body: parse(content.collect())?.body,
});
}
"if" => {
let mut depth: usize = 1;
let mut else_used = false;
let if_toks: Vec<_> = iter
.by_ref()
.take_while(|t| match t {
lexer::Token::Word(x) => match x.as_str() {
"if" => {
depth += 1;
true
}
"else" => {
if depth == 1 {
else_used = true;
false
} else {
true
}
}
"then" => {
depth -= 1;
depth != 0
}
_ => true,
},
_ => true,
})
.collect();
let else_toks: Vec<_> = if else_used {
iter.by_ref()
.take_while(|t| match t {
lexer::Token::Word(x) => match x.as_str() {
"if" => {
depth += 1;
true
}
"then" => {
depth -= 1;
depth != 0
}
_ => true,
},
_ => true,
})
.collect()
} else {
vec![]
};
if depth != 0 {
bail!("Unbalanced conditions");
}
body.push(ast::Node::Condition {
if_body: parse(if_toks)?.body,
else_body: parse(else_toks)?.body,
});
}
_ => {
body.push(ast::Node::Word(x));
}
},
}
}
Ok(ast::AST { body })
}

View file

@ -0,0 +1,90 @@
use hence::assembler::ToCode;
use itertools::Itertools;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct StackResult {
pub before: Vec<String>,
pub after: Vec<String>,
}
impl ToCode for StackResult {
fn to_code(&self) -> String {
format!(
"{}--{}",
if self.before.is_empty() {
"".to_string()
} else {
format!("{} ", self.before.join(" "))
},
if self.after.is_empty() {
"".to_string()
} else {
format!("{} ", self.after.join(" "))
}
)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Node {
Comment(String),
String {
mode: String,
string: String,
},
Number(i32),
WordDefinition {
name: String,
stack: Option<StackResult>,
body: Body,
},
Condition {
if_body: Body,
else_body: Body,
},
Word(String),
}
impl ToCode for Node {
fn to_code(&self) -> String {
match self {
Node::Comment(x) => format!("\\ {}", x),
Node::String { mode, string } => format!("{}\" {}\"", mode, string),
Node::Number(x) => x.to_string(),
Node::WordDefinition { name, stack, body } => format!(
": {}{} {} ;",
name,
match stack {
Some(x) => format!(" {}", x.to_code()),
None => "".to_string(),
},
body.iter().map(|x| x.to_code()).join(" ")
),
Node::Condition { if_body, else_body } => {
if else_body.is_empty() {
format!("if {} then", if_body.iter().map(|x| x.to_code()).join(" "))
} else {
format!(
"if {} else {} then",
if_body.iter().map(|x| x.to_code()).join(" "),
else_body.iter().map(|x| x.to_code()).join(" ")
)
}
}
Node::Word(x) => x.to_owned(),
}
}
}
pub type Body = Vec<Node>;
#[derive(Debug)]
pub struct AST {
pub body: Body,
}
impl ToCode for AST {
fn to_code(&self) -> String {
self.body.iter().map(|x| x.to_code()).join(" ")
}
}

1
presentation/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dist/

30
presentation/Makefile Normal file
View file

@ -0,0 +1,30 @@
# Author: Daniel Nicolas Gisolfi
# Date: 2020-8-11
source=$(shell find . -type f -name "*.md")
title=$(shell grep -m 1 title $(source) | cut -d ':' -f2 | xargs)
version=$(shell grep -m 1 version $(source) | cut -d ':' -f2 | xargs)
theme=$(shell find . -type f -name "*.css")
flags=--allow-local-files --theme $(theme)
devflags=--server --watch --allow-local-files
dist="./dist"
.PHONY: pptx
pptx:
marp $(flags) "$(source)" -o "$(dist)/$(title)-$(version).pptx"
.PHONY: pdf
pdf:
marp $(flags) "$(source)" -o "$(dist)/$(title)-$(version).pdf"
.PHONY: html
html:
marp $(flags) "$(source)" -o "$(dist)/$(title)-$(version).html"
.PHONY: png
png:
marp $(flags) --images png "$(source)"
.PHONY: dev
dev:
marp $(devflags) .

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

330
presentation/dracula.css Normal file
View file

@ -0,0 +1,330 @@
@charset "UTF-8";
/*!
* Marp Dracula theme.
* @theme dracula
* @author Daniel Nicolas Gisolfi
*
* @auto-scaling true
* @size 4:3 960px 720px
* @size 16:9 1280px 720px
*/
@import url("https://fonts.googleapis.com/css?family=Lato:400,900|IBM+Plex+Sans:400,700");
:root {
--dracula-background: #282a36;
--dracula-current-line: #44475a;
--dracula-foreground: #f8f8f2;
--dracula-comment: #6272a4;
--dracula-cyan: #8be9fd;
--dracula-green: #50fa7b;
--dracula-orange: #ffb86c;
--dracula-pink: #ff79c6;
--dracula-purple: #bd93f9;
--dracula-red: #ff5555;
--dracula-yellow: #f1fa8c;
}
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: var(--dracula-background);
}
/* Dracula Foreground */
.hljs,
.hljs-subst,
.hljs-typing,
.hljs-variable,
.hljs-template-variable {
color: var(--dracula-foreground);
}
/* Dracula Comment */
.hljs-comment,
.hljs-quote,
.hljs-deletion {
color: var(--dracula-comment);
}
/* Dracula Cyan */
.hljs-meta .hljs-doctag,
.hljs-built_in,
.hljs-selector-tag,
.hljs-section,
.hljs-link,
.hljs-class {
color: var(--dracula-cyan);
}
/* Dracula Green */
.hljs-title {
color: var(--dracula-green);
}
/* Dracula Orange */
.hljs-params {
color: var(--dracula-orange);
}
/* Dracula Pink */
.hljs-keyword {
color: var(--dracula-pink);
}
/* Dracula Purple */
.hljs-literal,
.hljs-number {
color: var(--dracula-purple);
}
/* Dracula Red */
.hljs-regexp {
color: var(--dracula-red);
}
/* Dracula Yellow */
.hljs-string,
.hljs-name,
.hljs-type,
.hljs-attr,
.hljs-symbol,
.hljs-bullet,
.hljs-addition,
.hljs-template-tag {
color: var(--dracula-yellow);
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-title,
.hljs-section,
.hljs-doctag,
.hljs-type,
.hljs-name,
.hljs-strong {
font-weight: bold;
}
.hljs-params,
.hljs-emphasis {
font-style: italic;
}
svg[data-marp-fitting="svg"] {
max-height: 580px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0.5em 0 0 0;
color: var(--dracula-pink);
}
h1 strong,
h2 strong,
h3 strong,
h4 strong,
h5 strong,
h6 strong {
font-weight: inherit;
}
h1 {
font-size: 1.8em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.3em;
}
h4 {
font-size: 1.1em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: 0.9em;
}
p,
blockquote {
margin: 1em 0 0 0;
}
ul > li,
ol > li {
margin: 0.3em 0 0 0;
color: var(--dracula-cyan);
}
ul > li > p,
ol > li > p {
margin: 0.6em 0 0 0;
}
code {
display: inline-block;
font-family: "IBM Plex Mono", monospace;
font-size: 0.8em;
letter-spacing: 0;
margin: -0.1em 0.15em;
padding: 0.1em 0.2em;
vertical-align: baseline;
color: var(--dracula-green);
}
pre {
display: block;
margin: 1em 0 0 0;
min-height: 1em;
overflow: visible;
}
pre code {
box-sizing: border-box;
margin: 0;
min-width: 100%;
padding: 0.5em;
font-size: 0.7em;
}
pre code svg[data-marp-fitting="svg"] {
max-height: calc(580px - 1em);
}
blockquote {
margin: 1em 0 0 0;
padding: 0 1em;
position: relative;
color: var(--dracula-orange);
}
blockquote::after,
blockquote::before {
content: "“";
display: block;
font-family: "Times New Roman", serif;
font-weight: bold;
position: absolute;
color: var(--dracula-green);
}
blockquote::before {
top: 0;
left: 0;
}
blockquote::after {
right: 0;
bottom: 0;
transform: rotate(180deg);
}
blockquote > *:first-child {
margin-top: 0;
}
mark {
background: transparent;
}
table {
border-spacing: 0;
border-collapse: collapse;
margin: 1em 0 0 0;
}
table th,
table td {
padding: 0.2em 0.4em;
border-width: 1px;
border-style: solid;
}
section {
font-size: 35px;
font-family: "IBM Plex Sans";
line-height: 1.35;
letter-spacing: 1.25px;
padding: 70px;
color: var(--dracula-foreground);
background-color: var(--dracula-background);
}
section > *:first-child,
section > header:first-child + * {
margin-top: 0;
}
section a,
section mark {
color: var(--dracula-red);
}
section code {
background: var(--dracula-current-line);
color: var(--dracula-current-green);
}
section h1 strong,
section h2 strong,
section h3 strong,
section h4 strong,
section h5 strong,
section h6 strong {
color: var(--dracula-current-line);
}
section pre > code {
background: var(--dracula-current-line);
}
section header,
section footer,
section section::after,
section blockquote::before,
section blockquote::after {
color: var(--dracula-comment);
}
section table th,
section table td {
border-color: var(--dracula-current-line);
}
section table thead th {
background: var(--dracula-current-line);
color: var(--dracula-yellow);
}
section table tbody > tr:nth-child(even) td,
section table tbody > tr:nth-child(even) th {
background: var(--dracula-current-line);
}
header,
footer,
section::after {
box-sizing: border-box;
font-size: 66%;
height: 70px;
line-height: 50px;
overflow: hidden;
padding: 10px 25px;
position: absolute;
color: var(--dracula-comment);
}
header {
left: 0;
right: 0;
top: 0;
}
footer {
left: 0;
right: 0;
bottom: 0;
}
section::after {
right: 0;
bottom: 0;
font-size: 80%;
}

View file

@ -0,0 +1,101 @@
---
title: Hence
version: 1.0.0
theme: dracula
footer: Grimm
header: Hence
marp: true
size: 4K
---
# Hence
Ein Ausflug in die Welt einer virtuellen CPU
_Dominic Grimm_
<style scoped>
h1 {
padding-top: 1.5em;
}
</style>
![bg left](./assets/brian-kostiuk-S4jSvcHYcOs-unsplash.jpg)
---
# Inhalt
- Was ist eine CPU?
- Überblick Assembler
- Hence
- (Hence-)Forth
=> _Workshop_
---
# Was ist eine CPU?
- Herzstück eines Computers
- Kleiner Chip auf Hauptplatine
- Verarbeitung Arithmetik und Logik
- Zugriff auf Speicher und Peripherie
![bg right](./assets/harrison-broadbent-VOz0gV9HC0I-unsplash.jpg)
---
## Aufbau
![bg right:35% fit](./assets/was-ist-cpu-darstellung.png)
- _Steuerwerk, Rechenwerk, Cache, Register_
- Cache: schneller Speicher für oft benötigte Daten
- CPU kommuniziert über Bus-Systeme
- Leistung abhängig von Kernzahl, Taktfrequenz, Cache und Architektur
- Neueste Entwicklungen: Multi-Core-Prozessoren und KI
---
## Machine code
- Niedrigste Ebene der Programmierung.
- Binäre Instruktionen, direkt von der CPU ausgeführt
- Jede CPU-Architektur hat ihre eigene Maschinensprache, die spezifisch für diese Architektur ist
- Sehr effizient, da direkt von CPU ausgeführt
- Meist in höherer Programmiersprache geschrieben
=> *Assembler*
---
### Beispiel
*hexadezimal kodiert (`hexdump`)*
```
00000000 03 7f fc 86 03 a0 00 06 00 06 03 c0 02 8b 06 00 |................|
00000010 05 03 c0 01 8c 04 00 06 8c 03 00 01 06 00 05 03 |................|
*
```
| Code | Opcode | Argument |
| ---------- | -------------- | ---------------- |
| `03 7f fc` | `0x03` (`ts`) | `0x7ffc` (32764) |
| `86` | `0x06` (`tlr`) | |
| `03 a0 00` | `0x03` (`ts`) | `0xa000` (40960) |
---
# Überblick Assembler
![width:30cm](./assets/assembler.png)
---
# Hence
- Virtuelle "CPU" (eher SBC)
-