use crate::ast::ExprType;
use crate::bytecode::{self, ErrorHandlerMode, Register};
use crate::compiler::ids::HashMapWithIds;
use crate::compiler::{Error, Result, SymbolKey};
use crate::image::{GlobalVarInfo, Image, ImageDelta, InstrMetadata};
use crate::mem::ConstantDatum;
use crate::reader::LineCol;
use std::collections::HashMap;
use std::convert::TryFrom;
type Address = usize;
#[derive(Clone)]
pub(super) enum Fixup {
Call(Register, SymbolKey),
Gosub(String),
Goto(String),
OnErrorGoto(String),
}
#[derive(Clone, Default)]
pub(super) struct Codegen {
code: Vec<u32>,
constants: HashMapWithIds<ConstantDatum, ExprType, u16>,
fixups: HashMap<Address, Fixup>,
instrs: Vec<InstrMetadata>,
labels: HashMap<SymbolKey, Address>,
user_callables_addresses: HashMap<SymbolKey, (Address, Address)>,
upcalls: HashMapWithIds<SymbolKey, Option<ExprType>, u16>,
}
impl Codegen {
pub(super) fn next_pc(&self) -> Address {
self.code.len()
}
pub(super) fn emit(&mut self, op: u32, pos: LineCol) -> Address {
self.code.push(op);
self.instrs.push(InstrMetadata {
linecol: pos,
is_stmt_start: false,
arg_linecols: vec![],
});
self.code.len() - 1
}
pub(super) fn mark_statement_start(&mut self, addr: Address) {
self.instrs[addr].is_stmt_start = true;
}
pub(super) fn set_arg_linecols(&mut self, addr: Address, arg_linecols: Vec<LineCol>) {
self.instrs[addr].arg_linecols = arg_linecols;
}
pub(super) fn pop_eof(&mut self) {
if let Some(instr) = self.code.pop() {
debug_assert_eq!(bytecode::make_eof(), instr);
self.instrs.pop();
}
}
pub(super) fn emit_default(&mut self, reg: Register, vtype: ExprType, pos: LineCol) {
let instr = match vtype {
ExprType::Boolean | ExprType::Double | ExprType::Integer => {
bytecode::make_load_integer(reg, 0)
}
ExprType::Text => bytecode::make_alloc(reg, ExprType::Text),
};
self.emit(instr, pos);
}
pub(super) fn emit_value(
&mut self,
reg: Register,
datum: ConstantDatum,
pos: LineCol,
) -> Result<()> {
match datum {
ConstantDatum::Boolean(b) => {
self.emit(bytecode::make_load_integer(reg, if b { 1 } else { 0 }), pos);
}
ConstantDatum::Integer(i) => {
if let Ok(v) = u16::try_from(i) {
self.emit(bytecode::make_load_integer(reg, v), pos);
} else {
let idx = self.get_constant(ConstantDatum::Integer(i), pos)?;
self.emit(bytecode::make_load_constant(reg, idx), pos);
}
}
ConstantDatum::Double(d) => {
let idx = self.get_constant(ConstantDatum::Double(d), pos)?;
self.emit(bytecode::make_load_constant(reg, idx), pos);
}
ConstantDatum::Text(s) => {
let idx = self.get_constant(ConstantDatum::Text(s), pos)?;
self.emit(bytecode::make_load_integer(reg, idx), pos);
}
}
Ok(())
}
pub(super) fn patch(&mut self, addr: Address, op: u32) {
self.code[addr] = op;
}
pub(super) fn add_fixup(&mut self, addr: usize, fixup: Fixup) {
let previous = self.fixups.insert(addr, fixup);
debug_assert!(previous.is_none(), "Cannot handle more than one fixup per address");
}
pub(super) fn get_constant(&mut self, constant: ConstantDatum, pos: LineCol) -> Result<u16> {
match self.constants.get(&constant) {
Some((_etype, id)) => Ok(id),
None => {
let etype = constant.etype();
match self.constants.insert(constant, etype) {
Some((_etype, id)) => Ok(id),
None => Err(Error::OutOfConstants(pos)),
}
}
}
}
pub(super) fn define_user_callable(&mut self, key: SymbolKey, start: Address, end: Address) {
self.user_callables_addresses.insert(key, (start, end));
}
pub(super) fn define_label(&mut self, key: SymbolKey, address: Address) -> bool {
self.labels.insert(key, address).is_none()
}
fn make_target(target: usize, pos: LineCol) -> Result<u16> {
match u16::try_from(target) {
Ok(num) => Ok(num),
Err(_) => Err(Error::TargetTooFar(pos, target)),
}
}
fn apply_fixups(&mut self) -> Result<()> {
for (addr, fixup) in self.fixups.drain() {
let pos = self.instrs[addr].linecol;
let instr = match fixup {
Fixup::Call(reg, key) => {
let (target, _end) =
self.user_callables_addresses.get(&key).expect("Must be present");
bytecode::make_call(reg, Self::make_target(*target, pos)?)
}
Fixup::Gosub(label) => {
let key = SymbolKey::from(&label);
let Some(target) = self.labels.get(&key) else {
return Err(Error::UnknownLabel(pos, label));
};
bytecode::make_gosub(Self::make_target(*target, pos)?)
}
Fixup::Goto(label) => {
let key = SymbolKey::from(&label);
let Some(target) = self.labels.get(&key) else {
return Err(Error::UnknownLabel(pos, label));
};
bytecode::make_jump(Self::make_target(*target, pos)?)
}
Fixup::OnErrorGoto(label) => {
let key = SymbolKey::from(&label);
let Some(target) = self.labels.get(&key) else {
return Err(Error::UnknownLabel(pos, label));
};
bytecode::make_set_error_handler(
ErrorHandlerMode::Jump,
Self::make_target(*target, pos)?,
)
}
};
self.code[addr] = instr;
}
debug_assert!(self.fixups.is_empty());
Ok(())
}
pub(super) fn get_upcall(
&mut self,
key: SymbolKey,
etype: Option<ExprType>,
pos: LineCol,
) -> Result<u16> {
match self.upcalls.get(&key) {
Some((_etype, id)) => Ok(id),
None => match self.upcalls.insert(key, etype) {
Some((_etype, id)) => Ok(id),
None => Err(Error::OutOfUpcalls(pos)),
},
}
}
pub(super) fn build_image_delta(
&mut self,
image: &Image,
global_vars: HashMap<SymbolKey, GlobalVarInfo>,
program_vars: HashMap<SymbolKey, GlobalVarInfo>,
data: &[Option<ConstantDatum>],
) -> Result<ImageDelta> {
self.apply_fixups()?;
let code_start = image.code.len().saturating_sub(1);
let constants_start = image.constants.len();
let instrs_start = image.debug_info.instrs.len().saturating_sub(1);
let upcalls_start = image.upcalls.len();
debug_assert_eq!(code_start, instrs_start);
debug_assert!(code_start <= self.code.len());
debug_assert!(constants_start <= self.constants.len());
debug_assert!(image.data.len() <= data.len());
debug_assert!(upcalls_start <= self.upcalls.len());
debug_assert_eq!(&image.code[..code_start], &self.code[..code_start]);
debug_assert_eq!(&image.debug_info.instrs[..instrs_start], &self.instrs[..instrs_start],);
debug_assert!(
self.constants.keys_to_vec().starts_with(image.constants.as_slice()),
"Image constants must match the compiler state prefix",
);
debug_assert!(
self.upcalls.keys_to_vec().starts_with(image.upcalls.as_slice()),
"Image upcalls must match the compiler state prefix",
);
debug_assert_eq!(image.data.as_slice(), &data[..image.data.len()]);
let mut callables = HashMap::default();
for (key, (start_pc, end_pc)) in &self.user_callables_addresses {
let previous = callables.insert(*start_pc, (key.clone(), true));
debug_assert!(previous.is_none(), "An address can only start one callable");
let previous = callables.insert(*end_pc, (key.clone(), false));
debug_assert!(previous.is_none(), "An address can only start one callable");
}
Ok(ImageDelta {
code: self.code[code_start..].to_vec(),
upcalls: self.upcalls.keys_to_vec_from(upcalls_start),
constants: self.constants.keys_to_vec_from(constants_start),
data: data[image.data.len()..].to_vec(),
instrs: self.instrs[instrs_start..].to_vec(),
callables,
global_vars,
program_vars,
})
}
#[cfg(test)]
pub(super) fn build_image(
&mut self,
global_vars: HashMap<SymbolKey, GlobalVarInfo>,
program_vars: HashMap<SymbolKey, GlobalVarInfo>,
data: Vec<Option<ConstantDatum>>,
) -> Result<Image> {
let mut image = Image::default();
let delta = self.build_image_delta(&image, global_vars, program_vars, &data)?;
image.append(delta);
Ok(image)
}
}