1use crate::dw::{
15 parse_dw, unescape_string, AstNode, ComparisonType, ImportedElement, Location, LogicalType,
16 Message, MessageKind,
17};
18use crate::json::escape_str;
19
20#[derive(Clone, PartialEq, Debug, Default)]
24pub struct InputContext<'a> {
25 pub inputs: Vec<&'a str>,
26 pub functions: Vec<&'a str>,
27}
28
29#[derive(Clone, PartialEq, Debug, Default)]
30struct DeclaredImports {
31 pub imports: Vec<ImportDefinition>,
32}
33
34#[derive(Clone, PartialEq, Debug, Default)]
35struct ImportDefinition {
36 module: String,
37 alias: Option<String>,
38 imported_elements: Vec<ImportElement>,
39}
40
41#[derive(Clone, PartialEq, Debug, Default)]
42struct ImportElement {
43 element: String,
44 alias: Option<String>,
45}
46
47#[derive(Clone, PartialEq, Debug)]
51pub struct CompilationResult {
52 pub pel: Option<String>,
53 pub messages: Vec<Message>,
54}
55
56#[derive(Clone, PartialEq, Debug, Default)]
57pub struct DeclaredNamespaces {
58 pub namespaces: Vec<NamespaceInformation>,
59}
60
61impl DeclaredNamespaces {
62 pub fn contains_prefix(&self, prefix: &String) -> bool {
63 self.namespaces.iter().any(|n| n.prefix.eq(prefix))
64 }
65
66 pub fn resolve_prefix(&self, prefix: &String) -> Option<&String> {
67 self.namespaces
68 .iter()
69 .find(|n| n.prefix.eq(prefix))
70 .map(|ns| &ns.uri)
71 }
72}
73
74#[derive(Clone, PartialEq, Debug, Default)]
75pub struct NamespaceInformation {
76 pub prefix: String,
77 pub uri: String,
78}
79
80fn is_imported(var_name: &str, ctx: &InputContext, imports: &DeclaredImports) -> bool {
81 let mut fqn: String = String::new();
83 fqn.push_str("dw::Core");
84 fqn.push_str("::");
85 fqn.push_str(var_name);
86 if ctx.functions.contains(&fqn.as_str()) {
87 return true;
88 }
89
90 for import in &imports.imports {
91 for element in &import.imported_elements {
92 if element.element == "*" {
93 let mut fqn: String = String::new();
94 fqn.push_str(import.module.as_str());
95 fqn.push_str("::");
96 fqn.push_str(var_name);
97 if ctx.functions.contains(&fqn.as_str()) {
98 return true;
99 }
100 } else if element.element == var_name || element.alias.as_deref() == Some(var_name) {
101 let mut fqn: String = String::new();
102 fqn.push_str(import.module.as_str());
103 fqn.push_str("::");
104 fqn.push_str(element.element.as_str());
105 if ctx.functions.contains(&fqn.as_str()) {
106 return true;
107 }
108 }
109 }
110 }
111 false
112}
113
114fn validate_pel(
115 expression: &AstNode,
116 ctx: &InputContext,
117 imports: &DeclaredImports,
118 namespace: &DeclaredNamespaces,
119) -> Vec<Message> {
120 match expression {
121 AstNode::Name {
122 prefix, location, ..
123 } => {
124 let maybe_prefix: &Option<AstNode> = prefix;
125 match maybe_prefix {
126 None => {
127 vec![]
128 }
129 Some(prefix_node) => {
130 let prefix_string = prefix_node.get_value().unwrap();
131 if !namespace.contains_prefix(&prefix_string) {
132 vec![Message {
133 message: format!(
134 "Unable to resolve namespace prefix of : `{}`",
135 &prefix_string
136 ),
137 kind: MessageKind::Error,
138 location: Location {
139 start: location.start,
140 end: location.end,
141 },
142 }]
143 } else {
144 vec![]
145 }
146 }
147 }
148 }
149 AstNode::VariableReference { reference, .. } => match reference.as_ref() {
150 AstNode::NameIdentifier { name, location } => {
151 if name.len() == 1 {
152 let var_name = name.first().unwrap();
153 if !ctx.inputs.contains(&var_name.as_str()) {
154 if !is_imported(var_name, ctx, imports) {
155 vec![Message {
156 message: format!("Unable to resolve reference of: `{var_name}`."),
157 kind: MessageKind::Error,
158 location: Location {
159 start: location.start,
160 end: location.end,
161 },
162 }]
163 } else {
164 vec![]
165 }
166 } else {
167 vec![]
168 }
169 } else {
170 let fqn = name.join("::");
171 if !ctx.functions.contains(&fqn.as_str()) {
172 vec![Message {
173 message: format!("Unable to resolve module with identifier: `{fqn}`."),
174 kind: MessageKind::Error,
175 location: Location {
176 start: location.start,
177 end: location.end,
178 },
179 }]
180 } else {
181 vec![]
182 }
183 }
184 }
185 _ => {
186 vec![]
187 }
188 },
189
190 AstNode::Array { items, .. } => items
191 .iter()
192 .flat_map(|node| validate_pel(node, ctx, imports, namespace))
193 .collect(),
194 AstNode::Comparison { lhs, rhs, .. } => [lhs, rhs]
195 .iter()
196 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
197 .collect(),
198 AstNode::Logical { lhs, rhs, .. } => [lhs, rhs]
199 .iter()
200 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
201 .collect(),
202 AstNode::Not { rhs, .. } => validate_pel(rhs, ctx, imports, namespace),
203 AstNode::ValueSelector { lhs, rhs, .. } => [lhs, rhs]
204 .iter()
205 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
206 .collect(),
207 AstNode::InfixFunctionCall { lhs, func, rhs, .. } => [lhs, func, rhs]
208 .iter()
209 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
210 .collect(),
211 AstNode::FunctionCall { lhs, args, .. } => {
212 let mut vec1 = args.clone();
213 vec1.push(lhs.as_ref().to_owned());
214 vec1.iter()
215 .flat_map(|node| validate_pel(node, ctx, imports, namespace))
216 .collect()
217 }
218 AstNode::IfElse {
219 condition,
220 if_branch,
221 else_branch,
222 ..
223 } => [condition, if_branch, else_branch]
224 .iter()
225 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
226 .collect(),
227 AstNode::Default { lhs, rhs, .. } => [lhs, rhs]
228 .iter()
229 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
230 .collect(),
231 AstNode::Document {
232 header: _header,
233 body,
234 ..
235 } => [body]
236 .iter()
237 .flat_map(|node| validate_pel(node.as_ref(), ctx, imports, namespace))
238 .collect(),
239 AstNode::Number {
240 value, location, ..
241 } => match value.parse::<f64>() {
242 Ok(_) => {
243 vec![]
244 }
245 Err(_) => {
246 vec![Message {
247 message: format!("Number `{value}` should be a valid Double."),
248 kind: MessageKind::Error,
249 location: Location {
250 start: location.start,
251 end: location.end,
252 },
253 }]
254 }
255 },
256 AstNode::Object { location, .. } => {
257 vec![Message {
258 message: "Object expressions are not yet supported.".to_string(),
259 kind: MessageKind::Error,
260 location: Location {
261 start: location.start,
262 end: location.end,
263 },
264 }]
265 }
266 AstNode::NullSafeSelector { lhs, .. } => {
267 validate_pel(lhs.as_ref(), ctx, imports, namespace)
268 }
269 n => n
270 .children()
271 .iter()
272 .flat_map(|node| validate_pel(node, ctx, imports, namespace))
273 .collect(),
274 }
275}
276
277fn collect_declared_ns(expression: &AstNode) -> DeclaredNamespaces {
278 match expression {
279 AstNode::Document { header: None, .. } => DeclaredNamespaces::default(),
280 AstNode::Document {
281 header: Some(directives),
282 ..
283 } => directives
284 .iter()
285 .map(collect_declared_ns)
286 .reduce(|acc, e| {
287 let x: Vec<NamespaceInformation> =
288 [acc.namespaces.as_slice(), e.namespaces.as_slice()].concat();
289 DeclaredNamespaces { namespaces: x }
290 })
291 .unwrap_or_default(),
292 AstNode::NamespaceDirective { prefix, uri, .. } => DeclaredNamespaces {
293 namespaces: vec![NamespaceInformation {
294 prefix: prefix.get_value().unwrap(),
295 uri: uri.get_value().unwrap(),
296 }],
297 },
298 _ => DeclaredNamespaces::default(),
299 }
300}
301fn collect_declared_imports(expression: &AstNode) -> DeclaredImports {
302 match expression {
303 AstNode::Document { header: None, .. } => DeclaredImports::default(),
304 AstNode::Document {
305 header: Some(directives),
306 ..
307 } => directives
308 .iter()
309 .map(collect_declared_imports)
310 .reduce(|acc, e| {
311 let x: Vec<ImportDefinition> =
312 [acc.imports.as_slice(), e.imports.as_slice()].concat();
313 DeclaredImports { imports: x }
314 })
315 .unwrap_or_default(),
316 AstNode::ImportDirective {
317 module, elements, ..
318 } => DeclaredImports {
319 imports: vec![ImportDefinition {
320 module: name_as_string(module.as_ref()),
321 alias: alias_as_string(&module.alias),
322 imported_elements: elements
323 .iter()
324 .map(|e| ImportElement {
325 element: name_as_string(e),
326 alias: alias_as_string(&e.alias),
327 })
328 .collect(),
329 }],
330 },
331 _ => DeclaredImports::default(),
332 }
333}
334
335fn name_as_string(module: &ImportedElement) -> String {
336 match module.name.as_ref() {
337 AstNode::NameIdentifier { name, .. } => name.join("::"),
338 _ => "".to_string(),
339 }
340}
341
342fn alias_as_string(option: &Option<AstNode>) -> Option<String> {
343 option.as_ref().map(|a| match a {
344 AstNode::NameIdentifier { name, .. } => name.join("::"),
345 _ => "".to_string(),
346 })
347}
348
349pub fn compile_to_pel_expr(
351 script_name: &str,
352 expression_str: &str,
353 ctx: &InputContext,
354) -> CompilationResult {
355 let result = parse_dw(script_name, expression_str);
356 match result {
357 Ok(parse_result) => {
358 let expression = parse_result.ast;
359 let declared_imports: DeclaredImports = collect_declared_imports(&expression);
360 let declared_ns: DeclaredNamespaces = collect_declared_ns(&expression);
361 let messages = validate_pel(&expression, ctx, &declared_imports, &declared_ns);
362 let errors = messages.iter().any(|m| m.kind == MessageKind::Error);
363 CompilationResult {
364 pel: (!errors).then(|| to_pel(&expression, &declared_ns)),
365 messages,
366 }
367 }
368 Err(e) => CompilationResult {
369 pel: None,
370 messages: e.messages,
371 },
372 }
373}
374
375fn to_pel(expression: &AstNode, declared_namespaces: &DeclaredNamespaces) -> String {
376 match expression {
377 AstNode::Str {
378 value: content,
379 quote,
380 location,
381 } => {
382 format!(
383 "[{}, {}, {}]",
384 str(":str"),
385 loc_str(location),
386 str(unescape_string(content.to_string(), quote).as_str())
387 )
388 }
389 AstNode::Boolean { value, location } => {
390 format!(
391 "[{}, {}, {}]",
392 str(":bool"),
393 loc_str(location),
394 str(value.to_string().as_str())
395 )
396 }
397 AstNode::Number { value, location } => {
398 format!(
399 "[{}, {}, {}]",
400 str(":nbr"),
401 loc_str(location),
402 str(value.to_string().as_str())
403 )
404 }
405 AstNode::Null { location } => {
406 format!("[{}, {}]", str(":null"), loc_str(location))
407 }
408 AstNode::DateTime { value, location } => {
409 format!(
410 "[{}, {}, {}]",
411 str(":datetime"),
412 loc_str(location),
413 str(value.as_str())
414 )
415 }
416 AstNode::LocalDateTime { value, location } => {
417 format!(
418 "[{}, {}, {}]",
419 str(":ldatetime"),
420 loc_str(location),
421 str(value.as_str())
422 )
423 }
424 AstNode::Array { items, location } => {
425 if items.is_empty() {
426 format!("[{}, {}]", str(":array"), loc_str(location))
427 } else {
428 let elements: Vec<String> = items
429 .iter()
430 .map(|a| to_pel(a, declared_namespaces))
431 .collect();
432 let array_elements = elements.join(", ");
433 format!(
434 "[{}, {}, {}]",
435 str(":array"),
436 loc_str(location),
437 array_elements
438 )
439 }
440 }
441 AstNode::Comparison {
442 lhs,
443 rhs,
444 comp,
445 location,
446 } => {
447 let c = match comp {
448 ComparisonType::Greater => ">",
449 ComparisonType::Less => "<",
450 ComparisonType::Equal => "==",
451 ComparisonType::NotEqual => "!=",
452 ComparisonType::Similar => "~=",
453 ComparisonType::GreaterEqual => ">=",
454 _ => "<=",
455 };
456 format!(
457 "[{}, {}, {}, {}]",
458 str(c),
459 loc_str(location),
460 to_pel(lhs.as_ref(), declared_namespaces),
461 to_pel(rhs.as_ref(), declared_namespaces)
462 )
463 }
464 AstNode::Logical {
465 lhs,
466 rhs,
467 comp,
468 location,
469 } => {
470 let c = match comp {
471 LogicalType::And => "&&",
472 LogicalType::Or => "||",
473 };
474 format!(
475 "[{}, {}, {}, {}]",
476 str(c),
477 loc_str(location),
478 to_pel(lhs.as_ref(), declared_namespaces),
479 to_pel(rhs.as_ref(), declared_namespaces)
480 )
481 }
482 AstNode::Not { rhs: exp, location } => {
483 format!(
484 "[{}, {}, {}]",
485 str("!"),
486 loc_str(location),
487 to_pel(exp.as_ref(), declared_namespaces)
488 )
489 }
490 AstNode::ValueSelector { lhs, rhs, location } => {
491 format!(
492 "[{}, {}, {}, {}]",
493 str("."),
494 loc_str(location),
495 to_pel(lhs.as_ref(), declared_namespaces),
496 to_pel(rhs.as_ref(), declared_namespaces)
497 )
498 }
499 AstNode::AttributeSelector { lhs, rhs, location } => {
500 format!(
501 "[{}, {}, {}, {}]",
502 str("@"),
503 loc_str(location),
504 to_pel(lhs.as_ref(), declared_namespaces),
505 to_pel(rhs.as_ref(), declared_namespaces)
506 )
507 }
508 AstNode::MultiValueSelector { lhs, rhs, location } => {
509 format!(
510 "[{}, {}, {}, {}]",
511 str("*"),
512 loc_str(location),
513 to_pel(lhs.as_ref(), declared_namespaces),
514 to_pel(rhs.as_ref(), declared_namespaces)
515 )
516 }
517 AstNode::Name {
518 prefix,
519 value,
520 location,
521 } => {
522 let maybe_prefix: &Option<AstNode> = prefix;
523 match maybe_prefix {
524 None => {
525 to_pel(value, declared_namespaces)
527 }
528 Some(prefix) => {
529 let prefix_str = &prefix.get_value().unwrap();
530 let namespace = &declared_namespaces.resolve_prefix(prefix_str).unwrap();
531 let ns_node = format!(
532 "[{}, {}, {}]",
533 str(":ns"),
534 loc_str(&prefix.location()),
535 str(namespace)
536 );
537 format!(
538 "[{}, {}, {}, {}]",
539 str(":name"),
540 loc_str(location),
541 ns_node,
542 to_pel(value.as_ref(), declared_namespaces)
543 )
544 }
545 }
546 }
547 AstNode::NameIdentifier {
548 name,
549 location: _location,
550 } => {
551 str(name.last().unwrap().as_str())
553 }
554 AstNode::VariableReference {
555 reference,
556 location,
557 } => {
558 format!(
559 "[{}, {}, {}]",
560 str(":ref"),
561 loc_str(location),
562 to_pel(reference.as_ref(), declared_namespaces)
563 )
564 }
565 AstNode::InfixFunctionCall {
566 lhs,
567 func,
568 rhs,
569 location,
570 } => {
571 format!(
572 "[{}, {}, {}, {}, {}]",
573 str(":apply"),
574 loc_str(location),
575 to_pel(func.as_ref(), declared_namespaces),
576 to_pel(lhs.as_ref(), declared_namespaces),
577 to_pel(rhs.as_ref(), declared_namespaces)
578 )
579 }
580 AstNode::FunctionCall {
581 lhs,
582 args,
583 location,
584 } => {
585 if args.is_empty() {
586 format!(
587 "[{}, {}, {}]",
588 str(":apply"),
589 loc_str(location),
590 to_pel(lhs, declared_namespaces)
591 )
592 } else {
593 let elements: Vec<String> = args
594 .iter()
595 .map(|n| to_pel(n, declared_namespaces))
596 .collect();
597 let array_elements = elements.join(", ");
598 format!(
599 "[{}, {}, {}, {}]",
600 str(":apply"),
601 loc_str(location),
602 to_pel(lhs, declared_namespaces),
603 array_elements
604 )
605 }
606 }
607 AstNode::IfElse {
608 condition,
609 if_branch,
610 else_branch,
611 location,
612 } => {
613 format!(
614 "[{}, {}, {}, {}, {}]",
615 str(":if"),
616 loc_str(location),
617 to_pel(condition, declared_namespaces),
618 to_pel(if_branch, declared_namespaces),
619 to_pel(else_branch, declared_namespaces)
620 )
621 }
622 AstNode::Default { lhs, rhs, location } => {
623 format!(
624 "[{}, {}, {}, {}]",
625 str(":default"),
626 loc_str(location),
627 to_pel(lhs, declared_namespaces),
628 to_pel(rhs, declared_namespaces)
629 )
630 }
631 AstNode::Period { value, location } => {
632 format!(
633 "[{}, {}, {}]",
634 str(":period"),
635 loc_str(location),
636 str(value)
637 )
638 }
639 AstNode::Time { value, location } => {
640 format!("[{}, {}, {}]", str(":time"), loc_str(location), str(value))
641 }
642 AstNode::LocalTime { value, location } => {
643 format!("[{}, {}, {}]", str(":ltime"), loc_str(location), str(value))
644 }
645 AstNode::Date { value, location } => {
646 format!("[{}, {}, {}]", str(":ldate"), loc_str(location), str(value))
647 }
648 AstNode::NullSafeSelector { lhs, .. } => to_pel(lhs.as_ref(), declared_namespaces),
649 AstNode::Document { body, .. } => to_pel(body.as_ref(), declared_namespaces),
650 AstNode::StrInterpolation {
651 segments, location, ..
652 } => segments
653 .iter()
654 .map(|n| to_pel(n, declared_namespaces))
655 .reduce(|acc, value| {
656 format!(
657 "[{}, {}, {}, {}]",
658 str(":++"),
659 loc_str(location),
660 acc,
661 value
662 )
663 })
664 .unwrap(),
665 _ => "".to_string(),
666 }
667}
668
669fn loc_str(location: &Location) -> String {
670 str(format!("{}-{}", location.start, location.end).as_str())
671}
672
673fn str(text: &str) -> String {
674 escape_str(text)
675}