use crate::eval::{Value, Environment, StackTrace, StackFrame, PrimitiveProcedure};
use crate::ast::{Formals, Literal};
use crate::diagnostics::{Error, Result, Span};
use std::collections::HashMap;
use std::sync::Arc;
use std::rc::Rc;
#[derive(Debug, Clone, PartialEq)]
pub enum TypeInfo {
Boolean,
Number,
String,
Character,
Bytevector,
Symbol,
Keyword,
Nil,
Unspecified,
Pair,
Vector,
Hashtable,
AdvancedHashTable,
Ideque,
PriorityQueue,
OrderedSet,
ListQueue,
RandomAccessList,
Set,
Bag,
Generator,
Procedure {
arity: ArityInfo,
name: Option<String>
},
CaseLambda {
clauses: Vec<ArityInfo>
},
Primitive {
name: String,
arity: ArityInfo
},
Continuation {
id: String
},
Syntax {
name: Option<String>
},
Port {
mode: String,
direction: String
},
Promise {
forced: bool
},
Type {
type_name: String
},
Foreign {
type_name: String
},
ErrorObject {
category: String
},
CharSet,
Parameter {
name: Option<String>
},
Record {
type_name: String
},
Future {
status: String
},
Channel {
capacity: Option<usize>
},
Mutex {
locked: bool
},
Semaphore {
permits: usize
},
AtomicCounter {
value: i64
},
DistributedNode {
node_id: String
},
Opaque {
type_name: String
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArityInfo {
Fixed(usize),
Variable {
min: usize,
rest: bool
},
Multiple(Vec<ArityInfo>),
}
#[derive(Debug, Clone)]
pub struct MetadataInfo {
pub source: Option<Span>,
pub documentation: Option<String>,
pub fields: HashMap<String, Value>,
pub created_at: std::time::SystemTime,
pub type_annotations: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct EnvironmentInfo {
pub bindings: HashMap<String, Value>,
pub parent: Option<Rc<Environment>>,
pub generation: u64,
pub env_type: EnvironmentType,
pub creation_context: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EnvironmentType {
Global,
Local,
Macro,
Module,
Dynamic,
Sandbox,
}
#[derive(Debug, Clone)]
pub struct FrameInfo {
pub procedure_name: Option<String>,
pub location: Option<Span>,
pub local_bindings: HashMap<String, Value>,
pub frame_type: String,
pub arguments: Option<Vec<Value>>,
}
#[derive(Debug, Default)]
pub struct ObjectInspector {
type_cache: HashMap<*const Value, TypeInfo>,
metadata_cache: HashMap<*const Value, MetadataInfo>,
}
impl ObjectInspector {
pub fn new() -> Self {
Self {
type_cache: HashMap::new(),
metadata_cache: HashMap::new(),
}
}
pub fn get_type_info(&mut self, value: &Value) -> TypeInfo {
let ptr = value as *const Value;
if let Some(cached) = self.type_cache.get(&ptr) {
return cached.clone();
}
let type_info = match value {
Value::Literal(Literal::Boolean(_)) => TypeInfo::Boolean,
Value::Literal(Literal::ExactInteger(_)) | Value::Literal(Literal::InexactReal(_)) | Value::Literal(Literal::Number(_)) => TypeInfo::Number,
Value::Literal(Literal::Rational { .. }) => TypeInfo::Number,
Value::Literal(Literal::Complex { .. }) => TypeInfo::Number,
Value::Literal(Literal::String(_)) => TypeInfo::String,
Value::Literal(Literal::Character(_)) => TypeInfo::Character,
Value::Literal(Literal::Bytevector(_)) => TypeInfo::Bytevector,
Value::Literal(Literal::Nil) => TypeInfo::Nil,
Value::Literal(Literal::Unspecified) => TypeInfo::Unspecified,
Value::Symbol(_) => TypeInfo::Symbol,
Value::Keyword(_) => TypeInfo::Keyword,
Value::Nil => TypeInfo::Nil,
Value::Unspecified => TypeInfo::Unspecified,
Value::Pair(_, _) => TypeInfo::Pair,
Value::MutablePair(_, _) => TypeInfo::Pair,
Value::Vector(_) => TypeInfo::Vector,
Value::Hashtable(_) => TypeInfo::Hashtable,
Value::AdvancedHashTable(_) => TypeInfo::AdvancedHashTable,
Value::Ideque(_) => TypeInfo::Ideque,
Value::PriorityQueue(_) => TypeInfo::PriorityQueue,
Value::OrderedSet(_) => TypeInfo::OrderedSet,
Value::ListQueue(_) => TypeInfo::ListQueue,
Value::RandomAccessList(_) => TypeInfo::RandomAccessList,
Value::Procedure(proc) => {
let arity = self.analyze_formals(&proc.formals);
TypeInfo::Procedure {
arity,
name: proc.name.clone(),
}
}
Value::CaseLambda(case_lambda) => {
let clauses = case_lambda.clauses.iter()
.map(|clause| self.analyze_formals(&clause.formals))
.collect();
TypeInfo::CaseLambda { clauses }
}
Value::Primitive(prim) => {
let arity = self.analyze_primitive_arity(&prim.name);
TypeInfo::Primitive {
name: prim.name.clone(),
arity,
}
}
Value::Continuation(cont) => {
TypeInfo::Continuation {
id: format!("cont-{}", cont.id),
}
}
Value::Syntax(_syntax) => {
TypeInfo::Syntax {
name: None, }
}
Value::Port(port) => {
TypeInfo::Port {
mode: format!("{:?}", port.mode),
direction: format!("{:?}", port.direction),
}
}
Value::Promise(_promise) => {
let forced = false; TypeInfo::Promise { forced }
}
Value::Type(_type_val) => {
TypeInfo::Type {
type_name: "type".to_string(), }
}
Value::Foreign(_foreign) => {
TypeInfo::Foreign {
type_name: "foreign".to_string(), }
}
Value::ErrorObject(_error) => {
TypeInfo::ErrorObject {
category: "error".to_string(), }
}
Value::CharSet(_) => TypeInfo::CharSet,
Value::Parameter(_param) => {
TypeInfo::Parameter {
name: None, }
}
Value::Record(_record) => {
TypeInfo::Record {
type_name: "record".to_string(), }
}
#[cfg(feature = "async-runtime")]
Value::Future(_future) => {
TypeInfo::Future {
status: "pending".to_string(), }
}
#[cfg(feature = "async-runtime")]
Value::Channel(_channel) => {
TypeInfo::Channel {
capacity: None, }
}
#[cfg(feature = "async-runtime")]
Value::Mutex(_mutex) => {
TypeInfo::Mutex {
locked: false, }
}
#[cfg(feature = "async-runtime")]
Value::Semaphore(_semaphore) => {
TypeInfo::Semaphore {
permits: 0, }
}
#[cfg(feature = "async-runtime")]
Value::AtomicCounter(_counter) => {
TypeInfo::AtomicCounter {
value: 0, }
}
#[cfg(feature = "async-runtime")]
Value::DistributedNode(_node) => {
TypeInfo::DistributedNode {
node_id: "unknown".to_string(), }
}
Value::MutableString(_) => TypeInfo::String,
Value::Set(_) => TypeInfo::Set,
Value::Bag(_) => TypeInfo::Bag,
Value::Generator(_) => TypeInfo::Generator,
Value::Opaque(_opaque) => {
TypeInfo::Opaque {
type_name: "opaque".to_string(), }
}
};
self.type_cache.insert(ptr, type_info.clone());
type_info
}
pub fn get_metadata_info(&mut self, value: &Value) -> MetadataInfo {
let ptr = value as *const Value;
if let Some(cached) = self.metadata_cache.get(&ptr) {
return cached.clone();
}
let metadata = match value {
Value::Procedure(proc) => MetadataInfo {
source: proc.source,
documentation: proc.metadata.get("doc").and_then(|v| v.as_string().map(|s| s.to_string())),
fields: proc.metadata.clone(),
created_at: std::time::SystemTime::now(), type_annotations: vec!["procedure".to_string()],
},
Value::CaseLambda(case_lambda) => MetadataInfo {
source: case_lambda.source,
documentation: case_lambda.metadata.get("doc").and_then(|v| v.as_string().map(|s| s.to_string())),
fields: case_lambda.metadata.clone(),
created_at: std::time::SystemTime::now(),
type_annotations: vec!["case-lambda".to_string()],
},
Value::Primitive(_prim) => MetadataInfo {
source: None,
documentation: None, fields: HashMap::new(),
created_at: std::time::SystemTime::now(),
type_annotations: vec!["primitive".to_string()],
},
_ => MetadataInfo {
source: None,
documentation: None,
fields: HashMap::new(),
created_at: std::time::SystemTime::now(),
type_annotations: vec![],
},
};
self.metadata_cache.insert(ptr, metadata.clone());
metadata
}
fn analyze_formals(&self, formals: &Formals) -> ArityInfo {
match formals {
Formals::Fixed(params) => ArityInfo::Fixed(params.len()),
Formals::Variable(_) => ArityInfo::Variable { min: 0, rest: true },
Formals::Mixed { fixed, .. } => ArityInfo::Variable {
min: fixed.len(),
rest: true,
},
Formals::Keyword { fixed, .. } => ArityInfo::Variable {
min: fixed.len(),
rest: true,
},
}
}
fn analyze_primitive_arity(&self, name: &str) -> ArityInfo {
match name {
"+" | "*" | "and" | "or" => ArityInfo::Variable { min: 0, rest: true },
"-" | "/" => ArityInfo::Variable { min: 1, rest: true },
"=" | "<" | ">" | "<=" | ">=" => ArityInfo::Variable { min: 2, rest: true },
"cons" => ArityInfo::Fixed(2),
"car" | "cdr" | "not" | "null?" | "pair?" => ArityInfo::Fixed(1),
"if" => ArityInfo::Variable { min: 2, rest: false }, _ => ArityInfo::Variable { min: 0, rest: true }, }
}
}
#[derive(Debug, Default)]
pub struct TypeInspector {
type_hierarchy: HashMap<String, Vec<String>>,
}
impl TypeInspector {
pub fn new() -> Self {
let mut type_hierarchy = HashMap::new();
type_hierarchy.insert("value".to_string(), vec![]);
type_hierarchy.insert("number".to_string(), vec!["value".to_string()]);
type_hierarchy.insert("string".to_string(), vec!["value".to_string()]);
type_hierarchy.insert("symbol".to_string(), vec!["value".to_string()]);
type_hierarchy.insert("pair".to_string(), vec!["value".to_string()]);
type_hierarchy.insert("procedure".to_string(), vec!["value".to_string()]);
Self { type_hierarchy }
}
pub fn type_matches(&self, value: &Value, type_name: &str) -> bool {
let actual_type = self.get_type_name(value);
self.is_subtype(&actual_type, type_name)
}
pub fn get_type_name(&self, value: &Value) -> String {
match value {
Value::Literal(Literal::Boolean(_)) => "boolean".to_string(),
Value::Literal(Literal::ExactInteger(_)) | Value::Literal(Literal::InexactReal(_)) => "number".to_string(),
Value::Literal(Literal::String(_)) => "string".to_string(),
Value::Literal(Literal::Character(_)) => "character".to_string(),
Value::Symbol(_) => "symbol".to_string(),
Value::Keyword(_) => "keyword".to_string(),
Value::Nil => "null".to_string(),
Value::Pair(_, _) => "pair".to_string(),
Value::Vector(_) => "vector".to_string(),
Value::Procedure(_) => "procedure".to_string(),
Value::CaseLambda(_) => "case-lambda".to_string(),
Value::Primitive(_) => "primitive".to_string(),
Value::Port(_) => "port".to_string(),
_ => "value".to_string(),
}
}
pub fn is_subtype(&self, subtype: &str, supertype: &str) -> bool {
if subtype == supertype {
return true;
}
if let Some(parents) = self.type_hierarchy.get(subtype) {
for parent in parents {
if self.is_subtype(parent, supertype) {
return true;
}
}
}
false
}
pub fn try_cast(&self, value: &Value, target_type: &str) -> Result<Value> {
if self.type_matches(value, target_type) {
return Ok(value.clone());
}
match (value, target_type) {
(Value::Literal(Literal::ExactInteger(n)), "string") => {
Ok(Value::string(n.to_string()))
}
(Value::Literal(Literal::InexactReal(n)), "string") => {
Ok(Value::string(n.to_string()))
}
(Value::Literal(Literal::String(s)), "number") => {
if let Ok(n) = s.parse::<f64>() {
Ok(Value::number(n))
} else {
Err(Box::new(Error::runtime_error(
format!("Cannot cast string '{s}' to number"),
None,
)))
}
}
(Value::Symbol(sym), "string") => {
match crate::utils::symbol_name(*sym) {
Some(name) => Ok(Value::string(name)),
None => Ok(Value::string(format!("symbol-{}", sym.0))),
}
}
_ => Err(Box::new(Error::runtime_error(
format!("Cannot cast {} to {}", self.get_type_name(value), target_type),
None,
))),
}
}
}
#[derive(Debug)]
pub struct MetadataAccess {
metadata_store: HashMap<String, HashMap<String, Value>>,
}
impl Default for MetadataAccess {
fn default() -> Self {
Self::new()
}
}
impl MetadataAccess {
pub fn new() -> Self {
Self {
metadata_store: HashMap::new(),
}
}
pub fn get_metadata(&self, object_id: &str, key: &str) -> Option<&Value> {
self.metadata_store.get(object_id)?.get(key)
}
pub fn set_metadata(&mut self, object_id: String, key: String, value: Value) {
self.metadata_store
.entry(object_id)
.or_default()
.insert(key, value);
}
pub fn get_all_metadata(&self, object_id: &str) -> Option<&HashMap<String, Value>> {
self.metadata_store.get(object_id)
}
pub fn remove_metadata(&mut self, object_id: &str, key: &str) -> Option<Value> {
self.metadata_store.get_mut(object_id)?.remove(key)
}
}
#[derive(Debug)]
pub struct ReflectionSystem {
object_inspector: ObjectInspector,
type_inspector: TypeInspector,
metadata_access: MetadataAccess,
pub type_cache: HashMap<String, TypeInfo>,
}
impl ReflectionSystem {
pub fn new() -> Self {
Self {
object_inspector: ObjectInspector::new(),
type_inspector: TypeInspector::new(),
metadata_access: MetadataAccess::new(),
type_cache: HashMap::new(),
}
}
pub fn object_inspector(&mut self) -> &mut ObjectInspector {
&mut self.object_inspector
}
pub fn inspect_value(&mut self, value: &Value) -> ValueInspection {
ValueInspection {
type_info: self.object_inspector.get_type_info(value),
metadata: self.object_inspector.get_metadata_info(value),
type_name: self.type_inspector.get_type_name(value),
}
}
pub fn inspect_environment(&self, env: &Environment) -> EnvironmentInfo {
EnvironmentInfo {
bindings: env.bindings.borrow().clone(),
parent: env.parent.clone(),
generation: env.generation,
env_type: self.classify_environment(env),
creation_context: None, }
}
pub fn inspect_stack_trace(&self, stack_trace: &StackTrace) -> Vec<FrameInfo> {
stack_trace.frames.iter().map(|frame| {
self.inspect_frame(frame)
}).collect()
}
pub fn inspect_frame(&self, frame: &StackFrame) -> FrameInfo {
FrameInfo {
procedure_name: frame.name.clone(),
location: frame.location,
local_bindings: HashMap::new(), frame_type: format!("{:?}", frame.frame_type),
arguments: Some(Vec::new()), }
}
fn classify_environment(&self, env: &Environment) -> EnvironmentType {
if env.parent.is_none() {
EnvironmentType::Global
} else {
EnvironmentType::Local
}
}
pub fn install_primitives(&self, env: &Rc<Environment>) -> Result<()> {
env.define("type-of".to_string(), Value::Primitive(Arc::new(
PrimitiveProcedure {
name: "type-of".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: crate::eval::PrimitiveImpl::Native(primitive_type_of),
effects: vec![],
}
)));
env.define("type-name".to_string(), Value::Primitive(Arc::new(
PrimitiveProcedure {
name: "type-name".to_string(),
implementation: crate::eval::PrimitiveImpl::Native(primitive_type_name),
arity_min: 1,
arity_max: Some(1),
effects: vec![],
}
)));
env.define("get-metadata".to_string(), Value::Primitive(Arc::new(
PrimitiveProcedure {
name: "get-metadata".to_string(),
implementation: crate::eval::PrimitiveImpl::Native(primitive_get_metadata),
arity_min: 2,
arity_max: Some(2),
effects: vec![],
}
)));
env.define("environment-bindings".to_string(), Value::Primitive(Arc::new(
PrimitiveProcedure {
name: "environment-bindings".to_string(),
implementation: crate::eval::PrimitiveImpl::Native(primitive_environment_bindings),
arity_min: 1,
arity_max: Some(1),
effects: vec![],
}
)));
env.define("current-stack-trace".to_string(), Value::Primitive(Arc::new(
PrimitiveProcedure {
name: "current-stack-trace".to_string(),
implementation: crate::eval::PrimitiveImpl::Native(primitive_current_stack_trace),
arity_min: 0,
arity_max: Some(0),
effects: vec![],
}
)));
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ValueInspection {
pub type_info: TypeInfo,
pub metadata: MetadataInfo,
pub type_name: String,
}
fn primitive_type_of(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(Error::runtime_error(
format!("type-of expects 1 argument, got {}", args.len()),
None,
)));
}
let mut inspector = ObjectInspector::new();
let type_info = inspector.get_type_info(&args[0]);
let type_symbol = match type_info {
TypeInfo::Boolean => "boolean",
TypeInfo::Number => "number",
TypeInfo::String => "string",
TypeInfo::Character => "character",
TypeInfo::Symbol => "symbol",
TypeInfo::Keyword => "keyword",
TypeInfo::Nil => "null",
TypeInfo::Pair => "pair",
TypeInfo::Vector => "vector",
TypeInfo::Procedure { .. } => "procedure",
TypeInfo::Primitive { .. } => "primitive",
_ => "unknown",
};
Ok(Value::symbol(crate::utils::intern_symbol(type_symbol)))
}
fn primitive_type_name(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(Error::runtime_error(
format!("type-name expects 1 argument, got {}", args.len()),
None,
)));
}
let inspector = TypeInspector::new();
let type_name = inspector.get_type_name(&args[0]);
Ok(Value::string(type_name))
}
fn primitive_get_metadata(_args: &[Value]) -> Result<Value> {
Ok(Value::Nil)
}
fn primitive_environment_bindings(_args: &[Value]) -> Result<Value> {
Ok(Value::Nil)
}
fn primitive_current_stack_trace(_args: &[Value]) -> Result<Value> {
Ok(Value::Nil)
}
impl Default for ReflectionSystem {
fn default() -> Self {
Self::new()
}
}