use super::error::{ParseError, ProgramError};
use super::ir_builder::IrBuilder;
use super::linker::Linker;
use super::metadata::SymbolMetadata;
use super::parser::Parser;
use crate::ir::Instr;
use crate::number::Number;
use crate::span::SpanError;
use crate::symtable::SymTable;
use crate::vm::{Vm, VmError};
use colored::Colorize;
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthStr;
const PROGRAM_VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg(feature = "serialization")]
#[derive(Debug, Clone, Serialize, Deserialize)]
struct BinaryFormat {
version: String,
bytecode: Vec<Instr>,
symbols: Vec<SymbolMetadata>,
}
#[derive(Debug, Clone)]
pub enum ProgramOrigin {
#[cfg(feature = "serialization")]
File(String),
Source,
#[cfg(feature = "serialization")]
Bytecode,
}
#[derive(Debug)]
pub struct Program<'src, State> {
source: Option<&'src str>,
state: State,
}
#[derive(Debug)]
pub struct Compiled {
origin: ProgramOrigin,
version: String,
bytecode: Vec<Instr>,
symbols: Vec<SymbolMetadata>,
}
#[derive(Debug)]
pub struct Linked {
#[allow(dead_code)]
origin: ProgramOrigin,
version: String,
bytecode: Vec<Instr>,
symtable: SymTable,
}
impl<'src> Program<'src, Compiled> {
pub fn new_from_source(source: &'src str) -> Result<Self, ProgramError> {
let trimmed = source.trim();
let mut parser = Parser::new(trimmed);
let ast_opt = parser.parse().map_err(|parse_err| {
let highlighted = Self::highlight_error(trimmed, &parse_err);
ProgramError::ParseError(format!("{}\n{}", parse_err, highlighted))
})?;
let (bytecode, symbols) = if let Some(ast) = ast_opt {
IrBuilder::new().build(&ast)?
} else {
(Vec::new(), Vec::new())
};
Ok(Program {
source: Some(trimmed),
state: Compiled {
origin: ProgramOrigin::Source,
version: PROGRAM_VERSION.to_string(),
bytecode,
symbols,
},
})
}
#[cfg(feature = "serialization")]
pub fn new_from_file(path: impl Into<String>) -> Result<Self, ProgramError> {
let path_str = path.into();
let data = std::fs::read(&path_str)?;
Self::from_bytecode(&data, ProgramOrigin::File(path_str))
}
#[cfg(feature = "serialization")]
pub fn new_from_bytecode(data: &[u8]) -> Result<Self, ProgramError> {
Self::from_bytecode(data, ProgramOrigin::Bytecode)
}
pub fn link(self, table: SymTable) -> Result<Program<'src, Linked>, ProgramError> {
let linker = Linker::new(self.state.bytecode, self.state.symbols, table);
let (bytecode, symtable) = linker.link()?;
Ok(Program {
source: self.source,
state: Linked {
origin: self.state.origin,
version: self.state.version,
bytecode,
symtable,
},
})
}
#[cfg(feature = "serialization")]
fn from_bytecode(data: &[u8], origin: ProgramOrigin) -> Result<Self, ProgramError> {
let config = bincode::config::standard();
let (binary, _): (BinaryFormat, _) = bincode::serde::decode_from_slice(data, config)?;
if binary.version != PROGRAM_VERSION {
return Err(ProgramError::IncompatibleVersion {
expected: PROGRAM_VERSION.to_string(),
found: binary.version,
});
}
Ok(Program {
source: None, state: Compiled {
origin,
version: binary.version,
bytecode: binary.bytecode,
symbols: binary.symbols,
},
})
}
fn highlight_error(input: &str, error: &ParseError) -> String {
let span = error.span();
let pre = Self::escape(&input[..span.start]);
let tok = Self::escape(&input[span.start..span.end]);
let post = Self::escape(&input[span.end..]);
let line = format!("{}{}{}", pre, tok.red().bold(), post);
let caret = "^".green().bold();
let squiggly_len = UnicodeWidthStr::width(tok.as_str());
let caret_offset = UnicodeWidthStr::width(pre.as_str()) + caret.len();
format!(
"1 | {0}\n | {1: >2$}{3}",
line,
caret,
caret_offset,
"~".repeat(squiggly_len.saturating_sub(1)).green()
)
}
fn escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
other => out.push(other),
}
}
out
}
}
impl<'src> Program<'src, Linked> {
pub fn execute(&mut self) -> Result<Number, VmError> {
Vm::run(&self.state.bytecode, &mut self.state.symtable)
}
pub fn symtable_mut(&mut self) -> &mut SymTable {
&mut self.state.symtable
}
pub fn get_assembly(&self) -> String {
use std::fmt::Write as _;
let mut out = String::new();
out += &format!("; VERSION {}\n", self.state.version)
.bright_black()
.to_string();
for (i, instr) in self.state.bytecode.iter().enumerate() {
let _ = write!(out, "{} ", format!("{:04X}", i).yellow());
let line = match instr {
Instr::Push(v) => format!("{} {}", "PUSH".magenta(), v.to_string().green()),
Instr::Load(idx) => {
let sym_name = self
.state
.symtable
.get_by_index(*idx)
.map(|s| s.name())
.expect("Symbol not found in assembly");
format!("{} {}", "LOAD".magenta(), sym_name.blue())
}
Instr::Store(idx) => {
let sym_name = self
.state
.symtable
.get_by_index(*idx)
.map(|s| s.name())
.expect("Symbol not found in assembly");
format!("{} {}", "STORE".magenta(), sym_name.blue())
}
Instr::Neg => format!("{}", "NEG".magenta()),
Instr::Add => format!("{}", "ADD".magenta()),
Instr::Sub => format!("{}", "SUB".magenta()),
Instr::Mul => format!("{}", "MUL".magenta()),
Instr::Div => format!("{}", "DIV".magenta()),
Instr::Pow => format!("{}", "POW".magenta()),
Instr::Fact => format!("{}", "FACT".magenta()),
Instr::Call(idx, argc) => {
let sym_name = self
.state
.symtable
.get_by_index(*idx)
.map(|s| s.name())
.expect("Symbol not found in assembly");
format!(
"{} {} args: {}",
"CALL".magenta(),
sym_name.cyan(),
argc.to_string().bright_blue()
)
}
Instr::Equal => format!("{}", "EQ".magenta()),
Instr::NotEqual => format!("{}", "NEQ".magenta()),
Instr::Less => format!("{}", "LT".magenta()),
Instr::LessEqual => format!("{}", "LTE".magenta()),
Instr::Greater => format!("{}", "GT".magenta()),
Instr::GreaterEqual => format!("{}", "GTE".magenta()),
Instr::Jmp(target) => {
format!("{} {}", "JMP".magenta(), format!("{:04X}", target).yellow())
}
Instr::Jz(target) => {
format!("{} {}", "JZ".magenta(), format!("{:04X}", target).yellow())
}
};
let _ = writeln!(out, "{}", line);
}
out
}
#[cfg(feature = "serialization")]
pub fn to_bytecode(&self) -> Result<Vec<u8>, ProgramError> {
use std::collections::HashMap;
let mut reverse_map = HashMap::new();
let mut symbols = Vec::new();
let mut get_or_create_metadata = |idx: usize| -> usize {
if let Some(&existing) = reverse_map.get(&idx) {
existing
} else {
let symbol = self
.state
.symtable
.get_by_index(idx)
.expect("symbol index must be valid after linking");
let new_idx = symbols.len();
symbols.push(symbol.into());
reverse_map.insert(idx, new_idx);
new_idx
}
};
let bytecode: Vec<Instr> = self
.state
.bytecode
.iter()
.map(|instr| match instr {
Instr::Load(idx) => Instr::Load(get_or_create_metadata(*idx)),
Instr::Store(idx) => Instr::Store(get_or_create_metadata(*idx)),
Instr::Call(idx, argc) => Instr::Call(get_or_create_metadata(*idx), *argc),
other => other.clone(),
})
.collect();
let binary = BinaryFormat {
version: self.state.version.clone(),
bytecode,
symbols,
};
let config = bincode::config::standard();
Ok(bincode::serde::encode_to_vec(&binary, config)?)
}
#[cfg(feature = "serialization")]
pub fn save_bytecode_to_file(
&self,
path: impl AsRef<std::path::Path>,
) -> Result<(), ProgramError> {
let bytecode = self.to_bytecode()?;
std::fs::write(path, bytecode)?;
Ok(())
}
}