1use crate::writer::{Writer, comment_lines, verbatim_lines};
18use alloc::string::{String, ToString};
19use alloc::vec::Vec;
20use zerodds_lang_cst::ada83::{
21 AllocInit, Arm, Choice, Component, Decl, Discriminant, Expr, File, Index, Mode, ObjectDecl,
22 Param, Stmt, Subprogram, SubprogramBody, SubprogramKind, SubtypeDecl, TypeDecl, TypeDef,
23 VarDecl, Variant,
24};
25use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
26
27const UNIT: &str = " ";
29
30#[must_use]
32pub fn file(file: &File) -> String {
33 let mut w = Writer::new(UNIT);
34 doc(&mut w, &file.doc);
35 if !file.withs.is_empty() {
36 if !file.doc.is_empty() {
37 w.blank_line();
38 }
39 for unit in &file.withs {
40 let mut line = String::from("with ");
41 line.push_str(&path_text(unit));
42 line.push(';');
43 w.line(&line);
44 }
45 }
46 w.blank_line();
47 let mut head = String::from("package ");
48 head.push_str(file.package.as_str());
49 head.push_str(" is");
50 w.line(&head);
51 w.indent();
52 for decl in &file.decls {
53 w.blank_line();
54 render_decl(&mut w, decl);
55 }
56 w.dedent();
57 w.blank_line();
58 let mut tail = String::from("end ");
59 tail.push_str(file.package.as_str());
60 tail.push(';');
61 w.line(&tail);
62 w.finish()
63}
64
65#[must_use]
75pub fn package_body(package: &Ident, decls: &[Decl]) -> String {
76 let mut w = Writer::new(UNIT);
77 let mut head = String::from("package body ");
78 head.push_str(package.as_str());
79 head.push_str(" is");
80 w.line(&head);
81 w.indent();
82 for decl in decls {
83 w.blank_line();
84 render_decl(&mut w, decl);
85 }
86 w.dedent();
87 w.blank_line();
88 let mut tail = String::from("end ");
89 tail.push_str(package.as_str());
90 tail.push(';');
91 w.line(&tail);
92 w.finish()
93}
94
95#[must_use]
97pub fn decl(node: &Decl) -> String {
98 let mut w = Writer::new(UNIT);
99 render_decl(&mut w, node);
100 w.finish()
101}
102
103#[must_use]
105pub fn expr(node: &Expr) -> String {
106 expr_text(node)
107}
108
109#[must_use]
111pub fn ident(name: &Ident) -> String {
112 name.as_str().to_string()
113}
114
115#[must_use]
117pub fn path(name: &Path) -> String {
118 path_text(name)
119}
120
121fn path_text(name: &Path) -> String {
122 name.segments()
123 .iter()
124 .map(|segment| segment.as_str())
125 .collect::<Vec<&str>>()
126 .join(".")
127}
128
129#[must_use]
131pub fn stmt(node: &Stmt) -> String {
132 let mut w = Writer::new(UNIT);
133 render_stmts(&mut w, core::slice::from_ref(node));
134 w.finish()
135}
136
137#[must_use]
139pub fn stmts(nodes: &[Stmt]) -> String {
140 let mut w = Writer::new(UNIT);
141 render_stmts(&mut w, nodes);
142 w.finish()
143}
144
145fn render_decl(w: &mut Writer, node: &Decl) {
146 match node {
147 Decl::Type(node) => render_type_decl(w, node),
148 Decl::Subtype(node) => render_subtype(w, node),
149 Decl::Object(node) => render_object(w, node),
150 Decl::Subprogram(node) => render_subprogram(w, node),
151 Decl::SubprogramBody(node) => render_subprogram_body(w, node),
152 Decl::Comment(lines) => doc(w, lines),
153 Decl::Verbatim(text) => verbatim_lines(w, text),
154 }
155}
156
157fn render_type_decl(w: &mut Writer, node: &TypeDecl) {
158 doc(w, &node.doc);
159 let mut head = String::from("type ");
160 head.push_str(node.name.as_str());
161 match &node.def {
162 TypeDef::Record(record) => {
163 head.push_str(&discriminant_text(&record.discriminants));
164 if record.is_null() {
165 head.push_str(" is null record;");
166 w.line(&head);
167 return;
168 }
169 head.push_str(" is record");
170 w.line(&head);
171 w.indent();
172 for text in &record.body_text.begin {
173 verbatim_lines(w, text);
174 }
175 for component in &record.components {
176 render_component(w, component);
177 }
178 if let Some(variant) = &record.variant {
179 render_variant(w, variant);
180 }
181 for text in &record.body_text.end {
182 verbatim_lines(w, text);
183 }
184 w.dedent();
185 w.line("end record;");
186 }
187 TypeDef::Derived(mark) => {
188 head.push_str(" is new ");
189 head.push_str(&path_text(mark));
190 head.push(';');
191 w.line(&head);
192 }
193 TypeDef::Array { index, element } => {
194 head.push_str(" is array (");
195 head.push_str(
196 &index
197 .iter()
198 .map(index_text)
199 .collect::<Vec<String>>()
200 .join(", "),
201 );
202 head.push_str(") of ");
203 head.push_str(&path_text(element));
204 head.push(';');
205 w.line(&head);
206 }
207 TypeDef::Access(designated) => {
208 head.push_str(" is access ");
209 head.push_str(&path_text(designated));
210 head.push(';');
211 w.line(&head);
212 }
213 TypeDef::Incomplete => {
215 head.push(';');
216 w.line(&head);
217 }
218 }
219}
220
221fn discriminant_text(discriminants: &[Discriminant]) -> String {
223 if discriminants.is_empty() {
224 return String::new();
225 }
226 let parts: Vec<String> = discriminants
227 .iter()
228 .map(|discriminant| {
229 let mut out = String::from(discriminant.name.as_str());
230 out.push_str(" : ");
231 out.push_str(&path_text(&discriminant.type_));
232 if let Some(default) = &discriminant.default {
233 out.push_str(" := ");
234 out.push_str(&expr_text(default));
235 }
236 out
237 })
238 .collect();
239 let mut out = String::from(" (");
240 out.push_str(&parts.join("; "));
241 out.push(')');
242 out
243}
244
245fn render_component(w: &mut Writer, node: &Component) {
246 doc(w, &node.doc);
247 let mut line = String::from(node.name.as_str());
248 line.push_str(" : ");
249 line.push_str(&path_text(&node.type_));
250 line.push(';');
251 w.line(&line);
252}
253
254fn render_variant(w: &mut Writer, node: &Variant) {
256 let mut head = String::from("case ");
257 head.push_str(node.discriminant.as_str());
258 head.push_str(" is");
259 w.line(&head);
260 w.indent();
261 for arm in &node.arms {
262 render_arm(w, arm);
263 }
264 w.dedent();
265 w.line("end case;");
266}
267
268fn render_arm(w: &mut Writer, node: &Arm) {
269 let mut head = String::from("when ");
270 head.push_str(
271 &node
272 .choices
273 .iter()
274 .map(choice_text)
275 .collect::<Vec<String>>()
276 .join(" | "),
277 );
278 head.push_str(" =>");
279 w.line(&head);
280 w.indent();
281 if node.components.is_empty() {
282 w.line("null;");
283 } else {
284 for component in &node.components {
285 render_component(w, component);
286 }
287 }
288 w.dedent();
289}
290
291fn choice_text(node: &Choice) -> String {
292 match node {
293 Choice::Value(value) => expr_text(value),
294 Choice::Others => "others".to_string(),
295 }
296}
297
298fn index_text(node: &Index) -> String {
299 match node {
300 Index::Range { low, high } => {
301 let mut out = expr_text(low);
302 out.push_str(" .. ");
303 out.push_str(&expr_text(high));
304 out
305 }
306 Index::Unconstrained(mark) => {
307 let mut out = path_text(mark);
308 out.push_str(" range <>");
309 out
310 }
311 }
312}
313
314fn render_subtype(w: &mut Writer, node: &SubtypeDecl) {
315 doc(w, &node.doc);
316 let mut line = String::from("subtype ");
317 line.push_str(node.name.as_str());
318 line.push_str(" is ");
319 line.push_str(&path_text(&node.mark));
320 line.push(';');
321 w.line(&line);
322}
323
324fn render_object(w: &mut Writer, node: &ObjectDecl) {
325 doc(w, &node.doc);
326 let mut line = String::from(node.name.as_str());
327 line.push_str(" : constant ");
328 line.push_str(&path_text(&node.type_));
329 line.push_str(" := ");
330 line.push_str(&expr_text(&node.value));
331 line.push(';');
332 w.line(&line);
333}
334
335fn subprogram_head(
336 kind: SubprogramKind,
337 name: &Ident,
338 params: &[Param],
339 result: &Option<Path>,
340) -> String {
341 let mut line = String::from(match kind {
342 SubprogramKind::Procedure => "procedure ",
343 SubprogramKind::Function => "function ",
344 });
345 line.push_str(name.as_str());
346 if !params.is_empty() {
347 line.push_str(" (");
348 line.push_str(
349 ¶ms
350 .iter()
351 .map(param_text)
352 .collect::<Vec<String>>()
353 .join("; "),
354 );
355 line.push(')');
356 }
357 if let Some(result) = result {
358 line.push_str(" return ");
359 line.push_str(&path_text(result));
360 }
361 line
362}
363
364fn render_subprogram(w: &mut Writer, node: &Subprogram) {
365 doc(w, &node.doc);
366 let mut line = subprogram_head(node.kind, &node.name, &node.params, &node.result);
367 line.push(';');
368 w.line(&line);
369}
370
371fn render_subprogram_body(w: &mut Writer, node: &SubprogramBody) {
373 doc(w, &node.spec.doc);
374 let mut head = subprogram_head(
375 node.spec.kind,
376 &node.spec.name,
377 &node.spec.params,
378 &node.spec.result,
379 );
380 head.push_str(" is");
381 w.line(&head);
382 let split = node
383 .body
384 .iter()
385 .position(|statement| {
386 !matches!(
387 statement,
388 Stmt::Var(_) | Stmt::Subtype { .. } | Stmt::Comment(_)
389 )
390 })
391 .unwrap_or(node.body.len());
392 w.indent();
393 for statement in &node.body[..split] {
394 match statement {
395 Stmt::Var(var) => w.line(&var_text(var)),
396 Stmt::Subtype { name, mark } => w.line(&subtype_text(name, mark)),
397 Stmt::Comment(lines) => doc(w, lines),
398 _ => {}
399 }
400 }
401 w.dedent();
402 w.line("begin");
403 w.indent();
404 if node.body[split..].is_empty() {
405 w.line("null;");
406 } else {
407 render_stmts(w, &node.body[split..]);
408 }
409 w.dedent();
410 let mut tail = String::from("end ");
411 tail.push_str(node.spec.name.as_str());
412 tail.push(';');
413 w.line(&tail);
414}
415
416fn var_text(node: &VarDecl) -> String {
418 let mut out = String::from(node.name.as_str());
419 out.push_str(" : ");
420 out.push_str(&path_text(&node.mark));
421 if let Some(constraint) = &node.constraint {
422 out.push_str(" (");
423 out.push_str(&expr_text(constraint));
424 out.push(')');
425 }
426 if let Some(value) = &node.value {
427 out.push_str(" := ");
428 out.push_str(&expr_text(value));
429 }
430 out.push(';');
431 out
432}
433
434fn subtype_text(name: &Ident, mark: &Path) -> String {
435 let mut out = String::from("subtype ");
436 out.push_str(name.as_str());
437 out.push_str(" is ");
438 out.push_str(&path_text(mark));
439 out.push(';');
440 out
441}
442
443fn render_stmts(w: &mut Writer, nodes: &[Stmt]) {
457 let mut at = 0;
458 while at < nodes.len() {
459 let statement = &nodes[at];
460 if matches!(statement, Stmt::Var(_) | Stmt::Subtype { .. }) {
461 w.line("declare");
462 w.indent();
463 while at < nodes.len() {
464 match &nodes[at] {
465 Stmt::Var(var) => w.line(&var_text(var)),
466 Stmt::Subtype { name, mark } => w.line(&subtype_text(name, mark)),
467 Stmt::Comment(lines) => doc(w, lines),
468 _ => break,
469 }
470 at += 1;
471 }
472 w.dedent();
473 w.line("begin");
474 w.indent();
475 if nodes[at..].is_empty() {
476 w.line("null;");
477 } else {
478 render_stmts(w, &nodes[at..]);
479 }
480 w.dedent();
481 w.line("end;");
482 return;
483 }
484 render_stmt(w, statement);
485 at += 1;
486 }
487}
488
489fn render_stmt(w: &mut Writer, node: &Stmt) {
500 match node {
501 Stmt::Null => w.line("null;"),
502 Stmt::Var(var) => w.line(&var_text(var)),
503 Stmt::Subtype { name, mark } => w.line(&subtype_text(name, mark)),
504 Stmt::Assign { place, value } => {
505 let mut line = expr_text(place);
506 line.push_str(" := ");
507 line.push_str(&expr_text(value));
508 line.push(';');
509 w.line(&line);
510 }
511 Stmt::Call(value) => {
512 let mut line = expr_text(value);
513 line.push(';');
514 w.line(&line);
515 }
516 Stmt::Return(value) => match value {
517 Some(value) => {
518 let mut line = String::from("return ");
519 line.push_str(&expr_text(value));
520 line.push(';');
521 w.line(&line);
522 }
523 None => w.line("return;"),
524 },
525 Stmt::If {
526 condition,
527 then,
528 otherwise,
529 } => {
530 let mut head = String::from("if ");
531 head.push_str(&expr_text(condition));
532 head.push_str(" then");
533 w.line(&head);
534 w.indent();
535 if then.is_empty() {
536 w.line("null;");
537 } else {
538 render_stmts(w, then);
539 }
540 w.dedent();
541 if let Some(otherwise) = otherwise {
542 w.line("else");
543 w.indent();
544 if otherwise.is_empty() {
545 w.line("null;");
546 } else {
547 render_stmts(w, otherwise);
548 }
549 w.dedent();
550 }
551 w.line("end if;");
552 }
553 Stmt::For {
554 counter,
555 low,
556 high,
557 body,
558 } => {
559 let mut head = String::from("for ");
560 head.push_str(counter.as_str());
561 head.push_str(" in ");
562 head.push_str(&expr_text(low));
563 head.push_str(" .. ");
564 head.push_str(&expr_text(high));
565 head.push_str(" loop");
566 w.line(&head);
567 w.indent();
568 if body.is_empty() {
569 w.line("null;");
570 } else {
571 render_stmts(w, body);
572 }
573 w.dedent();
574 w.line("end loop;");
575 }
576 Stmt::While { condition, body } => {
577 let mut head = String::from("while ");
578 head.push_str(&expr_text(condition));
579 head.push_str(" loop");
580 w.line(&head);
581 w.indent();
582 if body.is_empty() {
583 w.line("null;");
584 } else {
585 render_stmts(w, body);
586 }
587 w.dedent();
588 w.line("end loop;");
589 }
590 Stmt::Exit => w.line("exit;"),
591 Stmt::Declare { vars, body } => {
592 w.line("declare");
593 w.indent();
594 for var in vars {
595 w.line(&var_text(var));
596 }
597 w.dedent();
598 w.line("begin");
599 w.indent();
600 if body.is_empty() {
601 w.line("null;");
602 } else {
603 render_stmts(w, body);
604 }
605 w.dedent();
606 w.line("end;");
607 }
608 Stmt::Comment(lines) => doc(w, lines),
609 }
610}
611
612fn param_text(node: &Param) -> String {
613 let mut out = String::from(node.name.as_str());
614 out.push_str(" : ");
615 out.push_str(mode_text(node.mode));
616 out.push(' ');
617 out.push_str(&path_text(&node.type_));
618 out
619}
620
621const fn mode_text(mode: Mode) -> &'static str {
622 mode.as_str()
623}
624
625fn expr_text(node: &Expr) -> String {
632 match node {
633 Expr::Literal(literal) => literal_text(literal),
634 Expr::Name(name) => path_text(name),
635 Expr::Call { name, args } => {
636 let mut out = path_text(name);
637 if !args.is_empty() {
639 out.push_str(" (");
640 out.push_str(
641 &args
642 .iter()
643 .map(expr_text)
644 .collect::<Vec<String>>()
645 .join(", "),
646 );
647 out.push(')');
648 }
649 out
650 }
651 Expr::Negate(inner) => {
652 let mut out = String::from("-");
653 out.push_str(&expr_text(inner));
654 out
655 }
656 Expr::Binary { op, lhs, rhs } => {
657 let mut out = operand_text(lhs);
658 out.push(' ');
659 out.push_str(ada_op(op));
660 out.push(' ');
661 out.push_str(&operand_text(rhs));
662 out
663 }
664 Expr::Unary { op, value } => {
665 let mut out = String::from(ada_op(op));
666 if *op != "-" {
667 out.push(' ');
668 }
669 out.push_str(&operand_text(value));
670 out
671 }
672 Expr::Selected { value, name } => {
673 let mut out = operand_text(value);
674 out.push('.');
675 out.push_str(name.as_str());
676 out
677 }
678 Expr::Indexed { value, index } => {
679 let mut out = operand_text(value);
680 out.push_str(" (");
681 out.push_str(&expr_text(index));
682 out.push(')');
683 out
684 }
685 Expr::Deref(value) => {
686 let mut out = operand_text(value);
687 out.push_str(".all");
688 out
689 }
690 Expr::Slice { value, low, high } => {
691 let mut out = operand_text(value);
692 out.push_str(" (");
693 out.push_str(&expr_text(low));
694 out.push_str(" .. ");
695 out.push_str(&expr_text(high));
696 out.push(')');
697 out
698 }
699 Expr::Attribute { value, name, args } => {
700 let mut out = operand_text(value);
701 out.push('\'');
702 out.push_str(name.as_str());
703 if !args.is_empty() {
704 out.push_str(" (");
705 out.push_str(
706 &args
707 .iter()
708 .map(expr_text)
709 .collect::<Vec<String>>()
710 .join(", "),
711 );
712 out.push(')');
713 }
714 out
715 }
716 Expr::Convert { mark, value } => {
717 let mut out = path_text(mark);
718 out.push_str(" (");
719 out.push_str(&expr_text(value));
720 out.push(')');
721 out
722 }
723 Expr::Aggregate(parts) => {
724 let mut out = String::from("(");
725 out.push_str(
726 &parts
727 .iter()
728 .map(|part| {
729 let mut one = String::from(part.choice.as_str());
730 one.push_str(" => ");
731 one.push_str(&expr_text(&part.value));
732 one
733 })
734 .collect::<Vec<String>>()
735 .join(", "),
736 );
737 out.push(')');
738 out
739 }
740 Expr::New { mark, init } => {
741 let mut out = String::from("new ");
742 out.push_str(&path_text(mark));
743 match init {
744 AllocInit::Range { low, high } => {
745 out.push_str(" (");
746 out.push_str(&expr_text(low));
747 out.push_str(" .. ");
748 out.push_str(&expr_text(high));
749 out.push(')');
750 }
751 AllocInit::Qualified(value) => {
752 out.push_str("'(");
753 out.push_str(&expr_text(value));
754 out.push(')');
755 }
756 }
757 out
758 }
759 }
760}
761
762fn operand_text(node: &Expr) -> String {
771 match node {
772 Expr::Binary { .. } | Expr::Unary { .. } | Expr::Negate(_) => {
773 let mut out = String::from("(");
774 out.push_str(&expr_text(node));
775 out.push(')');
776 out
777 }
778 _ => expr_text(node),
779 }
780}
781
782fn ada_op(op: &str) -> &str {
784 match op {
785 "==" => "=",
786 "!=" => "/=",
787 "&&" => "and then",
788 "||" => "or else",
789 "&" => "and",
790 "|" => "or",
791 "^" => "xor",
792 "%" => "mod",
793 "!" | "~" => "not",
794 other => other,
795 }
796}
797
798fn literal_text(literal: &Literal) -> String {
799 match literal {
800 Literal::Int(text) | Literal::Float(text) => text.clone(),
801 Literal::Bool(value) => {
802 if *value {
803 "True".to_string()
804 } else {
805 "False".to_string()
806 }
807 }
808 Literal::Char(ch) => alloc::format!("{}", u32::from(*ch)),
811 Literal::Str(text) => ada_string(text),
812 Literal::Bytes(bytes) => {
813 let mut out = String::from("(");
814 let parts: Vec<String> = bytes
815 .iter()
816 .map(|b| alloc::format!("16#{b:02X}#"))
817 .collect();
818 out.push_str(&parts.join(", "));
819 out.push(')');
820 out
821 }
822 Literal::Null => "null".to_string(),
823 }
824}
825
826fn ada_string(text: &str) -> String {
829 let mut out = String::from("\"");
830 for ch in text.chars() {
831 if ch == '"' {
832 out.push('"');
833 }
834 out.push(ch);
835 }
836 out.push('"');
837 out
838}
839
840fn doc(w: &mut Writer, doc: &Doc) {
841 if doc.is_empty() {
842 return;
843 }
844 comment_lines(w, "-- ", doc.as_lines());
845}
846
847#[cfg(test)]
848mod tests {
849 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
850 use super::*;
851 use alloc::vec;
852 use zerodds_lang_cst::ada83::{Primitive, Record};
853
854 fn component(name: &str, type_: Primitive) -> Component {
855 Component {
856 doc: Doc::none(),
857 name: Ident::new(name),
858 type_: type_.path(),
859 }
860 }
861
862 #[test]
863 fn a_record_writes_one_component_per_line() {
864 let node = Decl::Type(TypeDecl {
865 doc: Doc::none(),
866 name: Ident::new("Pose"),
867 def: TypeDef::Record(Record::plain(vec![
868 component("X", Primitive::LongInteger),
869 component("Y", Primitive::LongFloat),
870 ])),
871 });
872 assert_eq!(
873 decl(&node),
874 "type Pose is record\n \
875 X : Long_Integer;\n \
876 Y : Long_Float;\n\
877 end record;\n"
878 );
879 }
880
881 #[test]
882 fn a_record_with_no_components_is_a_null_record() {
883 let node = Decl::Type(TypeDecl {
884 doc: Doc::none(),
885 name: Ident::new("Empty"),
886 def: TypeDef::Record(Record::plain(Vec::new())),
887 });
888 assert_eq!(decl(&node), "type Empty is null record;\n");
889 }
890
891 #[test]
892 fn a_discriminated_record_writes_its_variant_part() {
893 let node = Decl::Type(TypeDecl {
894 doc: Doc::none(),
895 name: Ident::new("Payload"),
896 def: TypeDef::Record(Record {
897 body_text: zerodds_lang_cst::common::BodyText::none(),
898 discriminants: vec![Discriminant {
899 name: Ident::new("D"),
900 type_: Primitive::LongInteger.path(),
901 default: Some(Expr::int(0)),
902 }],
903 components: Vec::new(),
904 variant: Some(Variant {
905 discriminant: Ident::new("D"),
906 arms: vec![
907 Arm {
908 choices: vec![Choice::Value(Expr::int(0))],
909 components: vec![component("Idle", Primitive::LongInteger)],
910 },
911 Arm {
912 choices: vec![Choice::Others],
913 components: Vec::new(),
914 },
915 ],
916 }),
917 }),
918 });
919 assert_eq!(
920 decl(&node),
921 "type Payload (D : Long_Integer := 0) is record\n \
922 case D is\n \
923 when 0 =>\n \
924 Idle : Long_Integer;\n \
925 when others =>\n \
926 null;\n \
927 end case;\n\
928 end record;\n"
929 );
930 }
931
932 #[test]
933 fn no_enumeration_representation_clause_form_exists() {
934 let node = Decl::Object(ObjectDecl {
938 doc: Doc::none(),
939 name: Ident::new("ACTIVE"),
940 type_: Primitive::LongInteger.path(),
941 value: Expr::int(4),
942 });
943 assert_eq!(decl(&node), "ACTIVE : constant Long_Integer := 4;\n");
944 }
945
946 #[test]
947 fn an_unconstrained_array_and_the_access_type_that_reaches_it() {
948 let array = Decl::Type(TypeDecl {
949 doc: Doc::none(),
950 name: Ident::new("Zd_Seq_Long_Array"),
951 def: TypeDef::Array {
952 index: vec![Index::Unconstrained(Path::single("Natural"))],
953 element: Primitive::LongInteger.path(),
954 },
955 });
956 let access = Decl::Type(TypeDecl {
957 doc: Doc::none(),
958 name: Ident::new("Zd_Seq_Long"),
959 def: TypeDef::Access(Path::single("Zd_Seq_Long_Array")),
960 });
961 assert_eq!(
962 decl(&array),
963 "type Zd_Seq_Long_Array is array (Natural range <>) of Long_Integer;\n"
964 );
965 assert_eq!(
966 decl(&access),
967 "type Zd_Seq_Long is access Zd_Seq_Long_Array;\n"
968 );
969 }
970
971 #[test]
972 fn a_string_is_an_access_to_character_not_unbounded_string() {
973 let array = Decl::Type(TypeDecl {
974 doc: Doc::none(),
975 name: Ident::new("Zd_Str_Array"),
976 def: TypeDef::Array {
977 index: vec![Index::Unconstrained(Path::single("Natural"))],
978 element: Path::single("Character"),
979 },
980 });
981 assert_eq!(
982 decl(&array),
983 "type Zd_Str_Array is array (Natural range <>) of Character;\n"
984 );
985 }
986
987 #[test]
988 fn an_incomplete_type_declares_a_name_and_nothing_else() {
989 let incomplete = Decl::Type(TypeDecl {
990 doc: Doc::none(),
991 name: Ident::new("Node"),
992 def: TypeDef::Incomplete,
993 });
994 assert_eq!(decl(&incomplete), "type Node;\n");
995 }
996
997 #[test]
998 fn a_subprogram_has_no_is_abstract_form() {
999 let node = Decl::Subprogram(Subprogram {
1002 doc: Doc::none(),
1003 kind: SubprogramKind::Function,
1004 name: Ident::new("Zd_Dec_Node"),
1005 params: vec![Param {
1006 name: Ident::new("R"),
1007 mode: Mode::InOut,
1008 type_: Path::new(&["Zd_Wire83", "Reader"]),
1009 }],
1010 result: Some(Path::single("Node")),
1011 });
1012 assert_eq!(
1013 decl(&node),
1014 "function Zd_Dec_Node (R : in out Zd_Wire83.Reader) return Node;\n"
1015 );
1016 }
1017
1018 #[test]
1019 fn a_file_writes_its_context_clause_before_the_package() {
1020 let node = File {
1021 doc: Doc::line("Generated. Do not edit."),
1022 withs: vec![Path::single("Zd_Wire83")],
1023 package: Ident::new("Zd83_Types"),
1024 decls: vec![Decl::Type(TypeDecl {
1025 doc: Doc::none(),
1026 name: Ident::new("Pose"),
1027 def: TypeDef::Record(Record::plain(vec![component("X", Primitive::LongInteger)])),
1028 })],
1029 };
1030 assert_eq!(
1031 file(&node),
1032 "-- Generated. Do not edit.\n\
1033 \n\
1034 with Zd_Wire83;\n\
1035 \n\
1036 package Zd83_Types is\n\
1037 \n \
1038 type Pose is record\n \
1039 X : Long_Integer;\n \
1040 end record;\n\
1041 \n\
1042 end Zd83_Types;\n"
1043 );
1044 }
1045
1046 #[test]
1047 fn a_boolean_is_written_with_the_case_ada_declares_it_in() {
1048 assert_eq!(expr(&Expr::Literal(Literal::Bool(true))), "True");
1049 assert_eq!(expr(&Expr::Literal(Literal::Bool(false))), "False");
1050 }
1051
1052 #[test]
1053 fn a_hexadecimal_literal_uses_the_ada_base_form() {
1054 assert_eq!(
1055 expr(&Expr::Literal(Literal::Bytes(alloc::vec![1, 255]))),
1056 "(16#01#, 16#FF#)"
1057 );
1058 }
1059
1060 #[test]
1061 fn every_aggregate_carries_at_least_one_association() {
1062 let node = Expr::Aggregate(vec![zerodds_lang_cst::ada83::Association {
1066 choice: Ident::new("D"),
1067 value: Expr::int(0),
1068 }]);
1069 assert_eq!(expr(&node), "(D => 0)");
1070 }
1071
1072 #[test]
1073 fn selection_indexing_dereference_and_slice_render_as_ada_writes_them() {
1074 let name = |text: &str| Expr::name(text);
1075 let selected = Expr::Selected {
1076 value: alloc::boxed::Box::new(name("v")),
1077 name: Ident::new("Key"),
1078 };
1079 assert_eq!(expr(&selected), "v.Key");
1080 let deref = Expr::Deref(alloc::boxed::Box::new(name("v")));
1081 assert_eq!(expr(&deref), "v.all");
1082 let slice = Expr::Slice {
1083 value: alloc::boxed::Box::new(deref),
1084 low: alloc::boxed::Box::new(Expr::int(0)),
1085 high: alloc::boxed::Box::new(name("zd_n")),
1086 };
1087 assert_eq!(expr(&slice), "v.all (0 .. zd_n)");
1088 }
1089
1090 #[test]
1091 fn an_allocator_takes_a_range_or_a_qualified_value() {
1092 let empty = Expr::New {
1093 mark: Path::single("Zd_Seq_Long_Array"),
1094 init: AllocInit::Range {
1095 low: alloc::boxed::Box::new(Expr::int(0)),
1096 high: alloc::boxed::Box::new(Expr::int(-1)),
1097 },
1098 };
1099 assert_eq!(expr(&empty), "new Zd_Seq_Long_Array (0 .. -1)");
1100 let kept = Expr::New {
1101 mark: Path::single("Zd_Seq_Long_Array"),
1102 init: AllocInit::Qualified(alloc::boxed::Box::new(Expr::name("v"))),
1103 };
1104 assert_eq!(expr(&kept), "new Zd_Seq_Long_Array'(v)");
1105 }
1106
1107 #[test]
1108 fn a_declaration_mid_list_opens_a_declare_block_over_the_rest() {
1109 let nodes = vec![
1110 Stmt::Assign {
1111 place: Expr::name("a"),
1112 value: Expr::int(1),
1113 },
1114 Stmt::Var(VarDecl {
1115 name: Ident::new("zd_n"),
1116 mark: Primitive::LongInteger.path(),
1117 constraint: None,
1118 value: Some(Expr::int(0)),
1119 }),
1120 Stmt::Assign {
1121 place: Expr::name("a"),
1122 value: Expr::name("zd_n"),
1123 },
1124 ];
1125 assert_eq!(
1126 stmts(&nodes),
1127 "a := 1;\n\
1128 declare\n \
1129 zd_n : Long_Integer := 0;\n\
1130 begin\n \
1131 a := zd_n;\n\
1132 end;\n"
1133 );
1134 }
1135
1136 #[test]
1137 fn the_loop_forms_and_exit_render() {
1138 let counted = Stmt::For {
1139 counter: Ident::new("zd_i"),
1140 low: Expr::int(0),
1141 high: Expr::int(2),
1142 body: vec![Stmt::Null],
1143 };
1144 assert_eq!(
1145 stmt(&counted),
1146 "for zd_i in 0 .. 2 loop\n null;\nend loop;\n"
1147 );
1148 let headed = Stmt::While {
1149 condition: Expr::Literal(Literal::Bool(true)),
1150 body: vec![Stmt::Exit],
1151 };
1152 assert_eq!(stmt(&headed), "while True loop\n exit;\nend loop;\n");
1153 }
1154
1155 #[test]
1156 fn an_if_writes_its_else_and_an_empty_arm_is_null() {
1157 let node = Stmt::If {
1158 condition: Expr::name("ok"),
1159 then: Vec::new(),
1160 otherwise: Some(vec![Stmt::Return(None)]),
1161 };
1162 assert_eq!(
1163 stmt(&node),
1164 "if ok then\n null;\nelse\n return;\nend if;\n"
1165 );
1166 }
1167}