use std::sync::{Arc, Mutex};
use crate::{
ir::{ast_to_ir_node, ComponentRegistry, IRNode},
lifecycle::{Module, ModuleInstance},
reactive::{DependencyGraph, Scheduler},
reconcile::{reconcile_ir_with_ds, InstanceTree, Patch as InternalPatch},
};
#[uniffi::export]
pub fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[derive(Debug, Clone, uniffi::Enum)]
pub enum PatchType {
Create,
SetProp,
RemoveProp,
SetText,
Insert,
Move,
Remove,
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct Patch {
pub patch_type: PatchType,
pub id: String,
pub element_type: Option<String>,
pub props_json: Option<String>,
pub name: Option<String>,
pub value_json: Option<String>,
pub text: Option<String>,
pub parent_id: Option<String>,
pub before_id: Option<String>,
}
impl From<InternalPatch> for Patch {
fn from(p: InternalPatch) -> Self {
match p {
InternalPatch::Create {
id,
element_type,
props,
} => Patch {
patch_type: PatchType::Create,
id,
element_type: Some(element_type),
props_json: Some(serde_json::to_string(&props).unwrap_or_default()),
name: None,
value_json: None,
text: None,
parent_id: None,
before_id: None,
},
InternalPatch::SetProp { id, name, value } => Patch {
patch_type: PatchType::SetProp,
id,
element_type: None,
props_json: None,
name: Some(name),
value_json: Some(serde_json::to_string(&value).unwrap_or_default()),
text: None,
parent_id: None,
before_id: None,
},
InternalPatch::RemoveProp { id, name } => Patch {
patch_type: PatchType::RemoveProp,
id,
element_type: None,
props_json: None,
name: Some(name),
value_json: None,
text: None,
parent_id: None,
before_id: None,
},
InternalPatch::SetText { id, text } => Patch {
patch_type: PatchType::SetText,
id,
element_type: None,
props_json: None,
name: None,
value_json: None,
text: Some(text),
parent_id: None,
before_id: None,
},
InternalPatch::Insert {
parent_id,
id,
before_id,
} => Patch {
patch_type: PatchType::Insert,
id,
element_type: None,
props_json: None,
name: None,
value_json: None,
text: None,
parent_id: Some(parent_id),
before_id,
},
InternalPatch::Move {
parent_id,
id,
before_id,
} => Patch {
patch_type: PatchType::Move,
id,
element_type: None,
props_json: None,
name: None,
value_json: None,
text: None,
parent_id: Some(parent_id),
before_id,
},
InternalPatch::Remove { id } => Patch {
patch_type: PatchType::Remove,
id,
element_type: None,
props_json: None,
name: None,
value_json: None,
text: None,
parent_id: None,
before_id: None,
},
}
}
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct Action {
pub name: String,
pub payload_json: Option<String>,
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct ModuleConfig {
pub name: String,
pub actions: Vec<String>,
pub state_keys: Vec<String>,
pub initial_state_json: String,
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct ComponentDef {
pub name: String,
pub source: String,
pub path: String,
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum HypenError {
#[error("Parse error: {0}")]
ParseError(String),
#[error("Render error: {0}")]
RenderError(String),
#[error("State error: {0}")]
StateError(String),
#[error("Action error: {0}")]
ActionError(String),
#[error("Component error: {0}")]
ComponentError(String),
#[error("Initialization error: {0}")]
InitializationError(String),
}
impl From<crate::error::EngineError> for HypenError {
fn from(err: crate::error::EngineError) -> Self {
match err {
crate::error::EngineError::ParseError { message, .. } => {
HypenError::ParseError(message)
}
crate::error::EngineError::ComponentNotFound(name) => HypenError::ComponentError(name),
crate::error::EngineError::RenderError(msg) => HypenError::RenderError(msg),
crate::error::EngineError::ActionNotFound(name) => {
HypenError::ActionError(format!("No handler registered for action: {}", name))
}
crate::error::EngineError::StateError(msg) => HypenError::StateError(msg),
crate::error::EngineError::ExpressionError(msg) => {
HypenError::RenderError(format!("Expression error: {}", msg))
}
}
}
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct ImportInfo {
pub names: Vec<String>,
pub source_path: String,
pub source_type: String,
}
struct EngineState {
component_registry: ComponentRegistry,
module: Option<ModuleInstance>,
tree: InstanceTree,
dependencies: DependencyGraph,
scheduler: Scheduler,
revision: u64,
root_ir_node: Option<IRNode>,
registered_actions: Vec<String>,
pending_actions: Vec<Action>,
pending_imports: Vec<ImportInfo>,
}
#[derive(uniffi::Object)]
pub struct HypenEngine {
state: Mutex<EngineState>,
}
#[uniffi::export]
impl HypenEngine {
#[uniffi::constructor]
pub fn new() -> Result<Arc<Self>, HypenError> {
Ok(Arc::new(Self {
state: Mutex::new(EngineState {
component_registry: ComponentRegistry::new(),
module: None,
tree: InstanceTree::new(),
dependencies: DependencyGraph::new(),
scheduler: Scheduler::new(),
revision: 0,
root_ir_node: None,
registered_actions: Vec::new(),
pending_actions: Vec::new(),
pending_imports: Vec::new(),
}),
}))
}
pub fn parse_to_json(&self, source: String) -> Result<String, HypenError> {
match hypen_parser::parse_component(&source) {
Ok(component) => serde_json::to_string_pretty(&component)
.map_err(|e| HypenError::ParseError(e.to_string())),
Err(errors) => {
let msg = errors
.iter()
.map(|e| hypen_parser::error::format_error_simple(e))
.collect::<Vec<_>>()
.join("; ");
Err(HypenError::ParseError(msg))
}
}
}
pub fn render_source(&self, source: String) -> Result<Vec<Patch>, HypenError> {
let mut state = self
.state
.lock()
.map_err(|e| HypenError::RenderError(e.to_string()))?;
let doc = hypen_parser::parse_document(&source).map_err(|e| {
let msg = e
.iter()
.map(|err| hypen_parser::error::format_error_simple(err))
.collect::<Vec<_>>()
.join("; ");
HypenError::ParseError(msg)
})?;
state.pending_imports = doc
.imports
.iter()
.map(|imp| {
let (source_path, source_type) = match &imp.source {
hypen_parser::ImportSource::Local(p) => (p.clone(), "local".to_string()),
hypen_parser::ImportSource::Url(u) => (u.clone(), "url".to_string()),
};
ImportInfo {
names: imp
.imported_names()
.into_iter()
.map(|s| s.to_string())
.collect(),
source_path,
source_type,
}
})
.collect();
let component = doc
.components
.first()
.ok_or_else(|| HypenError::ParseError("No component found in source".to_string()))?;
let ir_node = ast_to_ir_node(component);
state.root_ir_node = Some(ir_node.clone());
let expanded = state.component_registry.expand_ir_node(&ir_node);
let module_state: serde_json::Value = state
.module
.as_ref()
.map(|m| m.get_state().clone())
.unwrap_or(serde_json::Value::Null);
let EngineState {
tree,
dependencies,
revision,
..
} = &mut *state;
dependencies.clear();
let patches = reconcile_ir_with_ds(tree, &expanded, None, &module_state, dependencies, None);
*revision += 1;
Ok(patches.into_iter().map(Patch::from).collect())
}
pub fn update_state(&self, state_json: String) -> Result<Vec<Patch>, HypenError> {
let patch: serde_json::Value =
serde_json::from_str(&state_json).map_err(|e| HypenError::StateError(e.to_string()))?;
let mut state = self
.state
.lock()
.map_err(|e| HypenError::StateError(e.to_string()))?;
let changed_paths = extract_changed_paths(&patch);
if let Some(module) = &mut state.module {
module.update_state(patch);
}
let EngineState {
dependencies,
scheduler,
tree,
module,
revision,
..
} = &mut *state;
let mut affected_nodes = Vec::new();
for path in &changed_paths {
affected_nodes.extend(dependencies.get_affected_nodes(path));
}
for &node_id in &affected_nodes {
scheduler.mark_dirty(node_id);
}
let patches =
crate::render::render_dirty_nodes_with_deps(scheduler, tree, module.as_ref(), dependencies);
if !patches.is_empty() {
*revision += 1;
}
Ok(patches.into_iter().map(Patch::from).collect())
}
pub fn set_module(&self, config: ModuleConfig) {
if let Ok(mut state) = self.state.lock() {
let initial_state: serde_json::Value =
serde_json::from_str(&config.initial_state_json).unwrap_or(serde_json::Value::Null);
let module = Module::new(&config.name)
.with_actions(config.actions)
.with_state_keys(config.state_keys);
let instance = ModuleInstance::new(module, initial_state);
state.module = Some(instance);
}
}
pub fn register_action(&self, action_name: String) {
if let Ok(mut state) = self.state.lock() {
state.registered_actions.push(action_name);
}
}
pub fn dispatch_action(
&self,
action_name: String,
payload_json: Option<String>,
) -> Result<(), HypenError> {
let mut state = self
.state
.lock()
.map_err(|e| HypenError::ActionError(e.to_string()))?;
if state.registered_actions.contains(&action_name) {
state.pending_actions.push(Action {
name: action_name,
payload_json,
});
}
Ok(())
}
pub fn get_pending_actions(&self) -> Vec<Action> {
if let Ok(mut state) = self.state.lock() {
std::mem::take(&mut state.pending_actions)
} else {
Vec::new()
}
}
pub fn get_pending_imports(&self) -> Vec<ImportInfo> {
if let Ok(mut state) = self.state.lock() {
std::mem::take(&mut state.pending_imports)
} else {
Vec::new()
}
}
pub fn register_primitive(&self, name: String) {
if let Ok(mut state) = self.state.lock() {
state.component_registry.register_primitive(&name);
}
}
pub fn register_default_primitives(&self) {
if let Ok(mut state) = self.state.lock() {
state.component_registry.register_default_primitives();
}
}
pub fn get_default_primitives(&self) -> Vec<String> {
crate::ir::DEFAULT_PRIMITIVES
.iter()
.map(|s| s.to_string())
.collect()
}
pub fn register_component(&self, component: ComponentDef) -> Result<(), HypenError> {
let mut state = self
.state
.lock()
.map_err(|e| HypenError::ComponentError(e.to_string()))?;
let component_spec = hypen_parser::parse_component(&component.source).map_err(|e| {
let msg = e
.iter()
.map(|err| hypen_parser::error::format_error_simple(err))
.collect::<Vec<_>>()
.join("; ");
HypenError::ParseError(msg)
})?;
let ir_node = ast_to_ir_node(&component_spec);
let ir_element = match ir_node {
IRNode::Element(e) => e,
_ => {
return Err(HypenError::ComponentError(
"Component root must be an element".to_string(),
));
}
};
let comp = crate::ir::Component::new(component.name, move |_props| ir_element.clone())
.with_source_path(&component.path);
state.component_registry.register(comp);
Ok(())
}
pub fn clear_tree(&self) {
if let Ok(mut state) = self.state.lock() {
state.tree.clear();
}
}
pub fn get_revision(&self) -> u64 {
self.state.lock().map(|s| s.revision).unwrap_or(0)
}
}
fn extract_changed_paths(patch: &serde_json::Value) -> Vec<String> {
let mut paths = Vec::new();
extract_paths_recursive(patch, String::new(), &mut paths);
paths
}
fn extract_paths_recursive(value: &serde_json::Value, prefix: String, paths: &mut Vec<String>) {
match value {
serde_json::Value::Object(map) => {
for (key, val) in map {
let path = if prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", prefix, key)
};
paths.push(path.clone());
extract_paths_recursive(val, path, paths);
}
}
_ => {}
}
}