use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Nil,
Bool(bool),
Number(f64),
Str(Vec<u8>),
Table(Table),
Function(Func),
Global(Global),
}
#[derive(Clone, Debug)]
pub struct Table(Rc<RefCell<TableData>>);
impl PartialEq for Table {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Key {
Bool(bool),
Number(f64),
Str(Vec<u8>),
Table(Table),
Function(Func),
Global(Global),
}
pub type MetaFn = Rc<dyn Fn() -> Result<Value, MetaError>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MetaError {
pub message: String,
}
impl MetaError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
MetaError {
message: message.into(),
}
}
}
impl std::fmt::Display for MetaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "metamethod error: {}", self.message)
}
}
impl std::error::Error for MetaError {}
#[derive(Default)]
pub struct TableData {
pub entries: Vec<(Key, Value)>,
pub serialize_meta: Option<MetaFn>,
pub tostring_meta: Option<MetaFn>,
}
impl std::fmt::Debug for TableData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TableData")
.field("entries", &self.entries)
.field("has_serialize_meta", &self.serialize_meta.is_some())
.field("has_tostring_meta", &self.tostring_meta.is_some())
.finish()
}
}
#[derive(Clone, Debug)]
pub struct Func {
pub id: usize,
pub bytecode: Option<Vec<u8>>,
}
impl PartialEq for Func {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[derive(Clone, Debug)]
pub struct Global {
pub name: String,
pub id: usize,
}
impl PartialEq for Global {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Table {
#[must_use]
pub fn new() -> Self {
Table(Rc::new(RefCell::new(TableData::default())))
}
pub fn push(&self, v: Value) {
let n = self.array_len_raw() + 1;
self.set(Key::Number(n as f64), v);
}
pub fn set(&self, key: Key, value: Value) {
let mut d = self.0.borrow_mut();
if let Some(slot) = d.entries.iter_mut().find(|(k, _)| k.same(&key)) {
slot.1 = value;
} else {
d.entries.push((key, value));
}
}
fn array_len_raw(&self) -> usize {
let d = self.0.borrow();
let mut n = 0usize;
loop {
let want = (n + 1) as f64;
if d.entries.iter().any(|(k, _)| match k {
Key::Number(x) => *x == want,
_ => false,
}) {
n += 1;
} else {
return n;
}
}
}
#[must_use]
pub fn border(&self) -> usize {
self.array_len_raw()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.borrow().entries.is_empty()
}
#[must_use]
pub fn id(&self) -> usize {
Rc::as_ptr(&self.0) as usize
}
pub fn set_serialize_meta(&self, f: MetaFn) {
self.0.borrow_mut().serialize_meta = Some(f);
}
pub fn set_tostring_meta(&self, f: MetaFn) {
self.0.borrow_mut().tostring_meta = Some(f);
}
#[must_use]
pub fn serialize_meta(&self) -> Option<MetaFn> {
self.0.borrow().serialize_meta.clone()
}
#[must_use]
pub fn tostring_meta(&self) -> Option<MetaFn> {
self.0.borrow().tostring_meta.clone()
}
#[must_use]
pub fn get(&self, key: &Key) -> Option<Value> {
self.0
.borrow()
.entries
.iter()
.find(|(k, _)| k.same(key))
.map(|(_, v)| v.clone())
}
#[must_use]
pub fn entries(&self) -> Vec<(Key, Value)> {
self.0.borrow().entries.clone()
}
pub fn with_entries<R>(&self, f: impl FnOnce(&[(Key, Value)]) -> R) -> R {
f(&self.0.borrow().entries)
}
}
impl Default for Table {
fn default() -> Self {
Table::new()
}
}
impl Key {
#[must_use]
pub fn same(&self, other: &Key) -> bool {
self == other
}
#[must_use]
pub fn to_value(&self) -> Value {
match self {
Key::Bool(b) => Value::Bool(*b),
Key::Number(n) => Value::Number(*n),
Key::Str(s) => Value::Str(s.clone()),
Key::Table(t) => Value::Table(t.clone()),
Key::Function(f) => Value::Function(f.clone()),
Key::Global(g) => Value::Global(g.clone()),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Ident {
Table(usize),
Function(usize),
Global(usize),
}
impl Value {
#[must_use]
pub fn ident(&self) -> Option<Ident> {
match self {
Value::Table(t) => Some(Ident::Table(t.id())),
Value::Function(f) => Some(Ident::Function(f.id)),
Value::Global(g) => Some(Ident::Global(g.id)),
_ => None,
}
}
}