1use super::span::Span;
2use derive_new::new;
3use derive_setters::Setters;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, PartialEq, Default, Setters, Serialize, Deserialize)]
9#[setters(prefix = "with_", strip_option)]
10pub struct Proto {
11 pub header: Option<Header>,
13 pub externprotos: Vec<ExternProto>,
15 pub proto: Option<ProtoDefinition>,
17 pub root_nodes: Vec<AstNode>,
19 #[serde(skip)]
21 pub source_path: Option<PathBuf>,
22 #[serde(skip)]
24 pub source_content: Option<String>,
25}
26
27impl Proto {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn new_with_header(header: Header) -> Self {
33 Self {
34 header: Some(header),
35 ..Self::default()
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Default, new, Setters, Serialize, Deserialize)]
42#[setters(prefix = "with_", strip_option)]
43pub struct Header {
44 pub version: String,
45 pub encoding: String,
46 pub raw: Option<String>,
47 pub span: Span,
48}
49
50#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
52#[setters(prefix = "with_", strip_option)]
53pub struct ExternProto {
54 pub url: String,
55 pub alias: Option<String>,
56 pub span: Span,
57}
58
59#[derive(Debug, Clone, PartialEq, Default, new, Setters, Serialize, Deserialize)]
61#[setters(prefix = "with_", strip_option)]
62pub struct ProtoDefinition {
63 pub name: String,
64 #[new(default)]
65 pub fields: Vec<ProtoField>,
66 #[new(default)]
67 pub body: Vec<ProtoBodyItem>,
68 pub span: Span,
69}
70
71#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
73#[setters(prefix = "with_", strip_option)]
74pub struct ProtoField {
75 pub name: String,
76 pub field_type: FieldType,
77 #[new(default)]
78 pub default_value: Option<FieldValue>,
79 pub keyword: FieldKeyword,
81 #[new(default)]
83 pub restrictions: Option<Vec<FieldValue>>,
84 pub span: Span,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub enum FieldKeyword {
89 Field,
90 VrmlField,
91 HiddenField,
92 DeprecatedField,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub enum FieldType {
97 SFBool,
98 SFInt32,
99 SFFloat,
100 SFString,
101 SFVec2f,
102 SFVec3f,
103 SFRotation,
104 SFColor,
105 SFNode,
106 MFBool,
107 MFInt32,
108 MFFloat,
109 MFString,
110 MFVec2f,
111 MFVec3f,
112 MFRotation,
113 MFColor,
114 MFNode,
115 Unknown(String),
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
120pub enum ProtoBodyItem {
121 Node(AstNode),
122 Template(TemplateBlock),
123}
124
125#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
127#[setters(prefix = "with_", strip_option)]
128pub struct TemplateBlock {
129 pub content: String,
130 pub is_expression: bool,
132 pub span: Span,
133}
134
135#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
137#[setters(prefix = "with_", strip_option)]
138pub struct AstNode {
139 pub kind: AstNodeKind,
140 pub span: Span,
141}
142
143impl Default for AstNode {
144 fn default() -> Self {
145 Self {
146 kind: AstNodeKind::Node {
147 type_name: "Group".to_string(),
148 def_name: None,
149 fields: vec![],
150 },
151 span: Span::default(),
152 }
153 }
154}
155
156impl From<AstNode> for FieldValue {
157 fn from(node: AstNode) -> Self {
158 FieldValue::Node(Box::new(node))
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub enum AstNodeKind {
164 Node {
166 type_name: String,
167 def_name: Option<String>,
168 fields: Vec<NodeBodyElement>,
169 },
170 Use { use_name: String },
172}
173
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
175pub enum NodeBodyElement {
176 Field(NodeField),
177 Template(TemplateBlock),
178 Raw(RawSyntax),
181}
182
183#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
184#[setters(prefix = "with_", strip_option)]
185pub struct RawSyntax {
186 pub text: String,
187 pub span: Span,
188}
189
190#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
192#[setters(prefix = "with_", strip_option)]
193pub struct NodeField {
194 pub name: String,
195 pub value: FieldValue,
196 pub span: Span,
197}
198
199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub enum FieldValue {
202 Bool(bool),
203 Int(i64, Option<String>),
204 Float(f64, Option<String>),
205 String(String),
206 Vec2f([f64; 2]),
207 Vec3f([f64; 3]),
208 Rotation([f64; 4]),
209 Color([f64; 3]),
210 Node(Box<AstNode>),
211 Array(ArrayValue),
212 NumberSequence(NumberSequence),
214 Is(String),
216 Null,
218 Template(TemplateBlock),
220 Raw(String),
222}
223
224#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
226#[setters(prefix = "with_", strip_option)]
227pub struct ArrayValue {
228 #[new(default)]
229 pub elements: Vec<ArrayElement>,
230}
231
232#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
234#[setters(prefix = "with_", strip_option)]
235pub struct ArrayElement {
236 pub value: FieldValue,
237}
238
239#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
241#[setters(prefix = "with_", strip_option)]
242pub struct NumberSequence {
243 #[new(default)]
244 pub elements: Vec<NumberSequenceElement>,
245}
246
247#[derive(Debug, Clone, PartialEq, new, Setters, Serialize, Deserialize)]
249#[setters(prefix = "with_", strip_option)]
250pub struct NumberSequenceElement {
251 pub value: FieldValue,
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use crate::proto::span::Span;
258
259 #[test]
260 fn test_manual_ast_construction() {
261 let span = Span::default();
262
263 let header = Header::new("R2025a".to_string(), "utf8".to_string(), None, span.clone());
264
265 let extern_proto =
266 ExternProto::new("PedestrianTorso.proto".to_string(), None, span.clone());
267
268 let translation_field = ProtoField::new(
269 "translation".to_string(),
270 FieldType::SFVec3f,
271 FieldKeyword::Field,
272 span.clone(),
273 )
274 .with_default_value(FieldValue::Vec3f([0.0, 0.0, 1.27]));
275
276 let rotation_field = ProtoField::new(
277 "rotation".to_string(),
278 FieldType::SFRotation,
279 FieldKeyword::Field,
280 span.clone(),
281 )
282 .with_default_value(FieldValue::Rotation([0.0, 0.0, 1.0, 0.0]));
283
284 let template_statement = ProtoBodyItem::Template(TemplateBlock::new(
285 " const rigid = fields.controllerArgs.value.length == 0; ".to_string(),
286 false,
287 span.clone(),
288 ));
289
290 let robot_node = AstNode::new(
291 AstNodeKind::Node {
292 type_name: "Robot".to_string(),
293 def_name: None,
294 fields: vec![],
295 },
296 span.clone(),
297 );
298
299 let proto_def = ProtoDefinition::new("Pedestrian".to_string(), span.clone())
300 .with_fields(vec![translation_field, rotation_field])
301 .with_body(vec![template_statement, ProtoBodyItem::Node(robot_node)]);
302
303 let document = Proto::new()
304 .with_header(header)
305 .with_externprotos(vec![extern_proto])
306 .with_proto(proto_def);
307
308 assert_eq!(document.header.as_ref().unwrap().version, "R2025a");
309 assert_eq!(document.externprotos.len(), 1);
310 assert_eq!(document.externprotos[0].url, "PedestrianTorso.proto");
311
312 let proto = document.proto.as_ref().unwrap();
313 assert_eq!(proto.name, "Pedestrian");
314 assert_eq!(proto.fields.len(), 2);
315 assert_eq!(proto.fields[0].name, "translation");
316 if let Some(FieldValue::Vec3f(val)) = &proto.fields[0].default_value {
317 assert_eq!(*val, [0.0, 0.0, 1.27]);
318 } else {
319 panic!("Expected Vec3f default value");
320 }
321
322 assert_eq!(proto.body.len(), 2);
323 if let ProtoBodyItem::Template(block) = &proto.body[0] {
324 assert_eq!(
325 block.content,
326 " const rigid = fields.controllerArgs.value.length == 0; "
327 );
328 assert!(!block.is_expression);
329 } else {
330 panic!("Expected TemplateBlock");
331 }
332 }
333}