hecate_bf/
lib.rs

1use fck::{
2    lexer::lex,
3    parser::{parse, AstNode},
4};
5use hecate_assembler::Assembler;
6use hecate_common::BytecodeFile;
7use stringlit::s;
8
9const SC: &str = "R1";
10const SC_ADDR: &str = "R2";
11const SC_LEN: &str = "R3";
12const Z: &str = "R0"; // Zero register
13const DP: &str = "R10"; // Data pointer
14const T1: &str = "R11"; // Temporary register 1
15const T2: &str = "R12"; // Temporary register 2
16
17pub fn ast_to_assembly(
18    ast: &AstNode,
19    asm: &mut Vec<String>,
20    dp_offset: &mut isize,
21    max_dp_offset_left: &mut isize,
22    max_dp_offset_right: &mut isize,
23) {
24    match ast {
25        AstNode::Sequence(nodes) => {
26            for node in nodes {
27                ast_to_assembly(
28                    node,
29                    asm,
30                    dp_offset,
31                    max_dp_offset_left,
32                    max_dp_offset_right,
33                );
34            }
35        }
36        AstNode::Loop(nodes) => {
37            let loop_start_label = format!("loop_start_{}", asm.len());
38            let loop_end_label = format!("loop_end_{}", asm.len());
39
40            asm.push(format!("{}:", loop_start_label));
41            asm.push(format!("loadreg {T1}, {DP}"));
42            asm.push(format!("cmp {T1}, {Z}"));
43            asm.push(format!("je @{}", loop_end_label));
44            for node in nodes {
45                ast_to_assembly(
46                    node,
47                    asm,
48                    dp_offset,
49                    max_dp_offset_left,
50                    max_dp_offset_right,
51                );
52            }
53            asm.push(format!("jmp @{}", loop_start_label));
54            asm.push(format!("{}:", loop_end_label));
55        }
56        AstNode::Clear => {
57            asm.push(format!("load {DP}, 500"));
58        }
59        AstNode::Right(steps) => {
60            asm.push(format!("add {DP}, {}", steps));
61            *dp_offset += *steps as isize;
62            if *dp_offset > *max_dp_offset_right {
63                *max_dp_offset_right = *dp_offset;
64            }
65        }
66        AstNode::Left(steps) => {
67            asm.push(format!("sub {DP}, {}", steps));
68            *dp_offset -= *steps as isize;
69            if *dp_offset < *max_dp_offset_left {
70                *max_dp_offset_left = *dp_offset;
71            }
72        }
73        AstNode::Increment(value) => {
74            asm.push(format!("loadreg {T2}, {DP}"));
75            asm.push(format!("add {T2}, {}", value));
76            asm.push(format!("storereg {DP}, {T2}"));
77        }
78        AstNode::Decrement(value) => {
79            asm.push(format!("loadreg {T2}, {DP}"));
80            asm.push(format!("sub {T2}, {}", value));
81            asm.push(format!("storereg {DP}, {T2}"));
82        }
83        AstNode::Output => {
84            asm.push(format!("load {SC}, {Z}"));
85            asm.push(format!("load {SC_ADDR}, {DP}"));
86            asm.push(format!("load {SC_LEN}, 1"));
87            asm.push(s!("syscall"));
88        }
89        AstNode::Input => {
90            panic!("Input not implemented");
91        }
92    }
93}
94
95pub fn compile_program(program: &str) -> anyhow::Result<BytecodeFile> {
96    let tokens = lex(program)?;
97    let ast = parse(&tokens)?;
98    let mut asm = Vec::new();
99    let mut dp_offset = 0;
100    let mut dp_offset_max_left = 0;
101    let mut dp_offset_max_right = 0;
102    asm.push(format!("load {DP}, 500"));
103    ast_to_assembly(
104        &ast,
105        &mut asm,
106        &mut dp_offset,
107        &mut dp_offset_max_left,
108        &mut dp_offset_max_right,
109    );
110
111    asm.push(s!("halt"));
112
113    let file = Assembler::new().assemble_program(&asm.join("\n"))?;
114
115    Ok(file)
116}