use crate::interpreter::{Instr, InstrPart};
use crate::types::Value;
const MAGIC_V1: &[u8; 4] = b"LKB1";
const MAGIC_V2: &[u8; 4] = b"LKB2";
const BYTECODE_VERSION: u16 = 1;
pub fn encode(instrs: &[Instr]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(MAGIC_V2);
write_u16(&mut out, BYTECODE_VERSION);
write_u16(&mut out, 0);
write_u32(&mut out, instrs.len() as u32);
for instr in instrs {
write_instr(&mut out, instr);
}
out
}
pub fn decode(bytes: &[u8]) -> Result<Vec<Instr>, String> {
if bytes.len() < 4 {
return Err("Invalid bytecode header".to_string());
}
if &bytes[..4] == MAGIC_V1 {
decode_v1(&bytes[4..])
} else if &bytes[..4] == MAGIC_V2 {
decode_v2(&bytes[4..])
} else {
Err("Invalid bytecode header".to_string())
}
}
fn decode_v1(bytes: &[u8]) -> Result<Vec<Instr>, String> {
let mut cur = Cursor::new(bytes);
let count = cur.read_u32()? as usize;
let mut instrs = Vec::with_capacity(count);
for _ in 0..count {
instrs.push(cur.read_instr()?);
}
Ok(instrs)
}
fn decode_v2(bytes: &[u8]) -> Result<Vec<Instr>, String> {
let mut cur = Cursor::new(bytes);
let version = cur.read_u16()?;
let _flags = cur.read_u16()?;
if version != BYTECODE_VERSION {
return Err(format!("Unsupported bytecode version {}", version));
}
let count = cur.read_u32()? as usize;
let mut instrs = Vec::with_capacity(count);
for _ in 0..count {
instrs.push(cur.read_instr()?);
}
Ok(instrs)
}
pub fn write_file(path: &str, instrs: &[Instr]) -> Result<(), String> {
let bytes = encode(instrs);
std::fs::write(path, bytes).map_err(|e| format!("Cannot write bytecode: {}", e))
}
pub fn read_file(path: &str) -> Result<Vec<Instr>, String> {
let bytes = std::fs::read(path).map_err(|e| format!("Cannot read bytecode: {}", e))?;
decode(&bytes)
}
fn write_instr(out: &mut Vec<u8>, instr: &Instr) {
match instr {
Instr::Push(v) => {
write_u8(out, 0);
write_value(out, v);
}
Instr::Load(name) => {
write_u8(out, 1);
write_string(out, name);
}
Instr::Store(name) => {
write_u8(out, 2);
write_string(out, name);
}
Instr::Pop => write_u8(out, 3),
Instr::Declare {
name,
is_mut,
type_hint,
} => {
write_u8(out, 4);
write_string(out, name);
write_u8(out, if *is_mut { 1 } else { 0 });
write_opt_string(out, type_hint.as_deref());
}
Instr::BinOp(op) => {
write_u8(out, 5);
write_string(out, op);
}
Instr::MakeArray(n) => {
write_u8(out, 6);
write_u32(out, *n as u32);
}
Instr::Range => write_u8(out, 7),
Instr::Call {
name,
argc,
line,
file,
} => {
out.push(8);
write_string(out, name);
write_usize(out, *argc);
write_usize(out, *line);
write_string(out, file);
}
Instr::If {
cond,
then_block,
else_block,
} => {
write_u8(out, 9);
write_instrs(out, cond);
write_instrs(out, then_block);
write_instrs(out, else_block);
}
Instr::Loop { body } => {
write_u8(out, 10);
write_instrs(out, body);
}
Instr::While { cond, body } => {
write_u8(out, 11);
write_instrs(out, cond);
write_instrs(out, body);
}
Instr::For { var, iter, body } => {
write_u8(out, 12);
write_string(out, var);
write_instrs(out, iter);
write_instrs(out, body);
}
Instr::FnDef { name, params, body } => {
write_u8(out, 13);
write_string(out, name);
write_strings(out, params);
write_instrs(out, body);
}
Instr::Return => write_u8(out, 14),
Instr::Break => write_u8(out, 15),
Instr::Switch {
value,
cases,
default,
} => {
write_u8(out, 16);
write_instrs(out, value);
write_u32(out, cases.len() as u32);
for (case_val, body) in cases {
write_instrs(out, case_val);
write_instrs(out, body);
}
match default {
Some(body) => {
write_u8(out, 1);
write_instrs(out, body);
}
None => write_u8(out, 0),
}
}
Instr::Try { body, catch_body } => {
write_u8(out, 17);
write_instrs(out, body);
write_instrs(out, catch_body);
}
Instr::Interpolated(parts) => {
write_u8(out, 18);
write_u32(out, parts.len() as u32);
for part in parts {
match part {
InstrPart::Text(t) => {
write_u8(out, 0);
write_string(out, t);
}
InstrPart::Expr(code) => {
write_u8(out, 1);
write_instrs(out, code);
}
}
}
}
Instr::Import(path) => {
write_u8(out, 19);
write_string(out, path);
}
Instr::StructDef { name, fields } => {
write_u8(out, 20);
write_string(out, name);
write_strings(out, fields);
}
}
}
fn write_instrs(out: &mut Vec<u8>, instrs: &[Instr]) {
write_u32(out, instrs.len() as u32);
for instr in instrs {
write_instr(out, instr);
}
}
fn write_strings(out: &mut Vec<u8>, strings: &[String]) {
write_u32(out, strings.len() as u32);
for s in strings {
write_string(out, s);
}
}
fn write_opt_string(out: &mut Vec<u8>, value: Option<&str>) {
match value {
Some(s) => {
write_u8(out, 1);
write_string(out, s);
}
None => write_u8(out, 0),
}
}
fn write_value(out: &mut Vec<u8>, value: &Value) {
match value {
Value::Null => write_u8(out, 0),
Value::Int(n) => {
write_u8(out, 1);
write_i64(out, *n);
}
Value::Float(n) => {
write_u8(out, 2);
write_f64(out, *n);
}
Value::Bool(b) => {
write_u8(out, 3);
write_u8(out, if *b { 1 } else { 0 });
}
Value::Str(s) => {
write_u8(out, 4);
write_string(out, s);
}
Value::Array(arr) => {
write_u8(out, 5);
write_u32(out, arr.len() as u32);
for v in arr {
write_value(out, v);
}
}
Value::Tuple(items) => {
write_u8(out, 6);
write_u32(out, items.len() as u32);
for v in items {
write_value(out, v);
}
}
Value::Map(map) => {
write_u8(out, 7);
write_u32(out, map.len() as u32);
for (k, v) in map {
write_string(out, k);
write_value(out, v);
}
}
Value::Set(items) => {
write_u8(out, 8);
write_u32(out, items.len() as u32);
for v in items {
write_value(out, v);
}
}
Value::Object(name, fields) => {
write_u8(out, 9);
write_string(out, name);
write_u32(out, fields.len() as u32);
for (k, v) in fields {
write_string(out, k);
write_value(out, v);
}
}
}
}
fn write_u8(out: &mut Vec<u8>, n: u8) {
out.push(n);
}
fn write_u32(out: &mut Vec<u8>, n: u32) {
out.extend_from_slice(&n.to_le_bytes());
}
fn write_u16(out: &mut Vec<u8>, n: u16) {
out.extend_from_slice(&n.to_le_bytes());
}
fn write_usize(out: &mut Vec<u8>, n: usize) {
write_u32(out, n as u32); }
fn write_i64(out: &mut Vec<u8>, n: i64) {
out.extend_from_slice(&n.to_le_bytes());
}
fn write_f64(out: &mut Vec<u8>, n: f64) {
out.extend_from_slice(&n.to_le_bytes());
}
fn write_string(out: &mut Vec<u8>, s: &str) {
write_u32(out, s.len() as u32);
out.extend_from_slice(s.as_bytes());
}
struct Cursor<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
fn read_u8(&mut self) -> Result<u8, String> {
if self.pos >= self.bytes.len() {
return Err("Unexpected end of bytecode".to_string());
}
let v = self.bytes[self.pos];
self.pos += 1;
Ok(v)
}
fn read_u32(&mut self) -> Result<u32, String> {
let mut buf = [0u8; 4];
self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u16(&mut self) -> Result<u16, String> {
let mut buf = [0u8; 2];
self.read_exact(&mut buf)?;
Ok(u16::from_le_bytes(buf))
}
fn read_usize(&mut self) -> Result<usize, String> {
Ok(self.read_u32()? as usize)
}
fn read_i64(&mut self) -> Result<i64, String> {
let mut buf = [0u8; 8];
self.read_exact(&mut buf)?;
Ok(i64::from_le_bytes(buf))
}
fn read_f64(&mut self) -> Result<f64, String> {
let mut buf = [0u8; 8];
self.read_exact(&mut buf)?;
Ok(f64::from_le_bytes(buf))
}
fn read_exact(&mut self, out: &mut [u8]) -> Result<(), String> {
if self.pos + out.len() > self.bytes.len() {
return Err("Unexpected end of bytecode".to_string());
}
out.copy_from_slice(&self.bytes[self.pos..self.pos + out.len()]);
self.pos += out.len();
Ok(())
}
fn read_string(&mut self) -> Result<String, String> {
let len = self.read_u32()? as usize;
if self.pos + len > self.bytes.len() {
return Err("Unexpected end of bytecode".to_string());
}
let s = std::str::from_utf8(&self.bytes[self.pos..self.pos + len])
.map_err(|_| "Invalid UTF-8 in bytecode".to_string())?;
self.pos += len;
Ok(s.to_string())
}
fn read_opt_string(&mut self) -> Result<Option<String>, String> {
let flag = self.read_u8()?;
if flag == 0 {
Ok(None)
} else {
Ok(Some(self.read_string()?))
}
}
fn read_value(&mut self) -> Result<Value, String> {
match self.read_u8()? {
0 => Ok(Value::Null),
1 => Ok(Value::Int(self.read_i64()?)),
2 => Ok(Value::Float(self.read_f64()?)),
3 => Ok(Value::Bool(self.read_u8()? != 0)),
4 => Ok(Value::Str(self.read_string()?)),
5 => {
let len = self.read_u32()? as usize;
let mut arr = Vec::with_capacity(len);
for _ in 0..len {
arr.push(self.read_value()?);
}
Ok(Value::Array(arr))
}
6 => {
let len = self.read_u32()? as usize;
let mut items = Vec::with_capacity(len);
for _ in 0..len {
items.push(self.read_value()?);
}
Ok(Value::Tuple(items))
}
7 => {
let len = self.read_u32()? as usize;
let mut map = std::collections::HashMap::with_capacity(len);
for _ in 0..len {
let key = self.read_string()?;
let val = self.read_value()?;
map.insert(key, val);
}
Ok(Value::Map(map))
}
8 => {
let len = self.read_u32()? as usize;
let mut items = Vec::with_capacity(len);
for _ in 0..len {
items.push(self.read_value()?);
}
Ok(Value::Set(items))
}
9 => {
let name = self.read_string()?;
let len = self.read_u32()? as usize;
let mut fields = std::collections::HashMap::with_capacity(len);
for _ in 0..len {
let key = self.read_string()?;
let val = self.read_value()?;
fields.insert(key, val);
}
Ok(Value::Object(name, fields))
}
_ => Err("Invalid value tag in bytecode".to_string()),
}
}
fn read_instrs(&mut self) -> Result<Vec<Instr>, String> {
let len = self.read_u32()? as usize;
let mut instrs = Vec::with_capacity(len);
for _ in 0..len {
instrs.push(self.read_instr()?);
}
Ok(instrs)
}
fn read_strings(&mut self) -> Result<Vec<String>, String> {
let len = self.read_u32()? as usize;
let mut out = Vec::with_capacity(len);
for _ in 0..len {
out.push(self.read_string()?);
}
Ok(out)
}
fn read_instr(&mut self) -> Result<Instr, String> {
match self.read_u8()? {
0 => Ok(Instr::Push(self.read_value()?)),
1 => Ok(Instr::Load(self.read_string()?)),
2 => Ok(Instr::Store(self.read_string()?)),
3 => Ok(Instr::Pop),
4 => Ok(Instr::Declare {
name: self.read_string()?,
is_mut: self.read_u8()? != 0,
type_hint: self.read_opt_string()?,
}),
5 => Ok(Instr::BinOp(self.read_string()?)),
6 => Ok(Instr::MakeArray(self.read_u32()? as usize)),
7 => Ok(Instr::Range),
8 => Ok(Instr::Call {
name: self.read_string()?,
argc: self.read_usize()?,
line: self.read_usize()?,
file: self.read_string()?,
}),
9 => Ok(Instr::If {
cond: self.read_instrs()?,
then_block: self.read_instrs()?,
else_block: self.read_instrs()?,
}),
10 => Ok(Instr::Loop {
body: self.read_instrs()?,
}),
11 => Ok(Instr::While {
cond: self.read_instrs()?,
body: self.read_instrs()?,
}),
12 => Ok(Instr::For {
var: self.read_string()?,
iter: self.read_instrs()?,
body: self.read_instrs()?,
}),
13 => Ok(Instr::FnDef {
name: self.read_string()?,
params: self.read_strings()?,
body: self.read_instrs()?,
}),
14 => Ok(Instr::Return),
15 => Ok(Instr::Break),
16 => {
let value = self.read_instrs()?;
let count = self.read_u32()? as usize;
let mut cases = Vec::with_capacity(count);
for _ in 0..count {
let case_val = self.read_instrs()?;
let body = self.read_instrs()?;
cases.push((case_val, body));
}
let has_default = self.read_u8()? != 0;
let default = if has_default {
Some(self.read_instrs()?)
} else {
None
};
Ok(Instr::Switch {
value,
cases,
default,
})
}
17 => Ok(Instr::Try {
body: self.read_instrs()?,
catch_body: self.read_instrs()?,
}),
18 => {
let count = self.read_u32()? as usize;
let mut parts = Vec::with_capacity(count);
for _ in 0..count {
let tag = self.read_u8()?;
if tag == 0 {
parts.push(InstrPart::Text(self.read_string()?));
} else {
parts.push(InstrPart::Expr(self.read_instrs()?));
}
}
Ok(Instr::Interpolated(parts))
}
19 => Ok(Instr::Import(self.read_string()?)),
20 => Ok(Instr::StructDef {
name: self.read_string()?,
fields: self.read_strings()?,
}),
_ => Err("Invalid instruction tag in bytecode".to_string()),
}
}
}