mortar_compiler 0.5.2

Mortar language compiler core library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
//! # deserializer.rs
//!
//! # deserializer.rs 文件
//!
//! ## Module Overview
//!
//! ## 模块概述
//!
//! Handles the deserialization of compiled JSON (`.mortared`) files back into Rust data structures.
//!
//! 处理将编译后的 JSON (`.mortared`) 文件反序列化回 Rust 数据结构。
//!
//! This is primarily used by the runtime or tools that need to consume the compiled output.
//!
//! 这主要由运行时或需要使用编译输出的工具使用。
//!
//! ## Source File Overview
//!
//! ## 源文件概述
//!
//! Defines the `MortaredData` structure and related helper structs that match the JSON schema.
//!
//! 定义了与 JSON 模式匹配的 `MortaredData` 结构体和相关辅助结构体。

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

use serde_json::Value;

// ... (keep existing imports)

// Represents the complete deserialized Mortar output structure
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MortaredData {
    pub metadata: Metadata,
    #[serde(default)]
    pub variables: Vec<Variable>,
    #[serde(default)]
    pub constants: Vec<Constant>,
    #[serde(default)]
    pub enums: Vec<Enum>,
    pub nodes: Vec<Node>,
    pub functions: Vec<Function>,
    #[serde(default)]
    pub events: Vec<EventDef>,
    #[serde(default)]
    pub timelines: Vec<TimelineDef>,
}

/// Metadata information about the compiled Mortar file
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Metadata {
    pub version: String,
    pub generated_at: DateTime<Utc>,
}

/// A dialogue node with a linear content flow
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Node {
    pub name: String,
    #[serde(default)]
    pub content: Vec<Value>, // Using Value for flexibility, can be parsed further
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub branches: Option<Vec<BranchDef>>,
    #[serde(default)]
    pub variables: Vec<Variable>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub next: Option<String>,
}

/// Represents the different types of content within a node's linear flow.
/// This enum itself is not directly deserialized into, but serves as a guide
/// to the structure of the `Value` objects in `Node.content`.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ContentItem {
    Text {
        value: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        interpolated_parts: Option<Vec<StringPart>>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        condition: Option<IfCondition>,
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        pre_statements: Vec<Statement>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        events: Option<Vec<Event>>,
    },
    Line {
        value: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        interpolated_parts: Option<Vec<StringPart>>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        condition: Option<IfCondition>,
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        pre_statements: Vec<Statement>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        events: Option<Vec<Event>>,
    },
    RunEvent {
        name: String,
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        args: Vec<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        index_override: Option<IndexOverride>,
        #[serde(default)]
        ignore_duration: bool,
    },
    RunTimeline {
        name: String,
    },
    Choice {
        options: Vec<Choice>,
    },
}

/// Index override for run statements
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IndexOverride {
    #[serde(rename = "type")]
    pub override_type: String,
    pub value: String,
}

/// A branch definition for asymmetric text interpolation
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BranchDef {
    pub name: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub enum_type: Option<String>,
    pub cases: Vec<BranchCase>,
}

/// A single case in a branch definition
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BranchCase {
    pub condition: String,
    pub text: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub events: Option<Vec<Event>>,
}

/// A statement (e.g., assignment)
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Statement {
    #[serde(rename = "type")]
    pub stmt_type: String, // "assignment"
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub var_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
}

/// A conditional expression for if-else statements
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IfCondition {
    #[serde(rename = "type")]
    pub cond_type: String, // "binary", "unary", "identifier", "literal"
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub left: Option<Box<IfCondition>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub right: Option<Box<IfCondition>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operand: Option<Box<IfCondition>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
}

/// A part of an interpolated string
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StringPart {
    #[serde(rename = "type")]
    pub part_type: String, // "text", "expression", or "placeholder"
    pub content: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub function_name: Option<String>,
    #[serde(default)]
    pub args: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub enum_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub branches: Option<Vec<BranchCase>>,
}

/// An event triggered at a specific index
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Event {
    pub index: f64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub index_variable: Option<String>,
    pub actions: Vec<Action>,
}

/// An action to be executed
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Action {
    #[serde(rename = "type")]
    pub action_type: String,
    #[serde(default)]
    pub args: Vec<String>,
}

/// A choice option for branching dialogue
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Choice {
    pub text: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub condition: Option<Condition>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub next: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub action: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub choice: Option<Vec<Choice>>,
}

/// A condition for choice availability
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Condition {
    #[serde(rename = "type")]
    pub condition_type: String,
    #[serde(default)]
    pub args: Vec<String>,
}

/// A function declaration
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Function {
    pub name: String,
    #[serde(default)]
    pub params: Vec<Param>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[serde(rename = "return")]
    pub return_type: Option<String>,
}

