started on a compiler for a simple functional programming language for the DSA.

This commit is contained in:
2026-08-09 09:15:09 +01:00
parent b542fc3c21
commit 8b7f29cd48
5 changed files with 346 additions and 225 deletions
+19
View File
@@ -0,0 +1,19 @@
# Codegen.
## How?
(add (sub 5 4)
(add (sub 4 3)
(add (sub 3 2)
(add (sub 2 1)
(add (sub 1 0)
0
)
)
)
)
)
becomes
v1 = 5 - 4 v2 = 4 - 3 v3 = 3 - 2 v4 = 2 - 1 v5 = 1 - 0 v6 = v5 + 0 v7 = v4 + v6 v8 = v3 + v7 v9 = v2 + v8 v10 = v1 + v9
+61 -212
View File
@@ -1,12 +1,14 @@
mod parser;
use crate::parser::{AST, Lexer};
use assembler::prelude::Assembler; use assembler::prelude::Assembler;
use dsa_formats::binary_dse::DseExecutable; use dsa_formats::binary_dse::DseExecutable;
use dsa_tasks::{ProcessAgent, TraceableProcess}; use dsa_tasks::{ProcessAgent, TraceableProcess};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
#[derive(Debug)] #[derive(Debug)]
struct DsflCompiler { pub struct DsflCompiler {
src: PathBuf, filename: String,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -20,15 +22,20 @@ impl std::fmt::Display for DsflError {
} }
impl TraceableProcess for DsflCompiler { impl TraceableProcess for DsflCompiler {
type Input = PathBuf;
type Output = DseExecutable; type Output = DseExecutable;
type Error = DsflError; type Error = DsflError;
fn label(&self) -> String { fn label(&self) -> String {
format!("Compiling {}", self.src.file_name().unwrap().display()) format!("Compiling {}", self.filename)
} }
fn run(mut self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error> { fn run(
let src_file = self.src.clone(); mut self: Box<Self>,
input: impl Into<Self::Input>,
ctx: ProcessAgent,
) -> Result<Self::Output, Self::Error> {
let src_file = input.into();
ctx.log(format!("Building {}", src_file.display())); ctx.log(format!("Building {}", src_file.display()));
@@ -51,227 +58,69 @@ impl TraceableProcess for DsflCompiler {
ctx.log(String::from("Assembling Output...")); ctx.log(String::from("Assembling Output..."));
let res = Box::new(Assembler::new(&asm_output_file)) let res = Box::new(Assembler::new(
.run(ctx) &asm_output_file
.map_err(|e| DsflError("failed to run Assembler")); .file_name()
.unwrap()
.to_string_lossy()
.to_string(),
))
.run(asm_output_file, ctx)
.map_err(|e| DsflError("failed to run Assembler"));
res res
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Token {
Lparen,
Rparen,
If,
Then,
Else,
Match,
Operator(Operator),
Int(u32),
Bool(bool),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Operator {
Add,
Sub,
And,
Or,
Not,
Equals,
}
impl DsflCompiler { impl DsflCompiler {
fn compile(&mut self, src: String) -> Result<Vec<u8>, DsflError> { pub fn new(filename: &str) -> Self {
let lexed = Lexer::new(src).lex()?;
Ok(vec![])
}
}
pub struct Lexer {
src: Vec<char>,
buffer: String,
tokens: Vec<Token>,
}
impl Lexer {
pub fn new(src: String) -> Self {
Self { Self {
src: src.chars().rev().collect(), filename: filename.into(),
buffer: String::new(),
tokens: Vec::new(),
} }
} }
pub fn lex(&mut self) -> Result<Vec<Token>, DsflError> { fn compile(&mut self, src: String) -> Result<String, DsflError> {
while let Some(c) = self.src.last() { let tokens = Lexer::new(src).lex()?;
match c { let ast = AST::parse(tokens)?;
' ' | '\t' | '\n' => {
self.src.pop();
}
'0'..='9' => {
let result = self.parse_numeric()?;
self.tokens.push(result);
}
'a'..='z' | 'A'..='Z' | '_' => {
let result = self.parse_ident()?;
self.tokens.push(result);
}
'(' => {
self.tokens.push(Token::Lparen);
self.src.pop();
}
')' => {
self.tokens.push(Token::Rparen);
self.src.pop();
}
_ => return Err(DsflError("invalid char for language 1")),
}
}
Ok(self.tokens.clone()) let mut asm = String::new();
}
fn parse_numeric(&mut self) -> Result<Token, DsflError> { asm.push_str(
while let Some(c) = self.src.last() { r#"
match c { include print: "./print.dsa"
' ' | '\n' | '\t' | '(' | ')' | 'a'..='z' | 'A'..='Z' | '_' => break, dw stack: 0x10000
'0'..='9' => {
self.buffer.push(*c);
self.src.pop();
}
_ => return Err(DsflError("invalid numeric character")),
}
}
let result = Token::Int(u32::from_str(&self.buffer).unwrap()); _init:
self.buffer.clear(); ldw stack, bpr
Ok(result) mov bpr, spr
} main:
push zero
call program
call print::print_hex_word
pop zero
hlt
fn parse_ident(&mut self) -> Result<Token, DsflError> { program: func
while let Some(c) = self.src.last() { push rg0
match c { push rg1
' ' | '\n' | '\t' | '(' | ')' | '0'..='9' => break, "#,
'a'..='z' | 'A'..='Z' | '_' => { );
self.buffer.push(*c);
self.src.pop();
}
_ => return Err(DsflError("invalid char for language 2")),
}
}
let result = match self.buffer.as_str() { ast.emit_asm(&mut asm)?;
"true" => Ok(Token::Bool(true)),
"false" => Ok(Token::Bool(false)),
"if" => Ok(Token::If),
"then" => Ok(Token::Then),
"else" => Ok(Token::Else),
"match" => Ok(Token::Match),
"add" => Ok(Token::Operator(Operator::Add)),
"sub" => Ok(Token::Operator(Operator::Sub)),
"and" => Ok(Token::Operator(Operator::And)),
"or" => Ok(Token::Operator(Operator::Or)),
"not" => Ok(Token::Operator(Operator::Not)),
"equals" => Ok(Token::Operator(Operator::Equals)),
_ => Err(DsflError("invalid identifier")),
};
self.buffer.clear(); asm.push_str(
result r#"
} // return value in bpr+8
} pop rg0
stw rg0, bpr, 8
#[derive(Debug, Clone, PartialEq)]
pub enum AST { // epilogue
BinaryOperation(Operator, Box<AST>, Box<AST>), pop rg1
UnaryOperation(Operator, Box<AST>), pop rg0
Conditional { return
if_: Box<AST>, "#,
then: Box<AST>, );
else_: Box<AST>,
}, Ok(asm)
Match(Vec<AST>),
Int(u32),
Bool(bool),
}
impl AST {
pub fn parse(tokens: Vec<Token>) -> Result<AST, DsflError> {
Self::parse_expr(&mut tokens.into_iter().rev().collect())
}
fn parse_expr(tokens: &mut Vec<Token>) -> Result<AST, DsflError> {
println!("opening");
let next = tokens.pop().exp_tok()?;
if let Token::Bool(x) = next {
return Ok(Self::Bool(x));
};
if let Token::Int(x) = next {
return Ok(Self::Int(x));
};
Self::expect_true(|| next == Token::Lparen)?;
let next = tokens.pop().exp_tok()?;
if let Token::Operator(op) = next {
let left = Self::parse_expr(tokens)?;
let right = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Rparen)?;
return Ok(Self::BinaryOperation(op, Box::new(left), Box::new(right)));
};
if let Token::If = next {
let condition = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Then)?;
let then = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Else)?;
let else_ = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Rparen)?;
return Ok(Self::Conditional {
if_: Box::new(condition),
then: Box::new(then),
else_: Box::new(else_),
});
}
Err(DsflError("no expression given"))
}
fn expect_true(expr: impl FnOnce() -> bool) -> Result<(), DsflError> {
expr().ok_or_else(|| DsflError("Expected true expression"))
}
}
trait ToResult<T> {
fn exp_tok(self) -> Result<T, DsflError>;
}
impl<T> ToResult<T> for Option<T> {
fn exp_tok(self) -> Result<T, DsflError> {
match self {
Some(t) => Ok(t),
None => Err(DsflError("Unexpected EOF")),
}
} }
} }
+13 -13
View File
@@ -1,5 +1,8 @@
use clap::Parser; use clap::Parser;
use dsfl_compiler::{AST, Lexer}; use dsa_tasks::TraceableProcess;
use dsfl_compiler::DsflCompiler;
use std::fs;
use std::io::Cursor;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
@@ -15,19 +18,16 @@ fn main() {
// Parse command line arguments // Parse command line arguments
let args = Args::parse(); let args = Args::parse();
let src_file = args.input_path; let filename = args.input_path.file_name().unwrap().to_str().unwrap();
println!("Building {}", src_file.display()); let compiler = DsflCompiler::new(filename);
let result = compiler.run_sync(args.input_path).unwrap();
let Ok(dsfl_src_code) = std::fs::read_to_string(&src_file) else { let mut buffer = Cursor::new(Vec::new());
return; result.write(&mut buffer).unwrap();
};
let tokens = Lexer::new(dsfl_src_code).lex().unwrap(); if let Err(e) = fs::write(args.output_path, buffer.into_inner()) {
println!("{tokens:?}"); eprintln!("Failed to write to output file: {e}");
std::process::exit(1);
let ast = AST::parse(tokens.clone()).unwrap(); }
println!("{ast:#?}");
println!("Successfully compiled {}", src_file.display());
} }
+253
View File
@@ -0,0 +1,253 @@
use super::DsflError;
use std::str::FromStr;
use std::sync::atomic::AtomicUsize;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Token {
Lparen,
Rparen,
If,
Then,
Else,
Match,
Operator(Operator),
Int(u32),
Bool(bool),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Operator {
Add,
Sub,
And,
Or,
Not,
Equals,
}
pub struct Lexer {
src: Vec<char>,
buffer: String,
tokens: Vec<Token>,
}
impl Lexer {
pub fn new(src: String) -> Self {
Self {
src: src.chars().rev().collect(),
buffer: String::new(),
tokens: Vec::new(),
}
}
pub fn lex(&mut self) -> Result<Vec<Token>, DsflError> {
while let Some(c) = self.src.last() {
match c {
' ' | '\t' | '\n' => {
self.src.pop();
}
'0'..='9' => {
let result = self.parse_numeric()?;
self.tokens.push(result);
}
'a'..='z' | 'A'..='Z' | '_' => {
let result = self.parse_ident()?;
self.tokens.push(result);
}
'(' => {
self.tokens.push(Token::Lparen);
self.src.pop();
}
')' => {
self.tokens.push(Token::Rparen);
self.src.pop();
}
_ => return Err(DsflError("invalid char for language 1")),
}
}
Ok(self.tokens.clone())
}
fn parse_numeric(&mut self) -> Result<Token, DsflError> {
while let Some(c) = self.src.last() {
match c {
' ' | '\n' | '\t' | '(' | ')' | 'a'..='z' | 'A'..='Z' | '_' => break,
'0'..='9' => {
self.buffer.push(*c);
self.src.pop();
}
_ => return Err(DsflError("invalid numeric character")),
}
}
let result = Token::Int(u32::from_str(&self.buffer).unwrap());
self.buffer.clear();
Ok(result)
}
fn parse_ident(&mut self) -> Result<Token, DsflError> {
while let Some(c) = self.src.last() {
match c {
' ' | '\n' | '\t' | '(' | ')' | '0'..='9' => break,
'a'..='z' | 'A'..='Z' | '_' => {
self.buffer.push(*c);
self.src.pop();
}
_ => return Err(DsflError("invalid char for language 2")),
}
}
let result = match self.buffer.as_str() {
"true" => Ok(Token::Bool(true)),
"false" => Ok(Token::Bool(false)),
"if" => Ok(Token::If),
"then" => Ok(Token::Then),
"else" => Ok(Token::Else),
"match" => Ok(Token::Match),
"add" => Ok(Token::Operator(Operator::Add)),
"sub" => Ok(Token::Operator(Operator::Sub)),
"and" => Ok(Token::Operator(Operator::And)),
"or" => Ok(Token::Operator(Operator::Or)),
"not" => Ok(Token::Operator(Operator::Not)),
"equals" => Ok(Token::Operator(Operator::Equals)),
_ => Err(DsflError("invalid identifier")),
};
self.buffer.clear();
result
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AST {
BinaryOperation(Operator, Box<AST>, Box<AST>),
UnaryOperation(Operator, Box<AST>),
Conditional {
if_: Box<AST>,
then: Box<AST>,
else_: Box<AST>,
},
Match(Vec<AST>),
Int(u32),
Bool(bool),
}
static COUNTER: AtomicUsize = AtomicUsize::new(0);
fn get_unique_int() -> usize {
COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
}
impl AST {
pub fn emit_asm(&self, result: &mut String) -> Result<(), DsflError> {
match self {
AST::BinaryOperation(op, lhs, rhs) => {
lhs.emit_asm(result)?;
rhs.emit_asm(result)?;
result.push_str("\tpop rg1\n\tpop rg0\n");
let op_string = match op {
Operator::Add => "add",
Operator::Sub => "sub",
Operator::And => "and",
Operator::Or => "or",
Operator::Equals => "ieq",
_ => Err(DsflError("invalid operator for binary operation"))?,
};
result.push_str(&format!("\t{op_string} rg0, rg1, rg0\n\tpush rg0\n"));
}
AST::UnaryOperation(op, rhs) => {}
AST::Conditional { if_, then, else_ } => {}
AST::Match(cases) => {}
AST::Int(val) => {
result.push_str(&format!("\tlli {}, rg0\n", *val as u16));
if *val >> 16 != 0 {
result.push_str(&format!("\tlui {}, rg0\n", val >> 16));
}
result.push_str("\tpush rg0\n");
}
AST::Bool(val) => result.push_str(&format!(
"\tlli {}, rg0\n\tpush rg0\n",
if *val { 1 } else { 0 }
)),
};
Ok(())
}
pub fn parse(tokens: Vec<Token>) -> Result<AST, DsflError> {
Self::parse_expr(&mut tokens.into_iter().rev().collect())
}
fn parse_expr(tokens: &mut Vec<Token>) -> Result<AST, DsflError> {
println!("opening");
let next = tokens.pop().exp_tok()?;
if let Token::Bool(x) = next {
return Ok(Self::Bool(x));
};
if let Token::Int(x) = next {
return Ok(Self::Int(x));
};
Self::expect_true(|| next == Token::Lparen)?;
let next = tokens.pop().exp_tok()?;
if let Token::Operator(op) = next {
let left = Self::parse_expr(tokens)?;
let right = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Rparen)?;
return Ok(Self::BinaryOperation(op, Box::new(left), Box::new(right)));
};
if let Token::If = next {
let condition = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Then)?;
let then = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Else)?;
let else_ = Self::parse_expr(tokens)?;
let t = tokens.pop().exp_tok()?;
Self::expect_true(|| t == Token::Rparen)?;
return Ok(Self::Conditional {
if_: Box::new(condition),
then: Box::new(then),
else_: Box::new(else_),
});
}
Err(DsflError("no expression given"))
}
fn expect_true(expr: impl FnOnce() -> bool) -> Result<(), DsflError> {
expr().ok_or_else(|| DsflError("Expected true expression"))
}
}
trait ToResult<T> {
fn exp_tok(self) -> Result<T, DsflError>;
}
impl<T> ToResult<T> for Option<T> {
fn exp_tok(self) -> Result<T, DsflError> {
match self {
Some(t) => Ok(t),
None => Err(DsflError("Unexpected EOF")),
}
}
}