#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ObjFlags(u8);
impl ObjFlags {
pub const LITERAL: u8 = 0;
pub const EXECUTABLE: u8 = 1 << 3;
pub const ACCESS_NONE: u8 = 0;
pub const ACCESS_EXECUTE_ONLY: u8 = 1;
pub const ACCESS_READ_ONLY: u8 = 2;
pub const ACCESS_WRITE_ONLY: u8 = 3;
pub const ACCESS_UNLIMITED: u8 = 4;
const ACCESS_MASK: u8 = 0b0000_0111;
const EXEC_BIT: u8 = 1 << 3;
const GLOBAL_BIT: u8 = 1 << 4;
const COMPOSITE_BIT: u8 = 1 << 5;
const DEFERRED_BIT: u8 = 1 << 6;
pub fn new(access: u8, executable: bool, global: bool, composite: bool) -> Self {
let mut bits = access & Self::ACCESS_MASK;
if executable {
bits |= Self::EXEC_BIT;
}
if global {
bits |= Self::GLOBAL_BIT;
}
if composite {
bits |= Self::COMPOSITE_BIT;
}
Self(bits)
}
pub fn literal() -> Self {
Self::new(Self::ACCESS_UNLIMITED, false, false, false)
}
pub fn executable() -> Self {
Self::new(Self::ACCESS_UNLIMITED, true, false, false)
}
pub fn literal_composite() -> Self {
Self::new(Self::ACCESS_UNLIMITED, false, false, true)
}
pub fn executable_composite() -> Self {
Self::new(Self::ACCESS_UNLIMITED, true, false, true)
}
pub fn access(self) -> u8 {
self.0 & Self::ACCESS_MASK
}
pub fn is_executable(self) -> bool {
self.0 & Self::EXEC_BIT != 0
}
pub fn is_literal(self) -> bool {
!self.is_executable()
}
pub fn is_global(self) -> bool {
self.0 & Self::GLOBAL_BIT != 0
}
pub fn is_composite(self) -> bool {
self.0 & Self::COMPOSITE_BIT != 0
}
pub fn set_executable(&mut self) {
self.0 |= Self::EXEC_BIT;
}
pub fn set_literal(&mut self) {
self.0 &= !Self::EXEC_BIT;
}
pub fn set_access(&mut self, access: u8) {
self.0 = (self.0 & !Self::ACCESS_MASK) | (access & Self::ACCESS_MASK);
}
pub fn is_deferred(self) -> bool {
self.0 & Self::DEFERRED_BIT != 0
}
pub fn set_deferred(&mut self) {
self.0 |= Self::DEFERRED_BIT;
}
pub fn clear_deferred(&mut self) {
self.0 &= !Self::DEFERRED_BIT;
}
#[inline]
pub fn require_read(self) -> Result<(), crate::error::PsError> {
if self.access() >= Self::ACCESS_READ_ONLY {
Ok(())
} else {
Err(crate::error::PsError::InvalidAccess)
}
}
#[inline]
pub fn require_write(self) -> Result<(), crate::error::PsError> {
if self.access() >= Self::ACCESS_UNLIMITED {
Ok(())
} else {
Err(crate::error::PsError::InvalidAccess)
}
}
#[inline]
pub fn require_file_write(self) -> Result<(), crate::error::PsError> {
if self.access() >= Self::ACCESS_WRITE_ONLY {
Ok(())
} else {
Err(crate::error::PsError::InvalidAccess)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct NameId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EntityId(pub u32);
impl EntityId {
const GLOBAL_BIT: u32 = 1 << 31;
const INDEX_MASK: u32 = !(1 << 31);
pub fn local(index: u32) -> Self {
debug_assert!(
index & Self::GLOBAL_BIT == 0,
"index overflows into tag bit"
);
EntityId(index)
}
pub fn global(index: u32) -> Self {
debug_assert!(
index & Self::GLOBAL_BIT == 0,
"index overflows into tag bit"
);
EntityId(index | Self::GLOBAL_BIT)
}
#[inline]
pub fn is_global(self) -> bool {
self.0 & Self::GLOBAL_BIT != 0
}
#[inline]
pub fn raw_index(self) -> usize {
(self.0 & Self::INDEX_MASK) as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OpCode(pub u16);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SaveLevel(pub u32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PsValue {
Null,
Mark,
DictMark,
Bool(bool),
Int(i64),
Real(f64),
Name(NameId),
String {
entity: EntityId,
start: u32,
len: u32,
},
Array {
entity: EntityId,
start: u32,
len: u32,
},
PackedArray {
entity: EntityId,
start: u32,
len: u32,
},
Dict(EntityId),
Operator(OpCode),
File(EntityId),
Save(SaveLevel),
FontID(i32),
Gstate(u32),
Stopped,
Loop(EntityId),
HardReturn,
DictEnd(EntityId),
ExecArray {
entity: EntityId,
start: u32,
len: u32,
pos: u32,
},
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PsObject {
pub value: PsValue,
pub flags: ObjFlags,
}
impl PsObject {
pub fn int(v: impl Into<i64>) -> Self {
let v: i64 = v.into();
Self {
value: PsValue::Int(v),
flags: ObjFlags::literal(),
}
}
pub fn real(v: f64) -> Self {
Self {
value: PsValue::Real(v),
flags: ObjFlags::literal(),
}
}
pub fn bool(v: bool) -> Self {
Self {
value: PsValue::Bool(v),
flags: ObjFlags::literal(),
}
}
pub fn null() -> Self {
Self {
value: PsValue::Null,
flags: ObjFlags::literal(),
}
}
pub fn mark() -> Self {
Self {
value: PsValue::Mark,
flags: ObjFlags::literal(),
}
}
pub fn dict_mark() -> Self {
Self {
value: PsValue::DictMark,
flags: ObjFlags::literal(),
}
}
pub fn name_lit(id: NameId) -> Self {
Self {
value: PsValue::Name(id),
flags: ObjFlags::literal(),
}
}
pub fn name_exec(id: NameId) -> Self {
Self {
value: PsValue::Name(id),
flags: ObjFlags::executable(),
}
}
pub fn operator(op: OpCode) -> Self {
Self {
value: PsValue::Operator(op),
flags: ObjFlags::executable(),
}
}
pub fn string(entity: EntityId, len: u32) -> Self {
Self {
value: PsValue::String {
entity,
start: 0,
len,
},
flags: ObjFlags::literal_composite(),
}
}
pub fn array(entity: EntityId, len: u32) -> Self {
Self {
value: PsValue::Array {
entity,
start: 0,
len,
},
flags: ObjFlags::literal_composite(),
}
}
pub fn procedure(entity: EntityId, len: u32) -> Self {
Self {
value: PsValue::Array {
entity,
start: 0,
len,
},
flags: ObjFlags::executable_composite(),
}
}
pub fn dict(entity: EntityId) -> Self {
Self {
value: PsValue::Dict(entity),
flags: ObjFlags::literal_composite(),
}
}
pub fn stopped_mark() -> Self {
Self {
value: PsValue::Stopped,
flags: ObjFlags::executable(),
}
}
pub fn loop_mark(entity: EntityId) -> Self {
Self {
value: PsValue::Loop(entity),
flags: ObjFlags::executable(),
}
}
pub fn hard_return() -> Self {
Self {
value: PsValue::HardReturn,
flags: ObjFlags::executable(),
}
}
pub fn dict_end(entity: EntityId) -> Self {
Self {
value: PsValue::DictEnd(entity),
flags: ObjFlags::executable(),
}
}
pub fn is_numeric(&self) -> bool {
matches!(self.value, PsValue::Int(_) | PsValue::Real(_))
}
pub fn is_int(&self) -> bool {
matches!(self.value, PsValue::Int(_))
}
pub fn is_real(&self) -> bool {
matches!(self.value, PsValue::Real(_))
}
pub fn is_bool(&self) -> bool {
matches!(self.value, PsValue::Bool(_))
}
pub fn is_array_type(&self) -> bool {
matches!(
self.value,
PsValue::Array { .. } | PsValue::PackedArray { .. }
)
}
pub fn is_composite(&self) -> bool {
self.flags.is_composite()
}
pub fn is_global_vm(&self) -> bool {
match self.value {
PsValue::Dict(e) => e.is_global(),
PsValue::Array { entity, .. } | PsValue::PackedArray { entity, .. } => {
entity.is_global()
}
PsValue::String { entity, .. } => entity.is_global(),
_ => self.flags.is_global(),
}
}
pub fn type_name(&self) -> &'static [u8] {
match self.value {
PsValue::Int(_) => b"integertype",
PsValue::Real(_) => b"realtype",
PsValue::Bool(_) => b"booleantype",
PsValue::Null => b"nulltype",
PsValue::Mark | PsValue::DictMark => b"marktype",
PsValue::Name(_) => b"nametype",
PsValue::String { .. } => b"stringtype",
PsValue::Array { .. } => b"arraytype",
PsValue::PackedArray { .. } => b"packedarraytype",
PsValue::Dict(_) => b"dicttype",
PsValue::Operator(_) => b"operatortype",
PsValue::File(_) => b"filetype",
PsValue::Save(_) => b"savetype",
PsValue::FontID(_) => b"fonttype",
PsValue::Gstate(_) => b"gstatetype",
_ => b"nulltype", }
}
pub fn as_f64(&self) -> Option<f64> {
match self.value {
PsValue::Int(v) => Some(v as f64),
PsValue::Real(v) => Some(v),
_ => None,
}
}
pub fn as_i32(&self) -> Option<i32> {
match self.value {
PsValue::Int(v) => i32::try_from(v).ok(),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self.value {
PsValue::Int(v) => Some(v),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_obj_flags_basic() {
let f = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, false, true);
assert_eq!(f.access(), ObjFlags::ACCESS_UNLIMITED);
assert!(f.is_executable());
assert!(!f.is_global());
assert!(f.is_composite());
}
#[test]
fn test_obj_flags_set_literal() {
let mut f = ObjFlags::executable();
assert!(f.is_executable());
f.set_literal();
assert!(f.is_literal());
}
#[test]
fn test_ps_object_int() {
let obj = PsObject::int(42);
assert!(obj.is_int());
assert!(obj.is_numeric());
assert!(!obj.is_real());
assert_eq!(obj.as_i32(), Some(42));
assert_eq!(obj.as_f64(), Some(42.0));
assert_eq!(obj.type_name(), b"integertype");
}
#[test]
fn test_ps_object_real() {
let obj = PsObject::real(2.5);
assert!(obj.is_real());
assert!(obj.is_numeric());
assert_eq!(obj.as_f64(), Some(2.5));
assert_eq!(obj.as_i32(), None);
assert_eq!(obj.type_name(), b"realtype");
}
#[test]
fn test_ps_object_copy_semantics() {
let a = PsObject::int(10);
let b = a; assert_eq!(a.as_i32(), Some(10));
assert_eq!(b.as_i32(), Some(10));
}
#[test]
fn test_ps_object_procedure() {
let obj = PsObject::procedure(EntityId(0), 3);
assert!(obj.flags.is_executable());
assert!(obj.flags.is_composite());
assert!(obj.is_array_type());
}
}