/// A function parameter
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Param {
    pub name: String,
    #[serde(rename = "type")]
    pub param_type: String,
}

/// A variable declaration
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Variable {
    pub name: String,
    #[serde(rename = "type")]
    pub var_type: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<serde_json::Value>,
}

/// A constant declaration
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Constant {
    pub name: String,
    #[serde(rename = "type")]
    pub const_type: String,
    pub value: serde_json::Value,
    pub public: bool,
}

/// An enum definition
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Enum {
    pub name: String,
    pub variants: Vec<String>,
}

/// An event definition
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventDef {
    pub name: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub index: Option<f64>,
    pub action: Action,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration: Option<f64>,
}

/// A timeline definition
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TimelineDef {
    pub name: String,
    pub statements: Vec<TimelineStmt>,
}

/// A timeline statement
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TimelineStmt {
    #[serde(rename = "type")]
    pub stmt_type: String, // "run" or "wait"
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub event_name: Option<String>,
    #[serde(default)]
    pub args: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration: Option<f64>,
    #[serde(default)]
    pub ignore_duration: bool,
}

/// Deserializer for loading .mortared JSON files
pub struct Deserializer;

impl Deserializer {
    /// Deserialize a .mortared JSON string into MortaredData
    ///
    /// # Example
    /// ```
    /// use mortar_compiler::deserializer::Deserializer;
    ///
    /// let json_str = r#"{"metadata":{"version":"0.4.0","generated_at":"2025-11-20T00:00:00Z"},"nodes":[],"functions":[]}"#;
    /// let data = Deserializer::from_json(json_str).unwrap();
    /// assert_eq!(data.metadata.version, "0.4.0");
    /// ```
    pub fn from_json(json_str: &str) -> Result<MortaredData, String> {
        serde_json::from_str(json_str).map_err(|e| format!("Deserialization error: {}", e))
    }

    /// Load and deserialize a .mortared file from disk
    ///
    /// # Example
    /// ```no_run
    /// use mortar_compiler::deserializer::Deserializer;
    ///
    /// let data = Deserializer::from_file("dialogue.mortared").unwrap();
    /// println!("Loaded {} nodes", data.nodes.len());
    /// ```
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<MortaredData, String> {
        let path = path.as_ref();
        let content = fs::read_to_string(path)
            .map_err(|e| format!("Failed to read file {}: {}", path.display(), e))?;
        Self::from_json(&content)
    }

    /// Load and deserialize from a byte slice (useful for embedded resources)
    ///
    /// # Example
    /// ```
    /// use mortar_compiler::deserializer::Deserializer;
    ///
    /// let bytes = br#"{"metadata":{"version":"0.4.0","generated_at":"2025-11-20T00:00:00Z"},"nodes":[],"functions":[]}"#;
    /// let data = Deserializer::from_bytes(bytes).unwrap();
    /// ```
    pub fn from_bytes(bytes: &[u8]) -> Result<MortaredData, String> {
        serde_json::from_slice(bytes).map_err(|e| format!("Deserialization error: {}", e))
    }
}

impl MortaredData {
    /// Get a node by name
    ///
    /// # Example
    /// ```no_run
    /// # use mortar_compiler::deserializer::Deserializer;
    /// let data = Deserializer::from_file("dialogue.mortared").unwrap();
    /// if let Some(node) = data.get_node("Start") {
    ///     println!("Found node: {}", node.name);
    /// }
    /// ```
    pub fn get_node(&self, name: &str) -> Option<&Node> {
        self.nodes.iter().find(|n| n.name == name)
    }

    /// Get a function by name
    pub fn get_function(&self, name: &str) -> Option<&Function> {
        self.functions.iter().find(|f| f.name == name)
    }

    /// Get a variable by name
    pub fn get_variable(&self, name: &str) -> Option<&Variable> {
        self.variables.iter().find(|v| v.name == name)
    }

    /// Get a constant by name
    pub fn get_constant(&self, name: &str) -> Option<&Constant> {
        self.constants.iter().find(|c| c.name == name)
    }

    /// Get an enum by name
    pub fn get_enum(&self, name: &str) -> Option<&Enum> {
        self.enums.iter().find(|e| e.name == name)
    }

    /// Get an event definition by name
    pub fn get_event(&self, name: &str) -> Option<&EventDef> {
        self.events.iter().find(|e| e.name == name)
    }

    /// Get a timeline by name
    pub fn get_timeline(&self, name: &str) -> Option<&TimelineDef> {
        self.timelines.iter().find(|t| t.name == name)
    }

    /// Get all node names
    pub fn node_names(&self) -> Vec<&str> {
        self.nodes.iter().map(|n| n.name.as_str()).collect()
    }
}