use crate::parser::*;
use std::collections::HashMap;
use std::sync::LazyLock;
static LIBC_MATH_FNS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
HashMap::from([
("SIN", "sin"),
("COS", "cos"),
("TAN", "tan"),
("ATN", "atan"),
("EXP", "exp"),
("LOG", "log"),
])
});
static INLINE_MATH_FNS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
HashMap::from([
("SQR", "sqrtsd xmm0, xmm0"),
("INT", "roundsd xmm0, xmm0, 1"),
("FIX", "roundsd xmm0, xmm0, 3"),
])
});
#[cfg(target_os = "macos")]
const PREFIX: &str = "_";
#[cfg(not(target_os = "macos"))]
const PREFIX: &str = "";
fn is_string_var(name: &str) -> bool {
name.ends_with('$')
}
#[derive(Clone)]
struct VarInfo {
offset: i32,
data_type: DataType,
}
struct ArrayInfo {
ptr_offset: i32, dim_offsets: Vec<i32>, }
#[derive(Default)]
pub struct CodeGen {
output: String,
vars: HashMap<String, VarInfo>, arrays: HashMap<String, ArrayInfo>, stack_offset: i32, label_counter: u32, string_literals: Vec<String>, data_items: Vec<Literal>, current_proc: Option<String>, proc_vars: HashMap<String, VarInfo>, gosub_used: bool, }
impl CodeGen {
fn emit(&mut self, s: &str) {
self.output.push_str(s);
self.output.push('\n');
}
fn emit_typed(
&mut self,
work_type: DataType,
int_instr: &str,
single_instr: &str,
double_instr: &str,
) {
match work_type {
DataType::Integer | DataType::Long => self.emit(int_instr),
DataType::Single => self.emit(single_instr),
_ => self.emit(double_instr),
}
}
fn emit_cvt_float_to_int(&mut self, work_type: DataType) {
if !work_type.is_integer() {
self.emit_typed(
work_type,
"",
" cvttss2si eax, xmm0",
" cvttsd2si eax, xmm0",
);
self.emit_typed(
work_type,
"",
" cvttss2si ecx, xmm1",
" cvttsd2si ecx, xmm1",
);
}
}
fn emit_cvt_to_double(&mut self, work_type: DataType) {
match work_type {
DataType::Integer | DataType::Long => {
self.emit(" cvtsi2sd xmm0, eax");
self.emit(" cvtsi2sd xmm1, ecx");
}
DataType::Single => {
self.emit(" cvtss2sd xmm0, xmm0");
self.emit(" cvtss2sd xmm1, xmm1");
}
_ => {}
}
}
fn emit_label(&mut self, label: &str) {
self.output.push_str(label);
self.output.push_str(":\n");
}
fn new_label(&mut self, prefix: &str) -> String {
let label = format!(".L{}_{}", prefix, self.label_counter);
self.label_counter += 1;
label
}
fn add_string_literal(&mut self, s: &str) -> usize {
let idx = self.string_literals.len();
self.string_literals.push(s.to_string());
idx
}
fn get_var_info(&mut self, name: &str) -> VarInfo {
if self.current_proc.is_some() {
if let Some(info) = self.proc_vars.get(name) {
return info.clone();
}
}
if let Some(info) = self.vars.get(name) {
return info.clone();
}
let data_type = DataType::from_suffix(name);
self.stack_offset -= 8; let offset = self.stack_offset;
let info = VarInfo { offset, data_type };
if self.current_proc.is_some() {
self.proc_vars.insert(name.to_string(), info.clone());
} else {
self.vars.insert(name.to_string(), info.clone());
}
info
}
fn get_var_offset(&mut self, name: &str) -> i32 {
self.get_var_info(name).offset
}
fn expr_type(&self, expr: &Expr) -> DataType {
match expr {
Expr::Literal(lit) => match lit {
Literal::Integer(_) => DataType::Long, Literal::Float(_) => DataType::Double,
Literal::String(_) => DataType::String,
},
Expr::Variable(name) => DataType::from_suffix(name),
Expr::ArrayAccess { name, .. } => DataType::from_suffix(name),
Expr::FnCall { name, .. } => self.fn_return_type(name),
Expr::Unary { operand, .. } => self.expr_type(operand),
Expr::Binary { left, right, op } => {
let lt = self.expr_type(left);
let rt = self.expr_type(right);
self.promote_types(lt, rt, *op)
}
}
}
fn fn_return_type(&self, name: &str) -> DataType {
let upper = name.to_uppercase();
if upper.ends_with('$') {
return DataType::String;
}
match upper.as_str() {
"LEN" | "ASC" | "INSTR" | "CINT" | "CLNG" => DataType::Long,
_ => DataType::from_suffix(name),
}
}
fn promote_types(&self, left: DataType, right: DataType, op: BinaryOp) -> DataType {
if matches!(
op,
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge
) {
return DataType::Long; }
if op == BinaryOp::Div {
return DataType::Double;
}
if op == BinaryOp::IntDiv {
return DataType::Long;
}
if op == BinaryOp::Mod {
return DataType::Long;
}
if op == BinaryOp::Pow {
return DataType::Double;
}
if left == DataType::String && right == DataType::String {
return DataType::String;
}
match (left, right) {
(DataType::Double, _) | (_, DataType::Double) => DataType::Double,
(DataType::Single, _) | (_, DataType::Single) => DataType::Single,
(DataType::Long, _) | (_, DataType::Long) => DataType::Long,
_ => DataType::Integer,
}
}
fn gen_coercion(&mut self, from: DataType, to: DataType) {
if from == to {
return;
}
match (from, to) {
(DataType::Integer, DataType::Long) => {
self.emit(" movsx eax, ax"); }
(DataType::Long, DataType::Integer) => {
}
(DataType::Integer | DataType::Long, DataType::Single) => {
self.emit(" cvtsi2ss xmm0, eax");
}
(DataType::Integer | DataType::Long, DataType::Double) => {
self.emit(" cvtsi2sd xmm0, eax");
}
(DataType::Single, DataType::Double) => {
self.emit(" cvtss2sd xmm0, xmm0");
}
(DataType::Double, DataType::Single) => {
self.emit(" cvtsd2ss xmm0, xmm0");
}
(DataType::Single, DataType::Integer | DataType::Long) => {
self.emit(" cvttss2si eax, xmm0");
}
(DataType::Double, DataType::Integer | DataType::Long) => {
self.emit(" cvttsd2si eax, xmm0");
}
(DataType::String, _) | (_, DataType::String) => {
panic!("Cannot implicitly convert to/from String");
}
_ => {}
}
}
pub fn generate(&mut self, program: &Program) -> String {
for stmt in &program.statements {
self.preprocess(stmt);
}
self.emit(".intel_syntax noprefix");
self.emit(".text");
let p = PREFIX;
self.emit(&format!(".globl {}main", p));
self.emit("");
for stmt in &program.statements {
if let Stmt::Sub { name, params, body } = stmt {
self.gen_procedure(name, params, body, false);
} else if let Stmt::Function { name, params, body } = stmt {
self.gen_procedure(name, params, body, true);
}
}
self.emit_label(&format!("{}main", p));
self.emit(" push rbp");
self.emit(" mov rbp, rsp");
self.emit(" sub rsp, 0 # STACK_RESERVE");
if self.gosub_used {
self.emit(" # Initialize GOSUB return stack");
self.emit(" lea rax, [rip + _gosub_stack + 8192]"); self.emit(" mov QWORD PTR [rip + _gosub_sp], rax");
}
for stmt in &program.statements {
match stmt {
Stmt::Sub { .. } | Stmt::Function { .. } => {}
_ => self.gen_stmt(stmt),
}
}
self.emit(" xor eax, eax");
self.emit(" leave");
self.emit(" ret");
self.emit("");
let stack_needed = -self.stack_offset;
let stack_size = (stack_needed + 15) & !15; let old = " sub rsp, 0 # STACK_RESERVE";
let new = format!(" sub rsp, {} # STACK_RESERVE", stack_size);
self.output = self.output.replace(old, &new);
self.emit_data_section();
self.output.clone()
}
fn preprocess(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Data(values) => self.data_items.extend(values.clone()),
Stmt::Gosub(_) => self.gosub_used = true,
_ => {}
}
let bodies: Vec<&[Stmt]> = match stmt {
Stmt::If {
then_branch,
else_branch,
..
} => {
let mut v = vec![then_branch.as_slice()];
if let Some(eb) = else_branch {
v.push(eb.as_slice());
}
v
}
Stmt::For { body, .. }
| Stmt::While { body, .. }
| Stmt::DoLoop { body, .. }
| Stmt::Sub { body, .. }
| Stmt::Function { body, .. } => vec![body.as_slice()],
_ => vec![],
};
for body in bodies {
for s in body {
self.preprocess(s);
}
}
}
fn gen_procedure(&mut self, name: &str, params: &[String], body: &[Stmt], is_function: bool) {
self.current_proc = Some(name.to_string());
self.proc_vars.clear();
let old_stack_offset = self.stack_offset;
self.stack_offset = 0;
self.emit_label(&format!("_proc_{}", name));
self.emit(" push rbp");
self.emit(" mov rbp, rsp");
self.emit(" sub rsp, 64 # local vars");
let int_regs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
for (i, param) in params.iter().enumerate() {
self.stack_offset -= 8;
let data_type = DataType::from_suffix(param);
self.proc_vars.insert(
param.clone(),
VarInfo {
offset: self.stack_offset,
data_type,
},
);
if i < int_regs.len() {
self.emit(&format!(
" mov QWORD PTR [rbp + {}], {}",
self.stack_offset, int_regs[i]
));
}
}
if is_function {
self.stack_offset -= 8;
let data_type = DataType::from_suffix(name);
self.proc_vars.insert(
name.to_string(),
VarInfo {
offset: self.stack_offset,
data_type,
},
);
}
for stmt in body {
self.gen_stmt(stmt);
}
if is_function {
let ret_info = &self.proc_vars[name];
self.emit(&format!(
" movsd xmm0, QWORD PTR [rbp + {}]",
ret_info.offset
));
}
self.emit(" leave");
self.emit(" ret");
self.emit("");
self.current_proc = None;
self.stack_offset = old_stack_offset;
}
fn gen_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Label(n) => {
self.emit_label(&format!("_line_{}", n));
}
Stmt::Let {
name,
indices,
value,
} => {
if indices.is_some() {
self.gen_array_store(name, indices.as_ref().unwrap(), value);
} else if is_string_var(name) {
self.gen_string_assign(name, value);
} else {
let expr_type = self.gen_expr(value);
let var_info = self.get_var_info(name);
self.gen_coercion(expr_type, var_info.data_type);
match var_info.data_type {
DataType::Integer => {
self.emit(&format!(" mov WORD PTR [rbp + {}], ax", var_info.offset));
}
DataType::Long => {
self.emit(&format!(
" mov DWORD PTR [rbp + {}], eax",
var_info.offset
));
}
DataType::Single => {
self.emit(&format!(
" movss DWORD PTR [rbp + {}], xmm0",
var_info.offset
));
}
DataType::Double => {
self.emit(&format!(
" movsd QWORD PTR [rbp + {}], xmm0",
var_info.offset
));
}
DataType::String => {
unreachable!("String assignment should be handled separately");
}
}
}
}
Stmt::Print { items, newline } => {
for item in items {
match item {
PrintItem::Expr(expr) => {
self.gen_print_expr(expr);
}
PrintItem::Tab => {
self.emit(" mov rdi, 9 # tab");
self.emit(" call _rt_print_char");
}
PrintItem::Empty => {}
}
}
if *newline {
self.emit(" call _rt_print_newline");
}
}
Stmt::Input { prompt, vars } => {
if let Some(pstr) = prompt {
let idx = self.add_string_literal(pstr);
self.emit(&format!(" lea rdi, [rip + _str_{}]", idx));
self.emit(&format!(" mov rsi, {}", pstr.len()));
self.emit(" call _rt_print_string");
}
for var in vars {
if is_string_var(var) {
self.emit(" call _rt_input_string");
let offset = self.get_var_offset(var);
self.emit(&format!(" mov QWORD PTR [rbp + {}], rax", offset));
self.emit(&format!(" mov QWORD PTR [rbp + {}], rdx", offset - 8));
} else {
self.emit(" call _rt_input_number");
let offset = self.get_var_offset(var);
self.emit(&format!(" movsd QWORD PTR [rbp + {}], xmm0", offset));
}
}
}
Stmt::LineInput { prompt, var } => {
if let Some(pstr) = prompt {
let idx = self.add_string_literal(pstr);
self.emit(&format!(" lea rdi, [rip + _str_{}]", idx));
self.emit(&format!(" mov rsi, {}", pstr.len()));
self.emit(" call _rt_print_string");
}
self.emit(" call _rt_input_string");
let offset = self.get_var_offset(var);
self.emit(&format!(" mov QWORD PTR [rbp + {}], rax", offset));
self.emit(&format!(" mov QWORD PTR [rbp + {}], rdx", offset - 8));
}
Stmt::If {
condition,
then_branch,
else_branch,
} => {
let else_label = self.new_label("else");
let end_label = self.new_label("endif");
let cond_type = self.gen_expr(condition);
if cond_type.is_integer() {
self.emit(" test eax, eax");
self.emit(&format!(" je {}", else_label));
} else {
self.emit(" xorpd xmm1, xmm1");
self.emit(" ucomisd xmm0, xmm1");
self.emit(&format!(" je {}", else_label));
}
for s in then_branch {
self.gen_stmt(s);
}
self.emit(&format!(" jmp {}", end_label));
self.emit_label(&else_label);
if let Some(eb) = else_branch {
for s in eb {
self.gen_stmt(s);
}
}
self.emit_label(&end_label);
}
Stmt::For {
var,
start,
end,
step,
body,
} => {
let start_label = self.new_label("for");
let end_label = self.new_label("endfor");
let var_offset = self.get_var_offset(var);
let start_type = self.gen_expr(start);
self.gen_coercion(start_type, DataType::Double);
self.emit(&format!(" movsd QWORD PTR [rbp + {}], xmm0", var_offset));
self.stack_offset -= 8;
let end_offset = self.stack_offset;
let end_type = self.gen_expr(end);
self.gen_coercion(end_type, DataType::Double);
self.emit(&format!(" movsd QWORD PTR [rbp + {}], xmm0", end_offset));
self.stack_offset -= 8;
let step_offset = self.stack_offset;
if let Some(s) = step {
let step_type = self.gen_expr(s);
self.gen_coercion(step_type, DataType::Double);
} else {
self.emit(" mov rax, 0x3FF0000000000000 # 1.0");
self.emit(" movq xmm0, rax");
}
self.emit(&format!(
" movsd QWORD PTR [rbp + {}], xmm0",
step_offset
));
self.emit_label(&start_label);
self.emit(&format!(" movsd xmm0, QWORD PTR [rbp + {}]", var_offset));
self.emit(&format!(" movsd xmm1, QWORD PTR [rbp + {}]", end_offset));
self.emit(&format!(
" movsd xmm2, QWORD PTR [rbp + {}]",
step_offset
));
self.emit(" xorpd xmm3, xmm3");
self.emit(" ucomisd xmm2, xmm3");
self.emit(&format!(" jb .Lfor_neg_{}", self.label_counter));
self.emit(" ucomisd xmm0, xmm1");
self.emit(&format!(" ja {}", end_label));
self.emit(&format!(" jmp .Lfor_body_{}", self.label_counter));
self.emit_label(&format!(".Lfor_neg_{}", self.label_counter));
self.emit(" ucomisd xmm0, xmm1");
self.emit(&format!(" jb {}", end_label));
self.emit_label(&format!(".Lfor_body_{}", self.label_counter));
self.label_counter += 1;
for s in body {
self.gen_stmt(s);
}
self.emit(&format!(" movsd xmm0, QWORD PTR [rbp + {}]", var_offset));
self.emit(&format!(
" addsd xmm0, QWORD PTR [rbp + {}]",
step_offset
));
self.emit(&format!(" movsd QWORD PTR [rbp + {}], xmm0", var_offset));
self.emit(&format!(" jmp {}", start_label));
self.emit_label(&end_label);
}
Stmt::While { condition, body } => {
let start_label = self.new_label("while");
let end_label = self.new_label("endwhile");
self.emit_label(&start_label);
let cond_type = self.gen_expr(condition);
if cond_type.is_integer() {
self.emit(" test eax, eax");
self.emit(&format!(" je {}", end_label));
} else {
self.emit(" xorpd xmm1, xmm1");
self.emit(" ucomisd xmm0, xmm1");
self.emit(&format!(" je {}", end_label));
}
for s in body {
self.gen_stmt(s);
}
self.emit(&format!(" jmp {}", start_label));
self.emit_label(&end_label);
}
Stmt::DoLoop {
condition,
cond_at_start,
is_until,
body,
} => {
let start_label = self.new_label("do");
let end_label = self.new_label("enddo");
self.emit_label(&start_label);
if *cond_at_start {
if let Some(cond) = condition {
let cond_type = self.gen_expr(cond);
if cond_type.is_integer() {
self.emit(" test eax, eax");
if *is_until {
self.emit(&format!(" jne {}", end_label));
} else {
self.emit(&format!(" je {}", end_label));
}
} else {
self.emit(" xorpd xmm1, xmm1");
self.emit(" ucomisd xmm0, xmm1");
if *is_until {
self.emit(&format!(" jne {}", end_label));
} else {
self.emit(&format!(" je {}", end_label));
}
}
}
}
for s in body {
self.gen_stmt(s);
}
if !*cond_at_start {
if let Some(cond) = condition {
let cond_type = self.gen_expr(cond);
if cond_type.is_integer() {
self.emit(" test eax, eax");
if *is_until {
self.emit(&format!(" je {}", start_label));
} else {
self.emit(&format!(" jne {}", start_label));
}
} else {
self.emit(" xorpd xmm1, xmm1");
self.emit(" ucomisd xmm0, xmm1");
if *is_until {
self.emit(&format!(" je {}", start_label));
} else {
self.emit(&format!(" jne {}", start_label));
}
}
} else {
self.emit(&format!(" jmp {}", start_label));
}
} else {
self.emit(&format!(" jmp {}", start_label));
}
self.emit_label(&end_label);
}
Stmt::Goto(target) => {
let label = match target {
GotoTarget::Line(n) => format!("_line_{}", n),
GotoTarget::Label(s) => format!("_label_{}", s),
};
self.emit(&format!(" jmp {}", label));
}
Stmt::Gosub(target) => {
let label = match target {
GotoTarget::Line(n) => format!("_line_{}", n),
GotoTarget::Label(s) => format!("_label_{}", s),
};
let ret_label = self.new_label("gosub_ret");
self.emit(&format!(" lea rax, [rip + {}]", ret_label));
self.emit(" mov rdi, QWORD PTR [rip + _gosub_sp]");
self.emit(" sub rdi, 8");
self.emit(" mov QWORD PTR [rdi], rax");
self.emit(" mov QWORD PTR [rip + _gosub_sp], rdi");
self.emit(&format!(" jmp {}", label));
self.emit_label(&ret_label);
}
Stmt::Return => {
self.emit(" mov rdi, QWORD PTR [rip + _gosub_sp]");
self.emit(" mov rax, QWORD PTR [rdi]");
self.emit(" add rdi, 8");
self.emit(" mov QWORD PTR [rip + _gosub_sp], rdi");
self.emit(" jmp rax");
}
Stmt::OnGoto { expr, targets } => {
let expr_type = self.gen_expr(expr);
if expr_type.is_integer() {
self.emit(" movsxd rax, eax");
} else {
self.emit(" cvttsd2si rax, xmm0");
}
for (i, target) in targets.iter().enumerate() {
let label = match target {
GotoTarget::Line(n) => format!("_line_{}", n),
GotoTarget::Label(s) => format!("_label_{}", s),
};
self.emit(&format!(" cmp rax, {}", i + 1));
self.emit(&format!(" je {}", label));
}
}
Stmt::Dim { arrays } => {
for arr in arrays {
self.gen_dim_array(arr);
}
}
Stmt::Sub { .. } | Stmt::Function { .. } => {
}
Stmt::Call { name, args } => {
self.gen_call(name, args);
}
Stmt::Data(_) => {
}
Stmt::Read(vars) => {
for var in vars {
if is_string_var(var) {
self.emit(" call _rt_read_string");
let offset = self.get_var_offset(var);
self.emit(&format!(" mov QWORD PTR [rbp + {}], rax", offset));
} else {
self.emit(" call _rt_read_number");
let offset = self.get_var_offset(var);
self.emit(&format!(" movsd QWORD PTR [rbp + {}], xmm0", offset));
}
}
}
Stmt::Restore(target) => {
let idx = if let Some(_t) = target {
0
} else {
0
};
self.emit(&format!(" mov rdi, {}", idx));
self.emit(" call _rt_restore");
}
Stmt::Cls => {
self.emit(" call _rt_cls");
}
Stmt::SelectCase { expr, cases } => {
let end_label = self.new_label("endselect");
let expr_type = self.gen_expr(expr);
self.gen_coercion(expr_type, DataType::Double);
self.stack_offset -= 8;
let temp_offset = self.stack_offset;
self.emit(&format!(
" movsd QWORD PTR [rbp + {}], xmm0",
temp_offset
));
for (i, (case_value, body)) in cases.iter().enumerate() {
let next_case_label = if i + 1 < cases.len() {
self.new_label("case")
} else {
end_label.clone()
};
if let Some(value) = case_value {
let val_type = self.gen_expr(value);
self.gen_coercion(val_type, DataType::Double);
self.emit(&format!(
" movsd xmm1, QWORD PTR [rbp + {}]",
temp_offset
));
self.emit(" ucomisd xmm0, xmm1");
self.emit(&format!(" jne {}", next_case_label));
}
for stmt in body {
self.gen_stmt(stmt);
}
if i + 1 < cases.len() {
self.emit(&format!(" jmp {}", end_label));
self.emit_label(&next_case_label);
}
}
self.emit_label(&end_label);
}
Stmt::End | Stmt::Stop => {
self.emit(" xor eax, eax");
self.emit(" leave");
self.emit(" ret");
}
Stmt::Open {
filename,
mode,
file_num,
} => {
self.gen_expr(filename);
self.emit(" mov rdi, rax # filename ptr");
self.emit(" mov rsi, rdx # filename len");
let mode_num = match mode {
FileMode::Input => 0,
FileMode::Output => 1,
FileMode::Append => 2,
};
self.emit(&format!(" mov rdx, {} # mode", mode_num));
self.emit(&format!(" mov rcx, {} # file number", file_num));
self.emit(" call _rt_file_open");
}
Stmt::Close { file_num } => {
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" call _rt_file_close");
}
Stmt::PrintFile {
file_num,
items,
newline,
} => {
for item in items {
match item {
PrintItem::Expr(expr) => {
self.gen_print_expr_to_file(expr, *file_num);
}
PrintItem::Tab => {
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" mov rsi, 9 # tab");
self.emit(" call _rt_file_print_char");
}
PrintItem::Empty => {}
}
}
if *newline {
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" call _rt_file_print_newline");
}
}
Stmt::InputFile { file_num, vars } => {
for var in vars {
if is_string_var(var) {
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" call _rt_file_input_string");
let offset = self.get_var_offset(var);
self.emit(&format!(" mov QWORD PTR [rbp + {}], rax", offset));
self.emit(&format!(" mov QWORD PTR [rbp + {}], rdx", offset - 8));
} else {
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" call _rt_file_input_number");
let offset = self.get_var_offset(var);
self.emit(&format!(" movsd QWORD PTR [rbp + {}], xmm0", offset));
}
}
}
}
}
fn gen_expr(&mut self, expr: &Expr) -> DataType {
match expr {
Expr::Literal(lit) => match lit {
Literal::Integer(n) => {
self.emit(&format!(" mov eax, {}", *n as i32));
DataType::Long
}
Literal::Float(f) => {
let bits = f.to_bits();
self.emit(&format!(" mov rax, 0x{:X}", bits));
self.emit(" movq xmm0, rax");
DataType::Double
}
Literal::String(s) => {
let idx = self.add_string_literal(s);
self.emit(&format!(" lea rax, [rip + _str_{}]", idx));
self.emit(&format!(" mov rdx, {}", s.len()));
DataType::String
}
},
Expr::Variable(name) => {
let info = self.get_var_info(name);
match info.data_type {
DataType::Integer => {
self.emit(&format!(" movsx eax, WORD PTR [rbp + {}]", info.offset));
}
DataType::Long => {
self.emit(&format!(" mov eax, DWORD PTR [rbp + {}]", info.offset));
}
DataType::Single => {
self.emit(&format!(
" movss xmm0, DWORD PTR [rbp + {}]",
info.offset
));
}
DataType::Double => {
self.emit(&format!(
" movsd xmm0, QWORD PTR [rbp + {}]",
info.offset
));
}
DataType::String => {
self.emit(&format!(" mov rax, QWORD PTR [rbp + {}]", info.offset));
self.emit(&format!(
" mov rdx, QWORD PTR [rbp + {}]",
info.offset - 8
));
}
}
info.data_type
}
Expr::ArrayAccess { name, indices } => {
self.gen_array_load(name, indices);
DataType::from_suffix(name)
}
Expr::Unary { op, operand } => {
let operand_type = self.gen_expr(operand);
match op {
UnaryOp::Neg => {
if operand_type.is_integer() {
self.emit(" neg eax");
operand_type
} else {
if operand_type == DataType::Single {
self.emit(" mov eax, 0x80000000");
self.emit(" movd xmm1, eax");
self.emit(" xorps xmm0, xmm1");
} else {
self.emit(" mov rax, 0x8000000000000000");
self.emit(" movq xmm1, rax");
self.emit(" xorpd xmm0, xmm1");
}
operand_type
}
}
UnaryOp::Not => {
if operand_type.is_integer() {
self.emit(" test eax, eax");
} else if operand_type == DataType::Single {
self.emit(" xorps xmm1, xmm1");
self.emit(" ucomiss xmm0, xmm1");
} else {
self.emit(" xorpd xmm1, xmm1");
self.emit(" ucomisd xmm0, xmm1");
}
self.emit(" sete al");
self.emit(" movzx eax, al");
self.emit(" neg eax");
DataType::Long
}
}
}
Expr::Binary { op, left, right } => self.gen_binary_expr(*op, left, right),
Expr::FnCall { name, args } => {
self.gen_fn_call(name, args);
self.fn_return_type(name)
}
}
}
fn gen_binary_expr(&mut self, op: BinaryOp, left: &Expr, right: &Expr) -> DataType {
let result_type = self.promote_types(self.expr_type(left), self.expr_type(right), op);
if result_type == DataType::String && op == BinaryOp::Add {
self.gen_expr(left);
self.emit(" push rdx"); self.emit(" push rax");
self.gen_expr(right);
self.emit(" mov rcx, rdx"); self.emit(" mov rdx, rax"); self.emit(" pop rdi"); self.emit(" pop rsi"); self.emit(" call _rt_strcat");
return DataType::String;
}
let work_type = if matches!(
op,
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Gt
| BinaryOp::Le
| BinaryOp::Ge
| BinaryOp::And
| BinaryOp::Or
| BinaryOp::Xor
) {
self.promote_types(self.expr_type(left), self.expr_type(right), BinaryOp::Add)
} else {
result_type
};
let left_type = self.gen_expr(left);
self.gen_coercion(left_type, work_type);
self.emit(" sub rsp, 16");
if work_type.is_integer() {
self.emit(" mov QWORD PTR [rsp], rax");
} else if work_type == DataType::Single {
self.emit(" movss DWORD PTR [rsp], xmm0");
} else {
self.emit(" movsd QWORD PTR [rsp], xmm0");
}
let right_type = self.gen_expr(right);
self.gen_coercion(right_type, work_type);
if work_type.is_integer() {
self.emit(" mov ecx, eax"); self.emit(" mov rax, QWORD PTR [rsp]"); } else if work_type == DataType::Single {
self.emit(" movss xmm1, xmm0"); self.emit(" movss xmm0, DWORD PTR [rsp]"); } else {
self.emit(" movsd xmm1, xmm0"); self.emit(" movsd xmm0, QWORD PTR [rsp]"); }
self.emit(" add rsp, 16");
match op {
BinaryOp::Add => self.emit_typed(
work_type,
" add eax, ecx",
" addss xmm0, xmm1",
" addsd xmm0, xmm1",
),
BinaryOp::Sub => self.emit_typed(
work_type,
" sub eax, ecx",
" subss xmm0, xmm1",
" subsd xmm0, xmm1",
),
BinaryOp::Mul => self.emit_typed(
work_type,
" imul eax, ecx",
" mulss xmm0, xmm1",
" mulsd xmm0, xmm1",
),
BinaryOp::Div => {
self.emit_cvt_to_double(work_type);
self.emit(" divsd xmm0, xmm1");
}
BinaryOp::IntDiv => {
self.emit_cvt_float_to_int(work_type);
self.emit(" cdq");
self.emit(" idiv ecx");
}
BinaryOp::Mod => {
self.emit_cvt_float_to_int(work_type);
self.emit(" cdq");
self.emit(" idiv ecx");
self.emit(" mov eax, edx");
}
BinaryOp::Pow => {
self.emit_cvt_to_double(work_type);
self.emit(&format!(" call {}pow", PREFIX));
}
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Gt
| BinaryOp::Le
| BinaryOp::Ge => {
let (signed, unsigned) = match op {
BinaryOp::Eq => ("sete", "sete"),
BinaryOp::Ne => ("setne", "setne"),
BinaryOp::Lt => ("setl", "setb"),
BinaryOp::Gt => ("setg", "seta"),
BinaryOp::Le => ("setle", "setbe"),
BinaryOp::Ge => ("setge", "setae"),
_ => unreachable!(),
};
self.emit_typed(
work_type,
" cmp eax, ecx",
" ucomiss xmm0, xmm1",
" ucomisd xmm0, xmm1",
);
let setcc = if work_type.is_integer() {
signed
} else {
unsigned
};
self.emit(&format!(" {} al", setcc));
self.emit(" movzx eax, al");
self.emit(" neg eax");
}
BinaryOp::And | BinaryOp::Or | BinaryOp::Xor => {
self.emit_cvt_float_to_int(work_type);
let instr = match op {
BinaryOp::And => "and",
BinaryOp::Or => "or",
BinaryOp::Xor => "xor",
_ => unreachable!(),
};
self.emit(&format!(" {} eax, ecx", instr));
}
}
result_type
}
fn gen_print_expr(&mut self, expr: &Expr) {
let expected_type = self.expr_type(expr);
if expected_type == DataType::String {
self.gen_expr(expr);
self.emit(" mov rdi, rax");
self.emit(" mov rsi, rdx");
self.emit(" call _rt_print_string");
} else {
let expr_type = self.gen_expr(expr);
self.gen_coercion(expr_type, DataType::Double);
self.emit(" call _rt_print_float");
}
}
fn gen_print_expr_to_file(&mut self, expr: &Expr, file_num: i32) {
let expected_type = self.expr_type(expr);
if expected_type == DataType::String {
self.gen_expr(expr);
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" mov rsi, rax");
self.emit(" mov rdx, rdx"); self.emit(" call _rt_file_print_string");
} else {
let expr_type = self.gen_expr(expr);
self.gen_coercion(expr_type, DataType::Double);
self.emit(&format!(" mov rdi, {}", file_num));
self.emit(" call _rt_file_print_float");
}
}
fn gen_fn_call(&mut self, name: &str, args: &[Expr]) {
let upper_name = name.to_uppercase();
if let Some(libc_fn) = LIBC_MATH_FNS.get(upper_name.as_str()) {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
self.emit(&format!(" call {}{}", PREFIX, libc_fn));
return;
}
if let Some(instr) = INLINE_MATH_FNS.get(upper_name.as_str()) {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
self.emit(&format!(" {}", instr));
return;
}
match upper_name.as_str() {
"ABS" => {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
self.emit(" mov rax, 0x7FFFFFFFFFFFFFFF");
self.emit(" movq xmm1, rax");
self.emit(" andpd xmm0, xmm1");
}
"SGN" => {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
self.emit(" xorpd xmm1, xmm1");
self.emit(" ucomisd xmm0, xmm1");
self.emit(" seta al");
self.emit(" movzx eax, al");
self.emit(" setb cl");
self.emit(" movzx ecx, cl");
self.emit(" sub eax, ecx");
self.emit(" cvtsi2sd xmm0, eax");
}
"RND" => {
if !args.is_empty() {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
}
self.emit(" call _rt_rnd");
}
"LEN" => {
self.gen_expr(&args[0]);
self.emit(" mov eax, edx"); }
"LEFT$" => {
self.gen_expr(&args[0]); self.emit(" mov rdi, rax");
self.emit(" mov rsi, rdx");
let count_type = self.gen_expr(&args[1]); if count_type.is_integer() {
self.emit(" movsxd rdx, eax");
} else {
self.emit(" cvttsd2si rdx, xmm0");
}
self.emit(" call _rt_left");
}
"RIGHT$" => {
self.gen_expr(&args[0]);
self.emit(" mov rdi, rax");
self.emit(" mov rsi, rdx");
let count_type = self.gen_expr(&args[1]);
if count_type.is_integer() {
self.emit(" movsxd rdx, eax");
} else {
self.emit(" cvttsd2si rdx, xmm0");
}
self.emit(" call _rt_right");
}
"MID$" => {
self.gen_expr(&args[0]);
self.emit(" mov rdi, rax");
self.emit(" mov rsi, rdx");
let pos_type = self.gen_expr(&args[1]);
if pos_type.is_integer() {
self.emit(" movsxd rdx, eax");
} else {
self.emit(" cvttsd2si rdx, xmm0");
}
if args.len() > 2 {
let len_type = self.gen_expr(&args[2]);
if len_type.is_integer() {
self.emit(" movsxd rcx, eax");
} else {
self.emit(" cvttsd2si rcx, xmm0");
}
} else {
self.emit(" mov rcx, -1"); }
self.emit(" call _rt_mid");
}
"INSTR" => {
let (start_arg, hay_arg, needle_arg) = if args.len() == 3 {
(Some(&args[0]), &args[1], &args[2])
} else {
(None, &args[0], &args[1])
};
if let Some(start) = start_arg {
let start_type = self.gen_expr(start);
if start_type.is_integer() {
self.emit(" movsxd r8, eax");
} else {
self.emit(" cvttsd2si r8, xmm0");
}
} else {
self.emit(" mov r8, 1");
}
self.gen_expr(hay_arg);
self.emit(" mov rdi, rax");
self.emit(" mov rsi, rdx");
self.gen_expr(needle_arg);
self.emit(" mov rcx, rdx"); self.emit(" mov rdx, rax"); self.emit(" call _rt_instr");
self.emit(" mov eax, eax"); }
"ASC" => {
self.gen_expr(&args[0]);
self.emit(" movzx eax, BYTE PTR [rax]");
}
"CHR$" => {
let arg_type = self.gen_expr(&args[0]);
if arg_type.is_integer() {
self.emit(" movsxd rdi, eax");
} else {
self.emit(" cvttsd2si rdi, xmm0");
}
self.emit(" call _rt_chr");
}
"VAL" => {
self.gen_expr(&args[0]);
self.emit(" mov rdi, rax");
self.emit(" mov rsi, rdx");
self.emit(" call _rt_val");
}
"STR$" => {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
self.emit(" call _rt_str");
}
"CINT" | "CLNG" => {
let arg_type = self.gen_expr(&args[0]);
if !arg_type.is_integer() {
self.gen_coercion(arg_type, DataType::Double);
self.emit(" cvtsd2si eax, xmm0");
}
}
"CSNG" | "CDBL" => {
let arg_type = self.gen_expr(&args[0]);
self.gen_coercion(arg_type, DataType::Double);
}
"TIMER" => {
self.emit(" call _rt_timer");
}
_ => {
if self.arrays.contains_key(&upper_name) || upper_name.ends_with('$') {
self.gen_array_load(&upper_name, args);
} else {
self.gen_call(name, args);
}
}
}
}
fn gen_call(&mut self, name: &str, args: &[Expr]) {
let int_regs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
if !args.is_empty() {
self.emit(" sub rsp, 16");
self.emit(" movsd QWORD PTR [rsp], xmm0");
}
let mut reg_idx = 0;
for arg in args.iter() {
let arg_type = self.gen_expr(arg);
if arg_type == DataType::String {
if reg_idx < int_regs.len() {
self.emit(&format!(" mov {}, rax", int_regs[reg_idx]));
reg_idx += 1;
}
if reg_idx < int_regs.len() {
self.emit(&format!(" mov {}, rdx", int_regs[reg_idx]));
reg_idx += 1;
}
} else {
self.gen_coercion(arg_type, DataType::Double);
if reg_idx < int_regs.len() {
self.emit(&format!(" movq {}, xmm0", int_regs[reg_idx]));
reg_idx += 1;
}
}
}
self.emit(&format!(" call _proc_{}", name));
if !args.is_empty() {
self.emit(" add rsp, 16");
}
}
fn gen_dim_array(&mut self, arr: &ArrayDecl) {
let elem_size = if is_string_var(&arr.name) { 16 } else { 8 };
let mut dim_offsets = Vec::new();
for dim in arr.dimensions.iter() {
let dim_type = self.gen_expr(dim);
if dim_type.is_integer() {
self.emit(" movsxd rax, eax");
} else {
self.emit(" cvttsd2si rax, xmm0");
}
self.emit(" inc rax"); self.stack_offset -= 8;
dim_offsets.push(self.stack_offset);
self.emit(&format!(
" mov QWORD PTR [rbp + {}], rax",
self.stack_offset
));
}
self.emit(&format!(
" mov rax, QWORD PTR [rbp + {}]",
dim_offsets[0]
));
for offset in dim_offsets.iter().skip(1) {
self.emit(&format!(" imul rax, QWORD PTR [rbp + {}]", offset));
}
self.emit(&format!(" imul rdi, rax, {}", elem_size));
self.emit(&format!(" call {}malloc", PREFIX));
self.stack_offset -= 8;
let ptr_offset = self.stack_offset;
self.emit(&format!(" mov QWORD PTR [rbp + {}], rax", ptr_offset));
self.arrays.insert(
arr.name.clone(),
ArrayInfo {
ptr_offset,
dim_offsets,
},
);
}
fn gen_array_load(&mut self, name: &str, indices: &[Expr]) {
let arr_info = self.arrays.get(name).expect("Array not declared");
let ptr_offset = arr_info.ptr_offset;
let dim_offsets = arr_info.dim_offsets.clone();
let elem_size = if is_string_var(name) { 16 } else { 8 };
let idx_type = self.gen_expr(&indices[0]);
if idx_type.is_integer() {
self.emit(" movsxd rax, eax");
} else {
self.emit(" cvttsd2si rax, xmm0");
}
for (i, idx_expr) in indices.iter().enumerate().skip(1) {
self.emit(" sub rsp, 16");
self.emit(" mov QWORD PTR [rsp], rax");
let idx_type = self.gen_expr(idx_expr);
if idx_type.is_integer() {
self.emit(" movsxd rcx, eax");
} else {
self.emit(" cvttsd2si rcx, xmm0");
}
self.emit(" mov rax, QWORD PTR [rsp]");
self.emit(" add rsp, 16");
self.emit(&format!(
" imul rax, QWORD PTR [rbp + {}]",
dim_offsets[i]
));
self.emit(" add rax, rcx");
}
self.emit(&format!(" imul rax, {}", elem_size));
self.emit(&format!(" add rax, QWORD PTR [rbp + {}]", ptr_offset));
if is_string_var(name) {
self.emit(" mov rcx, rax");
self.emit(" mov rax, QWORD PTR [rcx]");
self.emit(" mov rdx, QWORD PTR [rcx + 8]");
} else {
self.emit(" movsd xmm0, QWORD PTR [rax]");
}
}
fn gen_array_store(&mut self, name: &str, indices: &[Expr], value: &Expr) {
let arr_info = self.arrays.get(name).expect("Array not declared");
let ptr_offset = arr_info.ptr_offset;
let dim_offsets = arr_info.dim_offsets.clone();
let elem_size = if is_string_var(name) { 16 } else { 8 };
let idx_type = self.gen_expr(&indices[0]);
if idx_type.is_integer() {
self.emit(" movsxd rax, eax");
} else {
self.emit(" cvttsd2si rax, xmm0");
}
for (i, idx_expr) in indices.iter().enumerate().skip(1) {
self.emit(" sub rsp, 16");
self.emit(" mov QWORD PTR [rsp], rax");
let idx_type = self.gen_expr(idx_expr);
if idx_type.is_integer() {
self.emit(" movsxd rcx, eax");
} else {
self.emit(" cvttsd2si rcx, xmm0");
}
self.emit(" mov rax, QWORD PTR [rsp]");
self.emit(" add rsp, 16");
self.emit(&format!(
" imul rax, QWORD PTR [rbp + {}]",
dim_offsets[i]
));
self.emit(" add rax, rcx");
}
self.emit(&format!(" imul rax, {}", elem_size));
self.emit(&format!(" add rax, QWORD PTR [rbp + {}]", ptr_offset));
self.emit(" sub rsp, 16");
self.emit(" mov QWORD PTR [rsp], rax");
let val_type = self.gen_expr(value);
self.emit(" mov rcx, QWORD PTR [rsp]");
self.emit(" add rsp, 16");
if is_string_var(name) {
self.emit(" mov QWORD PTR [rcx], rax");
self.emit(" mov QWORD PTR [rcx + 8], rdx");
} else {
self.gen_coercion(val_type, DataType::Double);
self.emit(" movsd QWORD PTR [rcx], xmm0");
}
}
fn gen_string_assign(&mut self, name: &str, value: &Expr) {
self.gen_expr(value);
let offset = self.get_var_offset(name);
self.stack_offset -= 8; self.emit(&format!(" mov QWORD PTR [rbp + {}], rax", offset));
self.emit(&format!(" mov QWORD PTR [rbp + {}], rdx", offset - 8));
}
fn emit_data_section(&mut self) {
self.output.push_str("\n.data\n");
let strings = self.string_literals.clone();
for (i, s) in strings.iter().enumerate() {
self.output.push_str(&format!("_str_{}:\n", i));
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
self.output
.push_str(&format!(" .ascii \"{}\"\n", escaped));
}
self.output.push_str("_data_table:\n");
let data_items = self.data_items.clone();
for item in &data_items {
match item {
Literal::Integer(n) => {
self.output.push_str(" .quad 0 # type int\n");
self.output.push_str(&format!(" .quad {}\n", n));
}
Literal::Float(f) => {
self.output.push_str(" .quad 1 # type float\n");
self.output
.push_str(&format!(" .quad 0x{:X}\n", f.to_bits()));
}
Literal::String(s) => {
let idx = self.string_literals.len();
self.string_literals.push(s.clone());
self.output.push_str(" .quad 2 # type string\n");
self.output.push_str(&format!(" .quad _str_{}\n", idx));
}
}
}
self.output
.push_str(&format!("_data_count: .quad {}\n", data_items.len()));
self.emit("_data_ptr: .quad 0");
if self.gosub_used {
self.emit("_gosub_sp: .quad 0");
}
self.emit("");
self.emit(".bss");
if self.gosub_used {
self.emit("_gosub_stack: .skip 8192 # GOSUB return stack");
}
}
}