use crate::common::{BodyText, Doc, Ident, Literal, Path};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct File {
pub doc: Doc,
pub withs: Vec<Path>,
pub package: Ident,
pub decls: Vec<Decl>,
}
impl Default for File {
fn default() -> Self {
Self {
doc: Doc::none(),
withs: Vec::new(),
package: Ident::new("Zd83_Types"),
decls: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Type(TypeDecl),
Subtype(SubtypeDecl),
SubprogramBody(SubprogramBody),
Object(ObjectDecl),
Subprogram(Subprogram),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeDecl {
pub doc: Doc,
pub name: Ident,
pub def: TypeDef,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeDef {
Record(Record),
Derived(Path),
Array {
index: Vec<Index>,
element: Path,
},
Access(Path),
Incomplete,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Record {
pub discriminants: Vec<Discriminant>,
pub components: Vec<Component>,
pub variant: Option<Variant>,
pub body_text: BodyText,
}
impl Record {
#[must_use]
pub fn plain(components: Vec<Component>) -> Self {
Self {
discriminants: Vec::new(),
components,
variant: None,
body_text: BodyText::none(),
}
}
#[must_use]
pub fn is_null(&self) -> bool {
self.components.is_empty()
&& self.variant.is_none()
&& self.body_text.begin.is_empty()
&& self.body_text.end.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Discriminant {
pub name: Ident,
pub type_: Path,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Component {
pub doc: Doc,
pub name: Ident,
pub type_: Path,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Variant {
pub discriminant: Ident,
pub arms: Vec<Arm>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Arm {
pub choices: Vec<Choice>,
pub components: Vec<Component>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Choice {
Value(Expr),
Others,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Index {
Range {
low: Expr,
high: Expr,
},
Unconstrained(Path),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubtypeDecl {
pub doc: Doc,
pub name: Ident,
pub mark: Path,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectDecl {
pub doc: Doc,
pub name: Ident,
pub type_: Path,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subprogram {
pub doc: Doc,
pub kind: SubprogramKind,
pub name: Ident,
pub params: Vec<Param>,
pub result: Option<Path>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubprogramKind {
Procedure,
Function,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub name: Ident,
pub mode: Mode,
pub type_: Path,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
In,
Out,
InOut,
}
impl Mode {
pub const ALL: [Self; 3] = [Self::In, Self::Out, Self::InOut];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::In => "in",
Self::Out => "out",
Self::InOut => "in out",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Name(Path),
Call {
name: Path,
args: Vec<Expr>,
},
Negate(Box<Expr>),
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
Selected {
value: Box<Expr>,
name: Ident,
},
Indexed {
value: Box<Expr>,
index: Box<Expr>,
},
Deref(Box<Expr>),
Slice {
value: Box<Expr>,
low: Box<Expr>,
high: Box<Expr>,
},
Attribute {
value: Box<Expr>,
name: Ident,
args: Vec<Expr>,
},
Convert {
mark: Path,
value: Box<Expr>,
},
Aggregate(Vec<Association>),
New {
mark: Path,
init: AllocInit,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Association {
pub choice: Ident,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AllocInit {
Range {
low: Box<Expr>,
high: Box<Expr>,
},
Qualified(Box<Expr>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stmt {
Null,
Var(VarDecl),
Subtype {
name: Ident,
mark: Path,
},
Assign {
place: Expr,
value: Expr,
},
Call(Expr),
Return(Option<Expr>),
If {
condition: Expr,
then: Vec<Stmt>,
otherwise: Option<Vec<Stmt>>,
},
For {
counter: Ident,
low: Expr,
high: Expr,
body: Vec<Stmt>,
},
While {
condition: Expr,
body: Vec<Stmt>,
},
Exit,
Declare {
vars: Vec<VarDecl>,
body: Vec<Stmt>,
},
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VarDecl {
pub name: Ident,
pub mark: Path,
pub constraint: Option<Expr>,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubprogramBody {
pub spec: Subprogram,
pub body: Vec<Stmt>,
}
impl Expr {
#[must_use]
pub fn int(value: i128) -> Self {
Self::Literal(Literal::int(value))
}
#[must_use]
pub fn name(text: &str) -> Self {
Self::Name(Path::single(text))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Boolean,
LongInteger,
Float,
LongFloat,
Text,
Address,
}
impl Primitive {
pub const ALL: [Self; 6] = [
Self::Boolean,
Self::LongInteger,
Self::Float,
Self::LongFloat,
Self::Text,
Self::Address,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Boolean => "Boolean",
Self::LongInteger => "Long_Integer",
Self::Float => "Float",
Self::LongFloat => "Long_Float",
Self::Text => "Zd_Wire83.Text",
Self::Address => "System.Address",
}
}
#[must_use]
pub fn path(self) -> Path {
Path::new(&self.as_str().split('.').collect::<Vec<&str>>())
}
#[must_use]
pub fn unit(self) -> Option<Path> {
match self {
Self::Boolean | Self::LongInteger | Self::Float | Self::LongFloat => None,
Self::Text => Some(Path::single("Zd_Wire83")),
Self::Address => Some(Path::single("System")),
}
}
}
pub const ESCAPE_MARKER: &str = "Idl";
pub const ESCAPE_SUFFIX: &str = "_Idl";
#[must_use]
pub fn legal_name(name: &str) -> String {
let mut out = String::with_capacity(name.len() + ESCAPE_MARKER.len());
if name.starts_with('_') {
out.push_str(ESCAPE_MARKER);
}
for ch in name.chars() {
if ch == '_' && out.ends_with('_') {
out.push_str(ESCAPE_MARKER);
}
out.push(ch);
}
if out.ends_with('_') {
out.push_str(ESCAPE_MARKER);
}
out
}
#[must_use]
pub fn escape_name(name: &str) -> String {
if is_reserved(name) {
let mut out = name.to_string();
out.push_str(ESCAPE_SUFFIX);
return out;
}
name.to_string()
}
#[must_use]
pub fn is_reserved(name: &str) -> bool {
RESERVED
.iter()
.any(|reserved| reserved.eq_ignore_ascii_case(name))
}
#[must_use]
pub fn ada_case(name: &str) -> String {
let mut out = String::with_capacity(name.len());
let mut at_word_start = true;
for ch in name.chars() {
if at_word_start {
for upper in ch.to_uppercase() {
out.push(upper);
}
} else {
out.push(ch);
}
at_word_start = ch == '_';
}
out
}
#[must_use]
pub fn fold(name: &str) -> String {
name.to_lowercase()
}
pub const RESERVED: &[&str] = &[
"abort",
"abs",
"accept",
"access",
"all",
"and",
"array",
"at",
"begin",
"body",
"case",
"constant",
"declare",
"delay",
"delta",
"digits",
"do",
"else",
"elsif",
"end",
"entry",
"exception",
"exit",
"for",
"function",
"generic",
"goto",
"if",
"in",
"is",
"limited",
"loop",
"mod",
"new",
"not",
"null",
"of",
"or",
"others",
"out",
"package",
"pragma",
"private",
"procedure",
"raise",
"range",
"record",
"rem",
"renames",
"return",
"reverse",
"select",
"separate",
"subtype",
"task",
"then",
"type",
"use",
"when",
"while",
"with",
"xor",
"system",
"standard",
"zd_wire83",
"boolean",
"character",
"duration",
"float",
"integer",
"long_integer",
"long_float",
"natural",
"positive",
"string",
"zd83_types",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn every_named_type_writes_its_own_spelling() {
let mut seen = Vec::new();
for primitive in Primitive::ALL {
let text = primitive.as_str();
assert!(!seen.contains(&text), "{text} is used twice");
seen.push(text);
}
assert_eq!(seen.len(), Primitive::ALL.len());
}
#[test]
fn no_named_type_needs_a_use_clause() {
for primitive in Primitive::ALL {
assert!(
primitive.unit().is_none()
|| primitive == Primitive::Text
|| primitive == Primitive::Address,
);
}
}
#[test]
fn no_child_unit_is_named() {
for primitive in Primitive::ALL {
if let Some(unit) = primitive.unit() {
assert!(
!unit.segments().iter().any(|s| s.as_str() == "Ada"),
"{} names a child unit",
primitive.as_str()
);
}
}
}
#[test]
fn a_reserved_word_escapes_with_a_letter_and_never_an_underscore() {
assert_eq!(escape_name("type"), "type_Idl");
assert_eq!(escape_name("range"), "range_Idl");
assert_eq!(escape_name("payload"), "payload");
for name in RESERVED {
let escaped = escape_name(name);
assert!(!escaped.ends_with('_'), "{escaped} ends in an underscore");
assert!(!escaped.contains("__"), "{escaped} has two in a row");
}
}
#[test]
fn the_reserved_check_folds_case_because_ada_does() {
for spelling in ["type", "Type", "TYPE", "TyPe"] {
assert!(is_reserved(spelling), "{spelling} was not recognised");
assert_eq!(escape_name(spelling), alloc::format!("{spelling}_Idl"));
}
}
#[test]
fn ada_95_words_are_not_reserved_here() {
for word in [
"abstract",
"aliased",
"interface",
"overriding",
"protected",
"requeue",
"some",
"synchronized",
"tagged",
"terminate",
"until",
] {
assert!(!is_reserved(word), "{word} is not an Ada 83 reserved word");
}
}
#[test]
fn an_identifier_may_not_start_with_an_underscore() {
assert_eq!(legal_name("_long"), "Idl_long");
assert_eq!(legal_name("_d"), "Idl_d");
assert_eq!(legal_name("plain"), "plain");
}
#[test]
fn an_identifier_may_not_hold_two_underscores_in_a_row() {
assert_eq!(legal_name("a__b"), "a_Idl_b");
assert_eq!(legal_name("a_b"), "a_b");
}
#[test]
fn an_identifier_may_not_end_in_an_underscore() {
assert_eq!(legal_name("foo_"), "foo_Idl");
}
#[test]
fn ada_casing_moves_the_first_letter_of_each_word_and_nothing_else() {
assert_eq!(ada_case("value"), "Value");
assert_eq!(ada_case("my_field"), "My_Field");
assert_eq!(ada_case("IDLE"), "IDLE");
}
#[test]
fn two_spellings_that_differ_only_in_case_have_one_fold_key() {
assert_eq!(fold("Value"), fold("value"));
assert_ne!(fold("Value"), fold("Values"));
}
#[test]
fn the_reserved_list_has_no_duplicates_and_is_folded_already() {
let mut seen: Vec<String> = Vec::new();
for name in RESERVED {
let key = fold(name);
assert!(!seen.contains(&key), "{name} is listed twice");
seen.push(key);
}
}
#[test]
fn a_record_with_nothing_in_it_is_a_null_record() {
assert!(Record::plain(Vec::new()).is_null());
assert!(
!Record::plain(alloc::vec![Component {
doc: Doc::none(),
name: Ident::new("X"),
type_: Primitive::LongInteger.path(),
}])
.is_null()
);
}
#[test]
fn every_parameter_mode_writes_its_ada_spelling() {
assert_eq!(Mode::In.as_str(), "in");
assert_eq!(Mode::Out.as_str(), "out");
assert_eq!(Mode::InOut.as_str(), "in out");
assert_eq!(Mode::ALL.len(), 3);
}
}