1use crate::writer::{Writer, comment_lines, verbatim_lines};
29use alloc::string::{String, ToString};
30use alloc::vec::Vec;
31use zerodds_lang_cst::ada::{
32 AllocInit, Arm, Choice, Component, Decl, Discriminant, EnumRep, Expr, File, Index, Mode,
33 ObjectDecl, Param, Stmt, Subprogram, SubprogramBody, SubprogramKind, SubtypeDecl, TypeDecl,
34 TypeDef, VarDecl, Variant,
35};
36use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
37
38const UNIT: &str = " ";
40
41#[must_use]
43pub fn file(file: &File) -> String {
44 let mut w = Writer::new(UNIT);
45 doc(&mut w, &file.doc);
46 if !file.withs.is_empty() || !file.use_types.is_empty() {
47 if !file.doc.is_empty() {
48 w.blank_line();
49 }
50 for unit in &file.withs {
51 let mut line = String::from("with ");
52 line.push_str(&path_text(unit));
53 line.push(';');
54 w.line(&line);
55 }
56 for mark in &file.use_types {
61 let mut line = String::from("use type ");
62 line.push_str(&path_text(mark));
63 line.push(';');
64 w.line(&line);
65 }
66 }
67 w.blank_line();
68 let mut head = String::from("package ");
69 head.push_str(file.package.as_str());
70 head.push_str(" is");
71 w.line(&head);
72 w.indent();
73 for decl in &file.decls {
74 w.blank_line();
75 render_decl(&mut w, decl);
76 }
77 w.dedent();
78 w.blank_line();
79 let mut tail = String::from("end ");
80 tail.push_str(file.package.as_str());
81 tail.push(';');
82 w.line(&tail);
83 w.finish()
84}
85
86#[must_use]
96pub fn package_body(package: &Ident, decls: &[Decl]) -> String {
97 let mut w = Writer::new(UNIT);
98 let mut head = String::from("package body ");
99 head.push_str(package.as_str());
100 head.push_str(" is");
101 w.line(&head);
102 w.indent();
103 for decl in decls {
104 w.blank_line();
105 render_decl(&mut w, decl);
106 }
107 w.dedent();
108 w.blank_line();
109 let mut tail = String::from("end ");
110 tail.push_str(package.as_str());
111 tail.push(';');
112 w.line(&tail);
113 w.finish()
114}
115
116#[must_use]
118pub fn decl(node: &Decl) -> String {
119 let mut w = Writer::new(UNIT);
120 render_decl(&mut w, node);
121 w.finish()
122}
123
124#[must_use]
126pub fn expr(node: &Expr) -> String {
127 expr_text(node)
128}
129
130#[must_use]
132pub fn ident(name: &Ident) -> String {
133 name.as_str().to_string()
134}
135
136#[must_use]
138pub fn path(name: &Path) -> String {
139 path_text(name)
140}
141
142fn path_text(name: &Path) -> String {
143 name.segments()
144 .iter()
145 .map(|segment| segment.as_str())
146 .collect::<Vec<&str>>()
147 .join(".")
148}
149
150#[must_use]
152pub fn stmt(node: &Stmt) -> String {
153 let mut w = Writer::new(UNIT);
154 render_stmts(&mut w, core::slice::from_ref(node));
155 w.finish()
156}
157
158#[must_use]
160pub fn stmts(nodes: &[Stmt]) -> String {
161 let mut w = Writer::new(UNIT);
162 render_stmts(&mut w, nodes);
163 w.finish()
164}
165
166fn render_decl(w: &mut Writer, node: &Decl) {
167 match node {
168 Decl::Type(node) => render_type_decl(w, node),
169 Decl::Subtype(node) => render_subtype(w, node),
170 Decl::Object(node) => render_object(w, node),
171 Decl::EnumRep(node) => render_enum_rep(w, node),
172 Decl::Subprogram(node) => render_subprogram(w, node),
173 Decl::SubprogramBody(node) => render_subprogram_body(w, node),
174 Decl::Comment(lines) => doc(w, lines),
175 Decl::Verbatim(text) => verbatim_lines(w, text),
176 }
177}
178
179fn render_type_decl(w: &mut Writer, node: &TypeDecl) {
180 doc(w, &node.doc);
181 let mut head = String::from("type ");
182 head.push_str(node.name.as_str());
183 match &node.def {
184 TypeDef::Record(record) => {
185 head.push_str(&discriminant_text(&record.discriminants));
186 if record.is_null() {
187 head.push_str(" is null record;");
188 w.line(&head);
189 return;
190 }
191 head.push_str(" is record");
192 w.line(&head);
193 w.indent();
194 for text in &record.body_text.begin {
195 verbatim_lines(w, text);
196 }
197 for component in &record.components {
198 render_component(w, component);
199 }
200 if let Some(variant) = &record.variant {
201 render_variant(w, variant);
202 }
203 for text in &record.body_text.end {
204 verbatim_lines(w, text);
205 }
206 w.dedent();
207 w.line("end record;");
208 }
209 TypeDef::Enumeration(literals) => {
210 head.push_str(" is (");
211 head.push_str(
212 &literals
213 .iter()
214 .map(|literal| literal.as_str())
215 .collect::<Vec<&str>>()
216 .join(", "),
217 );
218 head.push_str(");");
219 w.line(&head);
220 }
221 TypeDef::Derived(mark) => {
222 head.push_str(" is new ");
223 head.push_str(&path_text(mark));
224 head.push(';');
225 w.line(&head);
226 }
227 TypeDef::Array { index, element } => {
228 head.push_str(" is array (");
229 head.push_str(
230 &index
231 .iter()
232 .map(index_text)
233 .collect::<Vec<String>>()
234 .join(", "),
235 );
236 head.push_str(") of ");
237 head.push_str(&path_text(element));
238 head.push(';');
239 w.line(&head);
240 }
241 TypeDef::Access(designated) => {
242 head.push_str(" is access ");
243 head.push_str(&path_text(designated));
244 head.push(';');
245 w.line(&head);
246 }
247 TypeDef::Interface => {
248 head.push_str(" is interface;");
249 w.line(&head);
250 }
251 TypeDef::Incomplete => {
254 head.push(';');
255 w.line(&head);
256 }
257 }
258}
259
260fn discriminant_text(discriminants: &[Discriminant]) -> String {
262 if discriminants.is_empty() {
263 return String::new();
264 }
265 let parts: Vec<String> = discriminants
266 .iter()
267 .map(|discriminant| {
268 let mut out = String::from(discriminant.name.as_str());
269 out.push_str(" : ");
270 out.push_str(&path_text(&discriminant.type_));
271 if let Some(default) = &discriminant.default {
272 out.push_str(" := ");
273 out.push_str(&expr_text(default));
274 }
275 out
276 })
277 .collect();
278 let mut out = String::from(" (");
279 out.push_str(&parts.join("; "));
280 out.push(')');
281 out
282}
283
284fn render_component(w: &mut Writer, node: &Component) {
285 doc(w, &node.doc);
286 let mut line = String::from(node.name.as_str());
287 line.push_str(" : ");
288 line.push_str(&path_text(&node.type_));
289 line.push(';');
290 w.line(&line);
291}
292
293fn render_variant(w: &mut Writer, node: &Variant) {
299 let mut head = String::from("case ");
300 head.push_str(node.discriminant.as_str());
301 head.push_str(" is");
302 w.line(&head);
303 w.indent();
304 for arm in &node.arms {
305 render_arm(w, arm);
306 }
307 w.dedent();
308 w.line("end case;");
309}
310
311fn render_arm(w: &mut Writer, node: &Arm) {
312 let mut head = String::from("when ");
313 head.push_str(
314 &node
315 .choices
316 .iter()
317 .map(choice_text)
318 .collect::<Vec<String>>()
319 .join(" | "),
320 );
321 head.push_str(" =>");
322 w.line(&head);
323 w.indent();
324 if node.components.is_empty() {
325 w.line("null;");
326 } else {
327 for component in &node.components {
328 render_component(w, component);
329 }
330 }
331 w.dedent();
332}
333
334fn choice_text(node: &Choice) -> String {
335 match node {
336 Choice::Value(value) => expr_text(value),
337 Choice::Others => "others".to_string(),
338 }
339}
340
341fn index_text(node: &Index) -> String {
342 match node {
343 Index::Range { low, high } => {
344 let mut out = expr_text(low);
345 out.push_str(" .. ");
346 out.push_str(&expr_text(high));
347 out
348 }
349 Index::Unconstrained(mark) => {
350 let mut out = path_text(mark);
351 out.push_str(" range <>");
352 out
353 }
354 }
355}
356
357fn render_subtype(w: &mut Writer, node: &SubtypeDecl) {
358 doc(w, &node.doc);
359 let mut line = String::from("subtype ");
360 line.push_str(node.name.as_str());
361 line.push_str(" is ");
362 line.push_str(&path_text(&node.mark));
363 line.push(';');
364 w.line(&line);
365}
366
367fn render_object(w: &mut Writer, node: &ObjectDecl) {
368 doc(w, &node.doc);
369 let mut line = String::from(node.name.as_str());
370 line.push_str(" : constant ");
371 line.push_str(&path_text(&node.type_));
372 line.push_str(" := ");
373 line.push_str(&expr_text(&node.value));
374 line.push(';');
375 w.line(&line);
376}
377
378fn render_enum_rep(w: &mut Writer, node: &EnumRep) {
379 doc(w, &node.doc);
380 let mut line = String::from("for ");
381 line.push_str(node.type_.as_str());
382 line.push_str(" use (");
383 line.push_str(
384 &node
385 .values
386 .iter()
387 .map(|(literal, code)| {
388 let mut out = String::from(literal.as_str());
389 out.push_str(" => ");
390 out.push_str(&expr_text(code));
391 out
392 })
393 .collect::<Vec<String>>()
394 .join(", "),
395 );
396 line.push_str(");");
397 w.line(&line);
398}
399
400fn subprogram_head(node: &Subprogram) -> String {
401 let mut line = String::from(match node.kind {
402 SubprogramKind::Procedure => "procedure ",
403 SubprogramKind::Function => "function ",
404 });
405 line.push_str(node.name.as_str());
406 if !node.params.is_empty() {
407 line.push_str(" (");
408 line.push_str(
409 &node
410 .params
411 .iter()
412 .map(param_text)
413 .collect::<Vec<String>>()
414 .join("; "),
415 );
416 line.push(')');
417 }
418 if let Some(result) = &node.result {
419 line.push_str(" return ");
420 line.push_str(&path_text(result));
421 }
422 line
423}
424
425fn render_subprogram(w: &mut Writer, node: &Subprogram) {
426 doc(w, &node.doc);
427 let mut line = subprogram_head(node);
428 if node.is_abstract {
429 line.push_str(" is abstract");
430 }
431 line.push(';');
432 w.line(&line);
433}
434
435fn render_subprogram_body(w: &mut Writer, node: &SubprogramBody) {
442 doc(w, &node.spec.doc);
443 let mut head = subprogram_head(&node.spec);
444 head.push_str(" is");
445 w.line(&head);
446 let split = node
447 .body
448 .iter()
449 .position(|statement| {
450 !matches!(
451 statement,
452 Stmt::Var(_) | Stmt::Subtype { .. } | Stmt::Comment(_)
453 )
454 })
455 .unwrap_or(node.body.len());
456 w.indent();
457 for statement in &node.body[..split] {
458 match statement {
459 Stmt::Var(var) => w.line(&var_text(var)),
460 Stmt::Subtype { name, mark } => w.line(&subtype_text(name, mark)),
461 Stmt::Comment(lines) => doc(w, lines),
462 _ => {}
463 }
464 }
465 w.dedent();
466 w.line("begin");
467 w.indent();
468 if node.body[split..].is_empty() {
469 w.line("null;");
470 } else {
471 render_stmts(w, &node.body[split..]);
472 }
473 w.dedent();
474 let mut tail = String::from("end ");
475 tail.push_str(node.spec.name.as_str());
476 tail.push(';');
477 w.line(&tail);
478}
479
480fn var_text(node: &VarDecl) -> String {
482 let mut out = String::from(node.name.as_str());
483 out.push_str(" : ");
484 out.push_str(&path_text(&node.mark));
485 if let Some(constraint) = &node.constraint {
486 out.push_str(" (");
487 out.push_str(&expr_text(constraint));
488 out.push(')');
489 }
490 if let Some(value) = &node.value {
491 out.push_str(" := ");
492 out.push_str(&expr_text(value));
493 }
494 out.push(';');
495 out
496}
497
498fn subtype_text(name: &Ident, mark: &Path) -> String {
499 let mut out = String::from("subtype ");
500 out.push_str(name.as_str());
501 out.push_str(" is ");
502 out.push_str(&path_text(mark));
503 out.push(';');
504 out
505}
506
507fn render_stmts(w: &mut Writer, nodes: &[Stmt]) {
522 let mut at = 0;
523 while at < nodes.len() {
524 let statement = &nodes[at];
525 if matches!(statement, Stmt::Var(_) | Stmt::Subtype { .. }) {
526 w.line("declare");
527 w.indent();
528 while at < nodes.len() {
529 match &nodes[at] {
530 Stmt::Var(var) => w.line(&var_text(var)),
531 Stmt::Subtype { name, mark } => w.line(&subtype_text(name, mark)),
532 Stmt::Comment(lines) => doc(w, lines),
533 _ => break,
534 }
535 at += 1;
536 }
537 w.dedent();
538 w.line("begin");
539 w.indent();
540 if nodes[at..].is_empty() {
541 w.line("null;");
542 } else {
543 render_stmts(w, &nodes[at..]);
544 }
545 w.dedent();
546 w.line("end;");
547 return;
548 }
549 render_stmt(w, statement);
550 at += 1;
551 }
552}
553
554fn render_stmt(w: &mut Writer, node: &Stmt) {
565 match node {
566 Stmt::Null => w.line("null;"),
567 Stmt::Var(var) => w.line(&var_text(var)),
570 Stmt::Subtype { name, mark } => w.line(&subtype_text(name, mark)),
571 Stmt::Assign { place, value } => {
572 let mut line = expr_text(place);
573 line.push_str(" := ");
574 line.push_str(&expr_text(value));
575 line.push(';');
576 w.line(&line);
577 }
578 Stmt::Call(value) => {
579 let mut line = expr_text(value);
580 line.push(';');
581 w.line(&line);
582 }
583 Stmt::Return(value) => match value {
584 Some(value) => {
585 let mut line = String::from("return ");
586 line.push_str(&expr_text(value));
587 line.push(';');
588 w.line(&line);
589 }
590 None => w.line("return;"),
591 },
592 Stmt::If {
593 condition,
594 then,
595 otherwise,
596 } => {
597 let mut head = String::from("if ");
598 head.push_str(&expr_text(condition));
599 head.push_str(" then");
600 w.line(&head);
601 w.indent();
602 if then.is_empty() {
603 w.line("null;");
604 } else {
605 render_stmts(w, then);
606 }
607 w.dedent();
608 if let Some(otherwise) = otherwise {
609 w.line("else");
610 w.indent();
611 if otherwise.is_empty() {
612 w.line("null;");
613 } else {
614 render_stmts(w, otherwise);
615 }
616 w.dedent();
617 }
618 w.line("end if;");
619 }
620 Stmt::For {
621 counter,
622 low,
623 high,
624 body,
625 } => {
626 let mut head = String::from("for ");
627 head.push_str(counter.as_str());
628 head.push_str(" in ");
629 head.push_str(&expr_text(low));
630 head.push_str(" .. ");
631 head.push_str(&expr_text(high));
632 head.push_str(" loop");
633 w.line(&head);
634 w.indent();
635 if body.is_empty() {
636 w.line("null;");
637 } else {
638 render_stmts(w, body);
639 }
640 w.dedent();
641 w.line("end loop;");
642 }
643 Stmt::While { condition, body } => {
644 let mut head = String::from("while ");
645 head.push_str(&expr_text(condition));
646 head.push_str(" loop");
647 w.line(&head);
648 w.indent();
649 if body.is_empty() {
650 w.line("null;");
651 } else {
652 render_stmts(w, body);
653 }
654 w.dedent();
655 w.line("end loop;");
656 }
657 Stmt::Exit => w.line("exit;"),
658 Stmt::Declare { vars, body } => {
659 w.line("declare");
660 w.indent();
661 for var in vars {
662 w.line(&var_text(var));
663 }
664 w.dedent();
665 w.line("begin");
666 w.indent();
667 if body.is_empty() {
668 w.line("null;");
669 } else {
670 render_stmts(w, body);
671 }
672 w.dedent();
673 w.line("end;");
674 }
675 Stmt::Comment(lines) => doc(w, lines),
676 }
677}
678
679fn param_text(node: &Param) -> String {
680 let mut out = String::from(node.name.as_str());
681 out.push_str(" : ");
682 out.push_str(mode_text(node.mode));
687 out.push(' ');
688 out.push_str(&path_text(&node.type_));
689 out
690}
691
692const fn mode_text(mode: Mode) -> &'static str {
693 mode.as_str()
694}
695
696fn expr_text(node: &Expr) -> String {
703 match node {
704 Expr::Literal(literal) => literal_text(literal),
705 Expr::Name(name) => path_text(name),
706 Expr::Call { name, args } => {
707 let mut out = path_text(name);
708 if !args.is_empty() {
711 out.push_str(" (");
712 out.push_str(
713 &args
714 .iter()
715 .map(expr_text)
716 .collect::<Vec<String>>()
717 .join(", "),
718 );
719 out.push(')');
720 }
721 out
722 }
723 Expr::Negate(inner) => {
724 let mut out = String::from("-");
725 out.push_str(&expr_text(inner));
726 out
727 }
728 Expr::Binary { op, lhs, rhs } => {
729 let mut out = operand_text(lhs);
730 out.push(' ');
731 out.push_str(ada_op(op));
732 out.push(' ');
733 out.push_str(&operand_text(rhs));
734 out
735 }
736 Expr::Unary { op, value } => {
737 let mut out = String::from(ada_op(op));
738 if *op != "-" {
739 out.push(' ');
740 }
741 out.push_str(&operand_text(value));
742 out
743 }
744 Expr::Selected { value, name } => {
745 let mut out = operand_text(value);
746 out.push('.');
747 out.push_str(name.as_str());
748 out
749 }
750 Expr::Indexed { value, index } => {
751 let mut out = operand_text(value);
752 out.push_str(" (");
753 out.push_str(&expr_text(index));
754 out.push(')');
755 out
756 }
757 Expr::Deref(value) => {
758 let mut out = operand_text(value);
759 out.push_str(".all");
760 out
761 }
762 Expr::Slice { value, low, high } => {
763 let mut out = operand_text(value);
764 out.push_str(" (");
765 out.push_str(&expr_text(low));
766 out.push_str(" .. ");
767 out.push_str(&expr_text(high));
768 out.push(')');
769 out
770 }
771 Expr::Attribute { value, name, args } => {
772 let mut out = operand_text(value);
773 out.push('\'');
774 out.push_str(name.as_str());
775 if !args.is_empty() {
776 out.push_str(" (");
777 out.push_str(
778 &args
779 .iter()
780 .map(expr_text)
781 .collect::<Vec<String>>()
782 .join(", "),
783 );
784 out.push(')');
785 }
786 out
787 }
788 Expr::Convert { mark, value } => {
789 let mut out = path_text(mark);
790 out.push_str(" (");
791 out.push_str(&expr_text(value));
792 out.push(')');
793 out
794 }
795 Expr::Aggregate(parts) => {
796 if parts.is_empty() {
797 return "(null record)".to_string();
798 }
799 let mut out = String::from("(");
800 out.push_str(
801 &parts
802 .iter()
803 .map(|part| {
804 let mut one = String::from(part.choice.as_str());
805 one.push_str(" => ");
806 one.push_str(&expr_text(&part.value));
807 one
808 })
809 .collect::<Vec<String>>()
810 .join(", "),
811 );
812 out.push(')');
813 out
814 }
815 Expr::BoxDefault => "<>".to_string(),
816 Expr::New { mark, init } => {
817 let mut out = String::from("new ");
818 out.push_str(&path_text(mark));
819 match init {
820 AllocInit::Range { low, high } => {
821 out.push_str(" (");
822 out.push_str(&expr_text(low));
823 out.push_str(" .. ");
824 out.push_str(&expr_text(high));
825 out.push(')');
826 }
827 AllocInit::Qualified(value) => {
828 out.push_str("'(");
829 out.push_str(&expr_text(value));
830 out.push(')');
831 }
832 }
833 out
834 }
835 }
836}
837
838fn operand_text(node: &Expr) -> String {
851 match node {
852 Expr::Binary { .. } | Expr::Unary { .. } | Expr::Negate(_) => {
853 let mut out = String::from("(");
854 out.push_str(&expr_text(node));
855 out.push(')');
856 out
857 }
858 _ => expr_text(node),
859 }
860}
861
862fn ada_op(op: &str) -> &str {
864 match op {
865 "==" => "=",
866 "!=" => "/=",
867 "&&" => "and then",
868 "||" => "or else",
869 "&" => "and",
870 "|" => "or",
871 "^" => "xor",
872 "%" => "mod",
873 "!" | "~" => "not",
874 other => other,
875 }
876}
877
878fn literal_text(literal: &Literal) -> String {
879 match literal {
880 Literal::Int(text) | Literal::Float(text) => text.clone(),
881 Literal::Bool(value) => {
882 if *value {
883 "True".to_string()
884 } else {
885 "False".to_string()
886 }
887 }
888 Literal::Char(ch) => alloc::format!("{}", u32::from(*ch)),
894 Literal::Str(text) => ada_string(text),
895 Literal::Bytes(bytes) => {
898 let mut out = String::from("(");
899 let parts: Vec<String> = bytes
900 .iter()
901 .map(|b| alloc::format!("16#{b:02X}#"))
902 .collect();
903 out.push_str(&parts.join(", "));
904 out.push(')');
905 out
906 }
907 Literal::Null => "null".to_string(),
908 }
909}
910
911fn ada_string(text: &str) -> String {
924 let mut out = String::from("\"");
925 for ch in text.chars() {
926 if ch == '"' {
927 out.push('"');
928 }
929 out.push(ch);
930 }
931 out.push('"');
932 out
933}
934
935fn doc(w: &mut Writer, doc: &Doc) {
936 if doc.is_empty() {
937 return;
938 }
939 comment_lines(w, "-- ", doc.as_lines());
940}
941
942#[cfg(test)]
943mod tests {
944 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
945 use super::*;
946 use alloc::vec;
947 use zerodds_lang_cst::ada::{Primitive, Record};
948
949 fn component(name: &str, type_: Primitive) -> Component {
950 Component {
951 doc: Doc::none(),
952 name: Ident::new(name),
953 type_: type_.path(),
954 }
955 }
956
957 #[test]
958 fn a_record_writes_one_component_per_line() {
959 let node = Decl::Type(TypeDecl {
960 doc: Doc::none(),
961 name: Ident::new("Pose"),
962 def: TypeDef::Record(Record::plain(vec![
963 component("X", Primitive::Integer32),
964 component("Y", Primitive::Float64),
965 ])),
966 });
967 assert_eq!(
968 decl(&node),
969 "type Pose is record\n \
970 X : Interfaces.Integer_32;\n \
971 Y : Interfaces.IEEE_Float_64;\n\
972 end record;\n"
973 );
974 }
975
976 #[test]
977 fn a_record_with_no_components_is_a_null_record() {
978 let node = Decl::Type(TypeDecl {
982 doc: Doc::none(),
983 name: Ident::new("Empty"),
984 def: TypeDef::Record(Record::plain(Vec::new())),
985 });
986 assert_eq!(decl(&node), "type Empty is null record;\n");
987 }
988
989 #[test]
990 fn a_discriminated_record_writes_its_variant_part() {
991 let node = Decl::Type(TypeDecl {
992 doc: Doc::none(),
993 name: Ident::new("Payload"),
994 def: TypeDef::Record(Record {
995 body_text: zerodds_lang_cst::common::BodyText::none(),
996 discriminants: vec![Discriminant {
997 name: Ident::new("D"),
998 type_: Path::single("Mode"),
999 default: Some(Expr::name("IDLE")),
1000 }],
1001 components: Vec::new(),
1002 variant: Some(Variant {
1003 discriminant: Ident::new("D"),
1004 arms: vec![
1005 Arm {
1006 choices: vec![Choice::Value(Expr::name("IDLE"))],
1007 components: vec![component("Idle", Primitive::Integer32)],
1008 },
1009 Arm {
1010 choices: vec![Choice::Others],
1011 components: Vec::new(),
1012 },
1013 ],
1014 }),
1015 }),
1016 });
1017 assert_eq!(
1018 decl(&node),
1019 "type Payload (D : Mode := IDLE) is record\n \
1020 case D is\n \
1021 when IDLE =>\n \
1022 Idle : Interfaces.Integer_32;\n \
1023 when others =>\n \
1024 null;\n \
1025 end case;\n\
1026 end record;\n"
1027 );
1028 }
1029
1030 #[test]
1031 fn an_enumeration_and_its_representation_clause_are_two_declarations() {
1032 let type_ = Decl::Type(TypeDecl {
1036 doc: Doc::none(),
1037 name: Ident::new("Mode"),
1038 def: TypeDef::Enumeration(vec![Ident::new("IDLE"), Ident::new("ACTIVE")]),
1039 });
1040 let rep = Decl::EnumRep(EnumRep {
1041 doc: Doc::none(),
1042 type_: Ident::new("Mode"),
1043 values: vec![
1044 (Ident::new("IDLE"), Expr::int(0)),
1045 (Ident::new("ACTIVE"), Expr::int(4)),
1046 ],
1047 });
1048 assert_eq!(decl(&type_), "type Mode is (IDLE, ACTIVE);\n");
1049 assert_eq!(decl(&rep), "for Mode use (IDLE => 0, ACTIVE => 4);\n");
1050 }
1051
1052 #[test]
1053 fn an_unconstrained_array_and_the_access_type_that_reaches_it() {
1054 let array = Decl::Type(TypeDecl {
1055 doc: Doc::none(),
1056 name: Ident::new("Zd_Seq_Long_Array"),
1057 def: TypeDef::Array {
1058 index: vec![Index::Unconstrained(Path::single("Natural"))],
1059 element: Primitive::Integer32.path(),
1060 },
1061 });
1062 let access = Decl::Type(TypeDecl {
1063 doc: Doc::none(),
1064 name: Ident::new("Zd_Seq_Long"),
1065 def: TypeDef::Access(Path::single("Zd_Seq_Long_Array")),
1066 });
1067 assert_eq!(
1068 decl(&array),
1069 "type Zd_Seq_Long_Array is array (Natural range <>) of Interfaces.Integer_32;\n"
1070 );
1071 assert_eq!(
1072 decl(&access),
1073 "type Zd_Seq_Long is access Zd_Seq_Long_Array;\n"
1074 );
1075 }
1076
1077 #[test]
1078 fn a_constrained_array_writes_its_bounds() {
1079 let node = Decl::Type(TypeDecl {
1080 doc: Doc::none(),
1081 name: Ident::new("Zd_Arr_Long_2"),
1082 def: TypeDef::Array {
1083 index: vec![Index::Range {
1084 low: Expr::int(0),
1085 high: Expr::int(1),
1086 }],
1087 element: Primitive::Integer32.path(),
1088 },
1089 });
1090 assert_eq!(
1091 decl(&node),
1092 "type Zd_Arr_Long_2 is array (0 .. 1) of Interfaces.Integer_32;\n"
1093 );
1094 }
1095
1096 #[test]
1097 fn an_incomplete_type_declares_a_name_and_nothing_else() {
1098 let incomplete = Decl::Type(TypeDecl {
1102 doc: Doc::none(),
1103 name: Ident::new("Node"),
1104 def: TypeDef::Incomplete,
1105 });
1106 let access = Decl::Type(TypeDecl {
1107 doc: Doc::none(),
1108 name: Ident::new("Node_Array"),
1109 def: TypeDef::Access(Path::single("Node")),
1110 });
1111 assert_eq!(decl(&incomplete), "type Node;\n");
1112 assert_eq!(decl(&access), "type Node_Array is access Node;\n");
1113 }
1114
1115 #[test]
1116 fn a_subtype_is_not_a_derived_type() {
1117 let alias = Decl::Subtype(SubtypeDecl {
1120 doc: Doc::none(),
1121 name: Ident::new("Distance"),
1122 mark: Primitive::Integer32.path(),
1123 });
1124 let derived = Decl::Type(TypeDecl {
1125 doc: Doc::none(),
1126 name: Ident::new("Flags"),
1127 def: TypeDef::Derived(Primitive::Unsigned16.path()),
1128 });
1129 assert_eq!(decl(&alias), "subtype Distance is Interfaces.Integer_32;\n");
1130 assert_eq!(
1131 decl(&derived),
1132 "type Flags is new Interfaces.Unsigned_16;\n"
1133 );
1134 }
1135
1136 #[test]
1137 fn a_constant_names_its_type_because_ada_has_no_untyped_one() {
1138 let node = Decl::Object(ObjectDecl {
1139 doc: Doc::none(),
1140 name: Ident::new("LIMIT"),
1141 type_: Primitive::Unsigned8.path(),
1142 value: Expr::int(8),
1143 });
1144 assert_eq!(
1145 decl(&node),
1146 "LIMIT : constant Interfaces.Unsigned_8 := 8;\n"
1147 );
1148 }
1149
1150 #[test]
1151 fn an_abstract_subprogram_writes_its_modes() {
1152 let procedure = Decl::Subprogram(Subprogram {
1153 doc: Doc::none(),
1154 kind: SubprogramKind::Procedure,
1155 name: Ident::new("Go"),
1156 params: vec![
1157 Param {
1158 name: Ident::new("Self"),
1159 mode: Mode::In,
1160 type_: Path::single("Control"),
1161 },
1162 Param {
1163 name: Ident::new("Target"),
1164 mode: Mode::Out,
1165 type_: Path::single("Pose"),
1166 },
1167 ],
1168 result: None,
1169 is_abstract: true,
1170 });
1171 let function = Decl::Subprogram(Subprogram {
1172 doc: Doc::none(),
1173 kind: SubprogramKind::Function,
1174 name: Ident::new("Status"),
1175 params: vec![Param {
1176 name: Ident::new("Self"),
1177 mode: Mode::In,
1178 type_: Path::single("Control"),
1179 }],
1180 result: Some(Primitive::Integer32.path()),
1181 is_abstract: true,
1182 });
1183 assert_eq!(
1184 decl(&procedure),
1185 "procedure Go (Self : in Control; Target : out Pose) is abstract;\n"
1186 );
1187 assert_eq!(
1188 decl(&function),
1189 "function Status (Self : in Control) return Interfaces.Integer_32 is abstract;\n"
1190 );
1191 }
1192
1193 #[test]
1194 fn a_string_literal_doubles_its_quote_and_never_escapes_it() {
1195 assert_eq!(
1198 expr(&Expr::Literal(Literal::Str("a\"b".to_string()))),
1199 "\"a\"\"b\""
1200 );
1201 assert_eq!(
1202 expr(&Expr::Literal(Literal::Str("a\\b".to_string()))),
1203 "\"a\\b\""
1204 );
1205 }
1206
1207 #[test]
1208 fn a_string_constant_is_a_call_because_a_private_type_has_no_literal() {
1209 let node = Decl::Object(ObjectDecl {
1210 doc: Doc::none(),
1211 name: Ident::new("TAG"),
1212 type_: Primitive::UnboundedString.path(),
1213 value: Expr::Call {
1214 name: Path::new(&["Ada", "Strings", "Unbounded", "To_Unbounded_String"]),
1215 args: vec![Expr::Literal(Literal::Str("abc".to_string()))],
1216 },
1217 });
1218 assert_eq!(
1219 decl(&node),
1220 "TAG : constant Ada.Strings.Unbounded.Unbounded_String := \
1221 Ada.Strings.Unbounded.To_Unbounded_String (\"abc\");\n"
1222 );
1223 }
1224
1225 #[test]
1226 fn a_file_writes_its_context_clause_before_the_package() {
1227 let node = File {
1230 doc: Doc::line("Generated. Do not edit."),
1231 withs: vec![Path::single("Interfaces")],
1232 use_types: Vec::new(),
1233 package: Ident::new("Zd_Types"),
1234 decls: vec![Decl::Type(TypeDecl {
1235 doc: Doc::none(),
1236 name: Ident::new("Pose"),
1237 def: TypeDef::Record(Record::plain(vec![component("X", Primitive::Integer32)])),
1238 })],
1239 };
1240 assert_eq!(
1241 file(&node),
1242 "-- Generated. Do not edit.\n\
1243 \n\
1244 with Interfaces;\n\
1245 \n\
1246 package Zd_Types is\n\
1247 \n \
1248 type Pose is record\n \
1249 X : Interfaces.Integer_32;\n \
1250 end record;\n\
1251 \n\
1252 end Zd_Types;\n"
1253 );
1254 }
1255
1256 #[test]
1257 fn a_use_type_clause_stands_after_the_with_it_depends_on() {
1258 let node = File {
1261 doc: Doc::none(),
1262 withs: vec![Path::single("Interfaces")],
1263 use_types: vec![Primitive::Integer16.path()],
1264 package: Ident::new("Zd_Types"),
1265 decls: Vec::new(),
1266 };
1267 assert_eq!(
1268 file(&node),
1269 "with Interfaces;\n\
1270 use type Interfaces.Integer_16;\n\
1271 \n\
1272 package Zd_Types is\n\
1273 \n\
1274 end Zd_Types;\n"
1275 );
1276 }
1277
1278 #[test]
1279 fn a_boolean_is_written_with_the_case_ada_declares_it_in() {
1280 assert_eq!(expr(&Expr::Literal(Literal::Bool(true))), "True");
1284 assert_eq!(expr(&Expr::Literal(Literal::Bool(false))), "False");
1285 }
1286
1287 #[test]
1288 fn a_hexadecimal_literal_uses_the_ada_base_form() {
1289 assert_eq!(
1291 expr(&Expr::Literal(Literal::Bytes(alloc::vec![1, 255]))),
1292 "(16#01#, 16#FF#)"
1293 );
1294 }
1295
1296 #[test]
1297 fn a_comment_uses_the_ada_marker_and_has_no_closing_delimiter() {
1298 let node = Decl::Comment(Doc::lines(&["first", "second"]));
1300 assert_eq!(decl(&node), "-- first\n-- second\n");
1301 }
1302
1303 use zerodds_lang_cst::ada::{AllocInit, Association, Stmt, SubprogramBody, VarDecl};
1304
1305 fn name(text: &str) -> Expr {
1306 Expr::name(text)
1307 }
1308
1309 #[test]
1310 fn the_walks_operators_take_their_ada_spellings() {
1311 let node = Expr::Binary {
1312 op: "&&",
1313 lhs: alloc::boxed::Box::new(Expr::Binary {
1314 op: "==",
1315 lhs: alloc::boxed::Box::new(name("A")),
1316 rhs: alloc::boxed::Box::new(name("B")),
1317 }),
1318 rhs: alloc::boxed::Box::new(Expr::Unary {
1319 op: "!",
1320 value: alloc::boxed::Box::new(name("C")),
1321 }),
1322 };
1323 assert_eq!(expr(&node), "(A = B) and then (not C)");
1327 }
1328
1329 #[test]
1330 fn selection_indexing_dereference_and_slice_render_as_ada_writes_them() {
1331 let selected = Expr::Selected {
1332 value: alloc::boxed::Box::new(name("v")),
1333 name: Ident::new("Key"),
1334 };
1335 assert_eq!(expr(&selected), "v.Key");
1336 let indexed = Expr::Indexed {
1337 value: alloc::boxed::Box::new(name("v")),
1338 index: alloc::boxed::Box::new(name("zd_i")),
1339 };
1340 assert_eq!(expr(&indexed), "v (zd_i)");
1341 let deref = Expr::Deref(alloc::boxed::Box::new(name("v")));
1342 assert_eq!(expr(&deref), "v.all");
1343 let slice = Expr::Slice {
1344 value: alloc::boxed::Box::new(deref),
1345 low: alloc::boxed::Box::new(Expr::int(0)),
1346 high: alloc::boxed::Box::new(name("zd_n")),
1347 };
1348 assert_eq!(expr(&slice), "v.all (0 .. zd_n)");
1349 }
1350
1351 #[test]
1352 fn an_attribute_a_conversion_and_an_aggregate_render() {
1353 let length = Expr::Attribute {
1354 value: alloc::boxed::Box::new(Expr::Deref(alloc::boxed::Box::new(name("v")))),
1355 name: Ident::new("Length"),
1356 args: Vec::new(),
1357 };
1358 assert_eq!(expr(&length), "v.all'Length");
1359 let enum_val = Expr::Attribute {
1360 value: alloc::boxed::Box::new(name("Colour")),
1361 name: Ident::new("Enum_Val"),
1362 args: vec![Expr::int(4)],
1363 };
1364 assert_eq!(expr(&enum_val), "Colour'Enum_Val (4)");
1365 let convert = Expr::Convert {
1366 mark: Primitive::Unsigned32.path(),
1367 value: alloc::boxed::Box::new(length),
1368 };
1369 assert_eq!(expr(&convert), "Interfaces.Unsigned_32 (v.all'Length)");
1370 let aggregate = Expr::Aggregate(vec![
1371 Association {
1372 choice: Ident::new("Present"),
1373 value: Expr::Literal(Literal::Bool(true)),
1374 },
1375 Association {
1376 choice: Ident::new("Value"),
1377 value: Expr::int(7),
1378 },
1379 ]);
1380 assert_eq!(expr(&aggregate), "(Present => True, Value => 7)");
1381 let boxed = Expr::Aggregate(vec![Association {
1382 choice: Ident::new("others"),
1383 value: Expr::BoxDefault,
1384 }]);
1385 assert_eq!(expr(&boxed), "(others => <>)");
1386 }
1387
1388 #[test]
1389 fn an_allocator_takes_a_range_or_a_qualified_value() {
1390 let empty = Expr::New {
1391 mark: Path::single("Zd_Seq_Long_Array"),
1392 init: AllocInit::Range {
1393 low: alloc::boxed::Box::new(Expr::int(0)),
1394 high: alloc::boxed::Box::new(Expr::int(-1)),
1395 },
1396 };
1397 assert_eq!(expr(&empty), "new Zd_Seq_Long_Array (0 .. -1)");
1398 let kept = Expr::New {
1399 mark: Path::single("Zd_Seq_Long_Array"),
1400 init: AllocInit::Qualified(alloc::boxed::Box::new(name("v"))),
1401 };
1402 assert_eq!(expr(&kept), "new Zd_Seq_Long_Array'(v)");
1403 }
1404
1405 #[test]
1406 fn a_declaration_mid_list_opens_a_declare_block_over_the_rest() {
1407 let nodes = vec![
1411 Stmt::Assign {
1412 place: name("a"),
1413 value: Expr::int(1),
1414 },
1415 Stmt::Var(VarDecl {
1416 name: Ident::new("zd_n"),
1417 mark: Primitive::Unsigned32.path(),
1418 constraint: None,
1419 value: Some(Expr::int(0)),
1420 }),
1421 Stmt::Assign {
1422 place: name("a"),
1423 value: name("zd_n"),
1424 },
1425 ];
1426 assert_eq!(
1427 stmts(&nodes),
1428 "a := 1;\n\
1429 declare\n \
1430 zd_n : Interfaces.Unsigned_32 := 0;\n\
1431 begin\n \
1432 a := zd_n;\n\
1433 end;\n"
1434 );
1435 }
1436
1437 #[test]
1438 fn a_subprogram_body_hoists_its_leading_declarations() {
1439 let node = Decl::SubprogramBody(SubprogramBody {
1440 spec: Subprogram {
1441 doc: Doc::none(),
1442 kind: SubprogramKind::Function,
1443 name: Ident::new("zd_dec_X"),
1444 params: vec![Param {
1445 name: Ident::new("r"),
1446 mode: Mode::InOut,
1447 type_: Path::new(&["Zd_Wire", "Reader"]),
1448 }],
1449 result: Some(Path::single("X")),
1450 is_abstract: false,
1451 },
1452 body: vec![
1453 Stmt::Var(VarDecl {
1454 name: Ident::new("v"),
1455 mark: Path::single("X"),
1456 constraint: None,
1457 value: None,
1458 }),
1459 Stmt::Return(Some(name("v"))),
1460 ],
1461 });
1462 assert_eq!(
1463 decl(&node),
1464 "function zd_dec_X (r : in out Zd_Wire.Reader) return X is\n \
1465 v : X;\n\
1466 begin\n \
1467 return v;\n\
1468 end zd_dec_X;\n"
1469 );
1470 }
1471
1472 #[test]
1473 fn a_constrained_object_declaration_carries_its_discriminant() {
1474 let node = Stmt::Declare {
1477 vars: vec![VarDecl {
1478 name: Ident::new("zd_nd"),
1479 mark: Path::single("Zd_Sample"),
1480 constraint: Some(name("zd_d")),
1481 value: None,
1482 }],
1483 body: vec![Stmt::Assign {
1484 place: name("v"),
1485 value: name("zd_nd"),
1486 }],
1487 };
1488 assert_eq!(
1489 stmt(&node),
1490 "declare\n \
1491 zd_nd : Zd_Sample (zd_d);\n\
1492 begin\n \
1493 v := zd_nd;\n\
1494 end;\n"
1495 );
1496 }
1497
1498 #[test]
1499 fn the_loop_forms_and_exit_render() {
1500 let counted = Stmt::For {
1501 counter: Ident::new("zd_i"),
1502 low: Expr::int(0),
1503 high: Expr::int(2),
1504 body: vec![Stmt::Null],
1505 };
1506 assert_eq!(
1507 stmt(&counted),
1508 "for zd_i in 0 .. 2 loop\n null;\nend loop;\n"
1509 );
1510 let headed = Stmt::While {
1511 condition: Expr::Literal(Literal::Bool(true)),
1512 body: vec![Stmt::Exit],
1513 };
1514 assert_eq!(stmt(&headed), "while True loop\n exit;\nend loop;\n");
1515 }
1516
1517 #[test]
1518 fn an_if_writes_its_else_and_an_empty_arm_is_null() {
1519 let node = Stmt::If {
1520 condition: name("ok"),
1521 then: Vec::new(),
1522 otherwise: Some(vec![Stmt::Return(None)]),
1523 };
1524 assert_eq!(
1525 stmt(&node),
1526 "if ok then\n null;\nelse\n return;\nend if;\n"
1527 );
1528 }
1529}