1use fml_syntax::ast::{
9 self, AstNode, ConceptMap as ConceptMapNode, Expr, GroupTypeMode, ListMode, ModelMode,
10 ParameterMode, TransformValue,
11};
12use std::collections::HashMap;
13
14use fml_syntax::{
15 Diagnostic, Direction, LineIndex, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
16 TextRange, parse, quote_fhirpath_string,
17};
18use serde_json::{Map, Value, json};
19
20use crate::metadata;
21use crate::model::{
22 Constant, Dependent, Extension, Group, Input, Parameter, ParameterValue, Rule, Source,
23 Structure, StructureMap, Target,
24};
25
26pub const SOURCE_ELEMENT_EXTENSION: &str =
28 "http://hl7.org/fhir/6.0/StructureDefinition/extension-StructureMap.group.rule.source.element";
29pub const TARGET_ELEMENT_EXTENSION: &str =
31 "http://hl7.org/fhir/6.0/StructureDefinition/extension-StructureMap.group.rule.target.element";
32pub const REFERENCES_CONTAINED_EXTENSION: &str =
34 "http://hl7.org/fhir/StructureDefinition/referencesContained";
35pub const AUTO_VARIABLE: &str = "vvv";
37pub const BATCH_UNNAMED_PREFIX: &str = "unnamed";
39
40#[derive(Debug, Clone)]
42pub struct Compiled {
43 pub map: StructureMap,
44 pub diagnostics: Vec<Diagnostic>,
46 pub locations: Locations,
48}
49
50#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct Locations {
55 ranges: HashMap<String, TextRange>,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct Span {
62 pub start_row: u32,
63 pub start_column: u32,
64 pub end_row: u32,
65 pub end_column: u32,
66}
67
68impl Locations {
69 pub fn range(&self, position: &str) -> Option<TextRange> {
71 self.ranges.get(position).copied()
72 }
73
74 pub fn span(&self, text: &str, index: &LineIndex, position: &str) -> Option<Span> {
77 let range = self.range(position)?;
78 let start = index.line_col(text, range.start());
79 let end = index.line_col(text, range.end());
80 Some(Span {
81 start_row: start.line,
82 start_column: start.col_utf16,
83 end_row: end.line,
84 end_column: end.col_utf16,
85 })
86 }
87
88 pub fn len(&self) -> usize {
89 self.ranges.len()
90 }
91
92 pub fn is_empty(&self) -> bool {
93 self.ranges.is_empty()
94 }
95}
96
97impl Compiled {
98 pub fn has_errors(&self) -> bool {
99 self.diagnostics
100 .iter()
101 .any(|d| d.severity == fml_syntax::Severity::Error)
102 }
103}
104
105pub fn compile(text: &str) -> Compiled {
107 let parse = parse(text);
108 let mut compiled = compile_tree(&parse.tree());
109 let mut diagnostics = parse.diagnostics().to_vec();
110 diagnostics.append(&mut compiled.diagnostics);
111 compiled.diagnostics = diagnostics;
112 compiled
113}
114
115pub fn compile_tree(file: &ast::SourceFile) -> Compiled {
117 let mut compiler = Compiler {
118 diagnostics: Vec::new(),
119 locations: Locations::default(),
120 };
121 let map = compiler.file(file);
122 Compiled {
123 map,
124 diagnostics: compiler.diagnostics,
125 locations: compiler.locations,
126 }
127}
128
129struct Compiler {
130 diagnostics: Vec<Diagnostic>,
131 locations: Locations,
132}
133
134impl Compiler {
135 fn error(&mut self, node: &SyntaxNode, message: impl Into<String>) {
136 self.diagnostics
137 .push(Diagnostic::error(trimmed_range(node), message));
138 }
139
140 fn locate(&mut self, position: String, node: &SyntaxNode) {
141 self.locations.ranges.insert(position, trimmed_range(node));
142 }
143
144 fn warning(&mut self, node: &SyntaxNode, message: impl Into<String>) {
145 self.diagnostics
146 .push(Diagnostic::warning(trimmed_range(node), message));
147 }
148
149 fn file(&mut self, file: &ast::SourceFile) -> StructureMap {
150 let mut map = StructureMap::new();
151 if let Some(declaration) = file.map_declaration() {
152 map.url = declaration.url().and_then(|u| u.value());
153 map.name = declaration.name().and_then(|n| n.value());
154 map.description = trailing_comments(declaration.syntax());
155 map.status = Some("draft".to_owned());
156 }
157 for metadata in file.metadata() {
158 self.metadata(&mut map, &metadata);
159 }
160 if map.id.is_none()
161 && let Some(name) = &map.name
162 {
163 let id = make_id(name);
164 if !id.is_empty() {
165 map.id = Some(id);
166 }
167 }
168 if map.status.is_none() {
169 map.status = Some("draft".to_owned());
170 }
171 if map.description.is_none() {
172 map.description = map.title.clone();
173 }
174 for concept_map in file.concept_maps() {
175 let contained = self.concept_map(&concept_map, map.status.as_deref());
176 map.contained.push(contained);
177 }
178 for uses in file.uses() {
179 let mode = uses.mode().unwrap_or(ModelMode::Source);
180 map.structure.push(Structure {
181 url: uses.url().and_then(|u| u.value()).unwrap_or_default(),
182 mode: mode.as_str().to_owned(),
183 alias: uses.alias(),
184 documentation: leading_comments(uses.syntax()),
185 other: Map::new(),
186 });
187 }
188 for import in file.imports() {
189 if let Some(url) = import.url().and_then(|u| u.value()) {
190 map.import.push(url);
191 }
192 }
193 for constant in file.constants() {
194 map.constants.push(Constant {
195 name: constant.name().unwrap_or_default(),
196 value: constant
197 .expression()
198 .map(|e| canonical(&e))
199 .unwrap_or_default(),
200 other: Map::new(),
201 });
202 }
203 for group in file.groups() {
204 let position = format!("StructureMap.group[{}]", map.group.len());
205 let group = self.group(&group, &position);
206 map.group.push(group);
207 }
208 map
209 }
210
211 fn metadata(&mut self, map: &mut StructureMap, metadata: &ast::Metadata) {
212 let Some(path) = metadata.key().map(|k| k.segments()) else {
213 return;
214 };
215 if path.first().is_some_and(|name| metadata::is_generic(name)) {
216 let literal = metadata.value().and_then(|literal| {
217 Some(metadata::Literal {
218 kind: literal.kind()?,
219 text: literal.value()?,
220 })
221 });
222 if let Err(message) = metadata::apply(&mut map.other, &path, literal) {
223 self.error(metadata.syntax(), message);
224 }
225 return;
226 }
227 let key = path.join(".");
228 let value = metadata.value().and_then(|v| v.value());
229 let field = match key.as_str() {
230 "url" => &mut map.url,
231 "name" => &mut map.name,
232 "title" => &mut map.title,
233 "version" => &mut map.version,
234 "description" => &mut map.description,
235 "status" => &mut map.status,
236 "date" => &mut map.date,
237 "publisher" => &mut map.publisher,
238 "purpose" => &mut map.purpose,
239 "copyright" => &mut map.copyright,
240 "experimental" => {
241 map.experimental = value.map(|v| v == "true");
242 return;
243 }
244 _ => {
245 self.warning(
246 metadata.syntax(),
247 format!("metadata `{key}` is not supported and is ignored"),
248 );
249 return;
250 }
251 };
252 *field = value;
253 }
254
255 fn concept_map(&mut self, node: &ConceptMapNode, status: Option<&str>) -> Value {
257 let id = node.url().and_then(|u| u.value()).unwrap_or_default();
258 let prefixes: Vec<(String, String)> = node
259 .prefixes()
260 .filter_map(|p| Some((p.name()?, p.url()?.value()?)))
261 .collect();
262 let resolve = |compiler: &mut Compiler, prefix: Option<String>, at: &SyntaxNode| {
263 let prefix = prefix.unwrap_or_default();
264 match prefixes.iter().find(|(name, _)| *name == prefix) {
265 Some((_, url)) => Some(url.clone()),
266 None => {
267 compiler.error(at, format!("unknown concept map prefix `{prefix}`"));
268 None
269 }
270 }
271 };
272 let mut groups: Vec<ConceptMapGroup> = Vec::new();
273 for unmapped in node.unmapped() {
274 let Some(source) = resolve(self, unmapped.prefix(), unmapped.syntax()) else {
275 continue;
276 };
277 let index = concept_map_group(&mut groups, &source, None);
278 match unmapped.mode().as_deref() {
279 Some("provided") => groups[index].unmapped = Some("use-source-code"),
280 other => self.error(
281 unmapped.syntax(),
282 format!(
283 "unsupported unmapped mode `{}`; only `provided` is defined",
284 other.unwrap_or_default()
285 ),
286 ),
287 }
288 }
289 for entry in node.entries() {
290 let (Some(source_code), Some(target_code)) = (entry.source(), entry.target()) else {
291 continue;
292 };
293 let source = resolve(self, source_code.prefix(), source_code.syntax());
294 let target = resolve(self, target_code.prefix(), target_code.syntax());
295 let (Some(source), Some(target)) = (source, target) else {
296 continue;
297 };
298 let relation = entry.relation().unwrap_or_default();
299 let relationship = match relation.as_str() {
300 "-" => "related-to",
301 "==" => "equivalent",
302 "!=" => "not-related-to",
303 "<=" => "source-is-narrower-than-target",
304 ">=" => "source-is-broader-than-target",
305 other => {
306 self.error(
307 entry.syntax(),
308 format!("unknown concept map relationship `{other}`"),
309 );
310 continue;
311 }
312 };
313 let index = concept_map_group(&mut groups, &source, Some(&target));
314 groups[index].elements.push(json!({
315 "code": source_code.code().unwrap_or_default(),
316 "target": [{
317 "code": target_code.code().unwrap_or_default(),
318 "relationship": relationship,
319 }],
320 }));
321 }
322 let mut map = Map::new();
323 map.insert("resourceType".into(), "ConceptMap".into());
324 map.insert("id".into(), id.into());
325 if let Some(status) = status {
326 map.insert("status".into(), status.into());
327 }
328 let groups: Vec<Value> = groups
329 .into_iter()
330 .map(|g| {
331 let mut group = Map::new();
332 group.insert("source".into(), g.source.into());
333 if let Some(target) = g.target {
334 group.insert("target".into(), target.into());
335 }
336 if !g.elements.is_empty() {
337 group.insert("element".into(), g.elements.into());
338 }
339 if let Some(mode) = g.unmapped {
340 group.insert("unmapped".into(), json!({ "mode": mode }));
341 }
342 Value::Object(group)
343 })
344 .collect();
345 if !groups.is_empty() {
346 map.insert("group".into(), groups.into());
347 }
348 Value::Object(map)
349 }
350
351 fn group(&mut self, node: &ast::Group, position: &str) -> Group {
352 self.locate(position.to_owned(), node.syntax());
353 let mut group = Group {
354 name: node.name().unwrap_or_default(),
355 extends: node.extends(),
356 type_mode: node.type_mode().map(|mode| {
357 match mode {
358 GroupTypeMode::Types => "types",
359 GroupTypeMode::TypeAndTypes => "type-and-types",
360 }
361 .to_owned()
362 }),
363 documentation: leading_comments(node.syntax()),
364 ..Group::default()
365 };
366 for parameter in node.parameters() {
367 group.input.push(Input {
368 name: parameter.name().unwrap_or_default(),
369 type_name: parameter.type_annotation().and_then(|t| t.type_name()),
370 mode: match parameter.mode() {
371 Some(ParameterMode::Target) => "target",
372 _ => "source",
373 }
374 .to_owned(),
375 documentation: None,
376 other: Map::new(),
377 });
378 }
379 if let Some(body) = node.body() {
380 group.rule = self.rules(&body, position);
381 }
382 group
383 }
384
385 fn rules(&mut self, block: &ast::RuleBlock, parent: &str) -> Vec<Rule> {
386 let mut rules = Vec::new();
387 for node in block.rules() {
388 let first = rules.len();
389 self.rule(&node, &mut rules, parent);
390 self.locate_rules(&node, parent, first..rules.len());
391 }
392 rules
393 }
394
395 fn locate_rules(&mut self, node: &ast::Rule, parent: &str, indices: std::ops::Range<usize>) {
397 let batch = indices.len() > 1;
398 let sources: Vec<ast::Source> = node.sources().collect();
399 let targets: Vec<ast::Target> = node.targets().collect();
400 for index in indices {
401 let position = format!("{parent}.rule[{index}]");
402 self.locate(position.clone(), node.syntax());
403 for (k, source) in sources.iter().enumerate() {
404 let at = if batch {
405 node.syntax()
406 } else {
407 source.syntax()
408 };
409 self.locate(format!("{position}.source[{k}]"), at);
410 }
411 for (k, target) in targets.iter().enumerate() {
412 let at = if batch {
413 node.syntax()
414 } else {
415 target.syntax()
416 };
417 self.locate(format!("{position}.target[{k}]"), at);
418 }
419 }
420 }
421
422 fn rule(&mut self, node: &ast::Rule, out: &mut Vec<Rule>, parent: &str) {
424 let mut rule = Rule {
425 documentation: leading_comments(node.syntax()),
426 ..Rule::default()
427 };
428 rule.source = node.sources().map(|s| self.source(&s)).collect();
429 rule.target = node.targets().map(|t| self.target(&t)).collect();
430
431 let fields = node.identity_fields();
432 if !fields.is_empty() {
433 return self.batch_identity(node, rule, &fields, out);
434 }
435
436 if let Some(dependent) = node.dependent() {
437 if let Some(block) = dependent.rules() {
438 let position = format!("{parent}.rule[{}]", out.len());
439 rule.rule = self.rules(&block, &position);
440 }
441 for invocation in dependent.invocations() {
442 rule.dependent.push(Dependent {
443 name: invocation.name().unwrap_or_default(),
444 parameter: invocation
445 .arguments()
446 .map(|argument| self.parameter(&argument, false).into())
447 .collect(),
448 other: Map::new(),
449 });
450 }
451 }
452 if is_simple_copy(&rule) {
453 rule.source[0].variable = Some(AUTO_VARIABLE.to_owned());
454 rule.target[0].variable = Some(AUTO_VARIABLE.to_owned());
455 rule.target[0].transform = Some("create".to_owned());
456 }
457 rule.name = match node.name() {
458 Some(name) => Some(name.replace('-', "")),
459 None => self.generated_name(node, &rule),
460 };
461 out.push(rule);
462 }
463
464 fn generated_name(&mut self, node: &ast::Rule, rule: &Rule) -> Option<String> {
467 if rule.source.len() != 1 {
468 self.error(node.syntax(), "a rule with several sources needs a name");
469 return None;
470 }
471 let source = &rule.source[0];
472 let path = source_path(source);
473 if path.is_empty() {
474 self.warning(
475 node.syntax(),
476 "a rule whose source has no element needs a name",
477 );
478 return None;
479 }
480 Some(match &source.type_name {
481 Some(type_name) => path + &capitalize(type_name),
482 None => path,
483 })
484 }
485
486 fn batch_identity(
488 &mut self,
489 node: &ast::Rule,
490 mut first: Rule,
491 fields: &[String],
492 out: &mut Vec<Rule>,
493 ) {
494 let prefix = node
495 .name()
496 .map(|n| n.replace('-', ""))
497 .unwrap_or_else(|| BATCH_UNNAMED_PREFIX.to_owned());
498 let source_context = first
499 .source
500 .first()
501 .map(|s| s.context.clone())
502 .unwrap_or_default();
503 let target_context = first.target.first().and_then(|t| t.context.clone());
504 if first.target.is_empty() {
505 first.target.push(Target::default());
506 }
507 let copy = |rule: &mut Rule, field: &str| {
508 let source = &mut rule.source[0];
509 source.element = Some(field.to_owned());
510 source.variable = Some(AUTO_VARIABLE.to_owned());
511 let target = &mut rule.target[0];
512 target.element = Some(field.to_owned());
513 target.variable = Some(AUTO_VARIABLE.to_owned());
514 target.transform = Some("create".to_owned());
515 rule.name = Some(make_id(&format!("{prefix}{field}")));
516 };
517 copy(&mut first, &fields[0]);
518 out.push(first);
519 for field in &fields[1..] {
520 let mut rule = Rule {
521 source: vec![Source {
522 context: source_context.clone(),
523 ..Source::default()
524 }],
525 target: vec![Target {
526 context: target_context.clone(),
527 ..Target::default()
528 }],
529 ..Rule::default()
530 };
531 copy(&mut rule, field);
532 out.push(rule);
533 }
534 }
535
536 fn source(&mut self, node: &ast::Source) -> Source {
537 let mut segments = node.path().map(|p| p.segments()).unwrap_or_default();
538 let mut source = Source::default();
539 if !segments.is_empty() {
540 source.context = segments.remove(0);
541 }
542 let mut segments = segments.into_iter();
543 source.element = segments.next();
544 source.extension = segments
545 .map(|s| Extension::string(SOURCE_ELEMENT_EXTENSION, s))
546 .collect();
547 source.type_name = node.type_annotation().and_then(|t| t.type_name());
548 if let Some((min, max)) = node.cardinality() {
549 source.min = min.parse().ok();
550 source.max = Some(max);
551 }
552 if let Some(default) = node.default_value()
553 && let Some(expression) = default.expression()
554 {
555 source.default_value = Some(match expression.syntax().kind() {
557 SyntaxKind::Literal
558 if expression
559 .syntax()
560 .first_token()
561 .is_some_and(|t| t.kind() == SyntaxKind::DoubleQuotedString) =>
562 {
563 quote_fhirpath_string(&ast::unescape(
564 expression.syntax().first_token().unwrap().text(),
565 ))
566 }
567 _ => canonical(&expression),
568 });
569 }
570 source.list_mode = node
571 .list_mode()
572 .map(|mode| list_mode_code(&mode).to_owned());
573 source.variable = node.alias();
574 source.condition = node.where_clause().map(|e| canonical(&e));
575 source.check = node.check_clause().map(|e| canonical(&e));
576 source.log_message = node.log_clause().map(|e| canonical(&e));
577 source
578 }
579
580 fn target(&mut self, node: &ast::Target) -> Target {
581 let mut target = Target::default();
582 if let Some(path) = node.path() {
583 let mut segments = path.segments().into_iter();
584 target.context = segments.next();
585 target.element = segments.next();
586 target.extension = segments
587 .map(|s| Extension::string(TARGET_ELEMENT_EXTENSION, s))
588 .collect();
589 }
590 match node.transform() {
591 Some(TransformValue::Literal(literal)) => {
592 target.transform = Some("copy".to_owned());
593 target.parameter.push(literal_parameter(&literal).into());
594 }
595 Some(TransformValue::Name(name)) => {
596 target.transform = Some("copy".to_owned());
597 target
598 .parameter
599 .push(ParameterValue::Id(name.text()).into());
600 }
601 Some(TransformValue::Expression(expression)) => {
602 target.transform = Some("evaluate".to_owned());
603 target
604 .parameter
605 .push(ParameterValue::String(canonical(&expression)).into());
606 }
607 Some(TransformValue::Invocation(invocation)) => {
608 let name = invocation.name().unwrap_or_default();
609 if !TRANSFORMS.contains(&name.as_str()) {
610 self.error(invocation.syntax(), format!("unknown transform `{name}`"));
611 }
612 let evaluate = name == "evaluate";
613 target.parameter = invocation
614 .arguments()
615 .enumerate()
616 .map(|(index, argument)| {
617 Parameter::from(self.parameter(&argument, evaluate && index == 1))
618 })
619 .collect();
620 if name == "translate"
623 && let Some(parameter) = target.parameter.get_mut(1)
624 && let ParameterValue::String(map) = ¶meter.value
625 && map.starts_with('#')
626 {
627 parameter.value_element = Some(json!({
628 "extension": [{
629 "url": REFERENCES_CONTAINED_EXTENSION,
630 "valueReference": { "reference": map },
631 }],
632 }));
633 }
634 target.transform = Some(name);
635 }
636 None => {}
637 }
638 target.variable = node.alias();
639 if let Some(mode) = node.list_mode() {
640 if let ListMode::Share(id) = &mode {
641 target.list_rule_id = Some(id.clone());
642 }
643 target.list_mode.push(list_mode_code(&mode).to_owned());
644 }
645 target
646 }
647
648 fn parameter(&mut self, argument: &Expr, expression: bool) -> ParameterValue {
651 if expression {
652 return ParameterValue::String(canonical(argument));
653 }
654 let node = argument.syntax();
655 match node.kind() {
656 SyntaxKind::NameRef => {
657 let token = node.first_token().expect("a name has a token");
658 ParameterValue::Id(ast::name_text(&token))
659 }
660 SyntaxKind::Literal => match ast::Literal::cast(node.clone()) {
661 Some(literal) => literal_parameter(&literal),
662 None => ParameterValue::String(canonical(argument)),
663 },
664 SyntaxKind::PrefixExpr
665 if node
666 .first_token()
667 .is_some_and(|t| t.kind() == SyntaxKind::Minus) =>
668 {
669 let text: String = canonical(argument).split_whitespace().collect();
670 if let Ok(value) = text.parse::<i64>() {
671 ParameterValue::Integer(value)
672 } else if let Ok(value) = text.parse::<serde_json::Number>() {
673 ParameterValue::Decimal(value)
674 } else {
675 self.complex_parameter(argument)
676 }
677 }
678 _ => self.complex_parameter(argument),
679 }
680 }
681
682 fn complex_parameter(&mut self, argument: &Expr) -> ParameterValue {
683 self.error(
684 argument.syntax(),
685 "a transform argument must be a variable or a constant; use `evaluate` for expressions",
686 );
687 ParameterValue::String(canonical(argument))
688 }
689}
690
691const TRANSFORMS: &[&str] = &[
693 "create",
694 "copy",
695 "truncate",
696 "escape",
697 "cast",
698 "append",
699 "translate",
700 "reference",
701 "dateOp",
702 "uuid",
703 "pointer",
704 "evaluate",
705 "cc",
706 "c",
707 "qty",
708 "id",
709 "cp",
710 "toDateTime",
711 "unixToDateTime",
712 "toDate",
713 "unixToDate",
714 "toTime",
715 "unixToTime",
716];
717
718struct ConceptMapGroup {
719 source: String,
720 target: Option<String>,
721 elements: Vec<Value>,
722 unmapped: Option<&'static str>,
723}
724
725fn concept_map_group(
728 groups: &mut Vec<ConceptMapGroup>,
729 source: &str,
730 target: Option<&str>,
731) -> usize {
732 for (index, group) in groups.iter_mut().enumerate() {
733 if group.source == source
734 && (group.target.is_none() || target.is_none() || group.target.as_deref() == target)
735 {
736 if group.target.is_none()
737 && let Some(target) = target
738 {
739 group.target = Some(target.to_owned());
740 }
741 return index;
742 }
743 }
744 groups.push(ConceptMapGroup {
745 source: source.to_owned(),
746 target: target.map(str::to_owned),
747 elements: Vec::new(),
748 unmapped: None,
749 });
750 groups.len() - 1
751}
752
753fn is_simple_copy(rule: &Rule) -> bool {
756 let [source] = rule.source.as_slice() else {
757 return false;
758 };
759 let [target] = rule.target.as_slice() else {
760 return false;
761 };
762 !source.context.is_empty()
763 && source.element.is_some()
764 && source.variable.is_none()
765 && target.context.is_some()
766 && target.element.is_some()
767 && target.variable.is_none()
768 && target.parameter.is_empty()
769 && rule.dependent.is_empty()
770 && rule.rule.is_empty()
771}
772
773pub fn source_path(source: &Source) -> String {
775 source
776 .element
777 .iter()
778 .cloned()
779 .chain(
780 source
781 .extension
782 .iter()
783 .filter(|e| e.url == SOURCE_ELEMENT_EXTENSION)
784 .filter_map(|e| e.value_string.clone()),
785 )
786 .collect::<Vec<_>>()
787 .join(".")
788}
789
790fn canonical(expression: &Expr) -> String {
792 crate::canonical::canonical(expression)
793}
794
795fn literal_parameter(literal: &ast::Literal) -> ParameterValue {
796 let value = literal.value().unwrap_or_default();
797 match literal.kind() {
798 Some(SyntaxKind::Integer | SyntaxKind::LongNumber) => value
799 .trim_end_matches('L')
800 .parse()
801 .map(ParameterValue::Integer)
802 .unwrap_or(ParameterValue::String(value)),
803 Some(SyntaxKind::Decimal) => value
804 .parse()
805 .map(ParameterValue::Decimal)
806 .unwrap_or(ParameterValue::String(value)),
807 Some(SyntaxKind::Date) => ParameterValue::Date(value.trim_start_matches('@').to_owned()),
808 Some(SyntaxKind::DateTime) => {
809 ParameterValue::DateTime(value.trim_start_matches('@').to_owned())
810 }
811 Some(SyntaxKind::Time) => ParameterValue::Time(value.trim_start_matches("@T").to_owned()),
812 Some(SyntaxKind::Ident) if value == "true" || value == "false" => {
813 ParameterValue::Boolean(value == "true")
814 }
815 _ => ParameterValue::String(value),
816 }
817}
818
819fn list_mode_code(mode: &ListMode) -> &'static str {
820 match mode {
821 ListMode::First => "first",
822 ListMode::NotFirst => "not_first",
823 ListMode::Last => "last",
824 ListMode::NotLast => "not_last",
825 ListMode::OnlyOne => "only_one",
826 ListMode::Single => "single",
827 ListMode::Collate => "collate",
828 ListMode::Share(_) => "share",
829 }
830}
831
832pub fn make_id(name: &str) -> String {
834 name.chars()
835 .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '.')
836 .collect()
837}
838
839fn capitalize(text: &str) -> String {
840 let mut chars = text.chars();
841 match chars.next() {
842 Some(first) => first.to_uppercase().chain(chars).collect(),
843 None => String::new(),
844 }
845}
846
847fn leading_comments(node: &SyntaxNode) -> Option<String> {
851 let mut comments = Vec::new();
852 let mut previous: Option<SyntaxElement> = None;
853 for element in node.siblings_with_tokens(Direction::Prev).skip(1) {
854 match element {
855 NodeOrToken::Token(token) if token.kind() == SyntaxKind::Whitespace => {}
856 NodeOrToken::Token(token) if token.kind().is_comment() => comments.push(token),
857 element => {
858 previous = Some(element);
859 break;
860 }
861 }
862 }
863 comments.reverse();
864 if previous
866 .as_ref()
867 .is_some_and(|p| p.kind() == SyntaxKind::MapDeclaration)
868 {
869 return None;
870 }
871 let owns_trailing = previous
872 .as_ref()
873 .is_some_and(|p| matches!(p.kind(), SyntaxKind::Rule | SyntaxKind::UsesDeclaration));
874 if owns_trailing
875 && comments
876 .first()
877 .is_some_and(|first| !after_line_break(first))
878 {
879 comments.remove(0);
880 }
881 if comments.is_empty() {
882 return None;
883 }
884 Some(
885 comments
886 .iter()
887 .map(|token| comment_text(token.text()))
888 .collect::<Vec<_>>()
889 .join("\r\n"),
890 )
891}
892
893fn after_line_break(token: &fml_syntax::SyntaxToken) -> bool {
895 token.prev_sibling_or_token().is_some_and(|e| match e {
896 NodeOrToken::Token(t) => {
897 t.kind() == SyntaxKind::Whitespace && t.text().contains(['\n', '\r'])
898 }
899 NodeOrToken::Node(_) => false,
900 })
901}
902
903fn trailing_comments(node: &SyntaxNode) -> Option<String> {
905 let comments: Vec<String> = node
906 .siblings_with_tokens(Direction::Next)
907 .skip(1)
908 .take_while(|e| e.kind().is_trivia())
909 .filter_map(|e| e.into_token())
910 .filter(|t| t.kind().is_comment())
911 .map(|t| comment_text(t.text()))
912 .collect();
913 (!comments.is_empty()).then(|| comments.join("\r\n"))
914}
915
916fn comment_text(text: &str) -> String {
917 let inner = if let Some(line) = text.strip_prefix("//") {
918 line
919 } else {
920 text.strip_prefix("/*")
921 .map(|t| t.strip_suffix("*/").unwrap_or(t))
922 .unwrap_or(text)
923 };
924 inner.trim().to_owned()
925}
926
927fn trimmed_range(node: &SyntaxNode) -> TextRange {
929 let mut tokens = node
930 .descendants_with_tokens()
931 .filter_map(NodeOrToken::into_token)
932 .filter(|t| !t.kind().is_trivia());
933 match tokens.next() {
934 Some(first) => {
935 let end = tokens
936 .last()
937 .map_or(first.text_range().end(), |t| t.text_range().end());
938 TextRange::new(first.text_range().start(), end)
939 }
940 None => node.text_range(),
941 }
942}