use crate::common::{BodyText, Doc, Ident, Literal};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct File {
pub doc: Doc,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Abstract(AbstractDecl),
Struct(StructDecl),
Enum(EnumDecl),
Const(ConstDecl),
Function(FunctionDecl),
FunctionDef(FunctionDef),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbstractDecl {
pub doc: Doc,
pub name: Ident,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructDecl {
pub doc: Doc,
pub name: Ident,
pub supertype: Option<Ident>,
pub fields: Vec<Field>,
pub body_text: BodyText,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub doc: Doc,
pub name: Ident,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumDecl {
pub doc: Doc,
pub name: Ident,
pub base: Primitive,
pub members: Vec<Enumerator>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enumerator {
pub doc: Doc,
pub name: Ident,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstDecl {
pub doc: Doc,
pub name: Ident,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FunctionDecl {
pub doc: Doc,
pub name: Ident,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FunctionDef {
pub doc: Doc,
pub name: Ident,
pub params: Vec<Param>,
pub returns: Option<Type>,
pub body: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub name: Ident,
pub type_: Option<Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Local {
name: Ident,
type_: Option<Type>,
value: Option<Expr>,
},
Assign {
place: Expr,
value: Expr,
},
Expr(Expr),
If {
condition: Expr,
then: Vec<Statement>,
otherwise: Option<Vec<Statement>>,
},
For {
counter: Ident,
iter: Expr,
body: Vec<Statement>,
},
While {
condition: Expr,
body: Vec<Statement>,
},
Break,
Return(Option<Expr>),
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Primitive(Primitive),
Named(Ident),
Vector(Box<Type>),
NTuple {
length: Box<Expr>,
element: Box<Type>,
},
Union(Vec<Type>),
}
impl Type {
#[must_use]
pub fn named(text: &str) -> Self {
Self::Named(Ident::new(text))
}
#[must_use]
pub fn vector(element: Self) -> Self {
Self::Vector(Box::new(element))
}
#[must_use]
pub fn ntuple(length: i128, element: Self) -> Self {
Self::NTuple {
length: Box::new(Expr::int(length)),
element: Box::new(element),
}
}
#[must_use]
pub fn optional(inner: Self) -> Self {
Self::Union(alloc::vec![inner, Self::Primitive(Primitive::Nothing)])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Bool,
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float32,
Float64,
String,
Nothing,
Any,
}
impl Primitive {
pub const ALL: [Self; 14] = [
Self::Bool,
Self::Int8,
Self::Int16,
Self::Int32,
Self::Int64,
Self::UInt8,
Self::UInt16,
Self::UInt32,
Self::UInt64,
Self::Float32,
Self::Float64,
Self::String,
Self::Nothing,
Self::Any,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bool => "Bool",
Self::Int8 => "Int8",
Self::Int16 => "Int16",
Self::Int32 => "Int32",
Self::Int64 => "Int64",
Self::UInt8 => "UInt8",
Self::UInt16 => "UInt16",
Self::UInt32 => "UInt32",
Self::UInt64 => "UInt64",
Self::Float32 => "Float32",
Self::Float64 => "Float64",
Self::String => "String",
Self::Nothing => "Nothing",
Self::Any => "Any",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Name(Ident),
Type(Box<Type>),
Call {
func: Box<Type>,
args: Vec<Expr>,
},
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Paren(Box<Expr>),
FreeCall {
func: Ident,
args: Vec<Expr>,
},
Field {
value: Box<Expr>,
name: Ident,
},
Index {
value: Box<Expr>,
index: Box<Expr>,
},
Range {
start: Box<Expr>,
stop: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
AnyVector(Vec<Expr>),
}
impl Expr {
#[must_use]
pub fn int(value: i128) -> Self {
Self::Literal(Literal::int(value))
}
#[must_use]
pub fn convert(type_: Type, value: Self) -> Self {
Self::Call {
func: Box::new(type_),
args: alloc::vec![value],
}
}
#[must_use]
pub fn name(text: &str) -> Self {
Self::Name(Ident::new(text))
}
#[must_use]
pub fn free_call(func: &str, args: Vec<Self>) -> Self {
Self::FreeCall {
func: Ident::new(func),
args,
}
}
#[must_use]
pub fn field(value: Self, name: &str) -> Self {
Self::Field {
value: Box::new(value),
name: Ident::new(name),
}
}
#[must_use]
pub fn index(value: Self, index: Self) -> Self {
Self::Index {
value: Box::new(value),
index: Box::new(index),
}
}
#[must_use]
pub fn range(start: Self, stop: Self) -> Self {
Self::Range {
start: Box::new(start),
stop: Box::new(stop),
}
}
}
#[must_use]
pub fn escape_name(name: &str) -> String {
if KEYWORDS.contains(&name) || WRITTEN_NAMES.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
name.to_string()
}
#[must_use]
pub fn escape_field(name: &str) -> String {
if KEYWORDS.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
name.to_string()
}
pub const KEYWORDS: &[&str] = &[
"baremodule",
"begin",
"break",
"catch",
"const",
"continue",
"do",
"else",
"elseif",
"end",
"export",
"false",
"for",
"function",
"global",
"if",
"import",
"let",
"local",
"macro",
"module",
"quote",
"return",
"struct",
"true",
"try",
"using",
"while",
];
pub const WRITTEN_NAMES: &[&str] = &[
"Any", "Bool", "Enum", "Float32", "Float64", "Int8", "Int16", "Int32", "Int64", "NTuple",
"Nothing", "String", "UInt8", "UInt16", "UInt32", "UInt64", "Union", "Vector", "nothing",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn every_primitive_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_primitive_has_a_platform_defined_width() {
for primitive in Primitive::ALL {
assert!(
!matches!(primitive.as_str(), "Int" | "UInt" | "BigInt" | "BigFloat"),
"{} has no fixed width",
primitive.as_str()
);
}
}
#[test]
fn a_keyword_escapes_with_a_trailing_underscore() {
assert_eq!(escape_name("end"), "end_");
assert_eq!(escape_name("function"), "function_");
assert_eq!(escape_name("payload"), "payload");
}
#[test]
fn a_name_the_file_writes_escapes_although_it_would_parse() {
for name in ["String", "Int32", "Vector", "Nothing", "Union", "nothing"] {
assert_eq!(escape_name(name), alloc::format!("{name}_"));
}
}
#[test]
fn a_field_escapes_a_keyword_and_keeps_a_type_name() {
assert_eq!(escape_field("end"), "end_");
assert_eq!(escape_field("Int32"), "Int32");
assert_eq!(escape_field("String"), "String");
}
#[test]
fn a_soft_keyword_is_left_alone_because_it_is_a_legal_name() {
for name in ["where", "in", "isa", "outer", "type", "mutable", "abstract"] {
assert_eq!(escape_name(name), name);
assert_eq!(escape_field(name), name);
}
}
#[test]
fn a_leading_underscore_is_left_alone() {
assert_eq!(escape_field("_d"), "_d");
assert_eq!(escape_name("_reserved"), "_reserved");
}
#[test]
fn neither_reserved_list_has_a_duplicate() {
for list in [KEYWORDS, WRITTEN_NAMES] {
let mut seen: Vec<&str> = Vec::new();
for name in list {
assert!(!seen.contains(name), "{name} is listed twice");
seen.push(name);
}
}
}
#[test]
fn an_array_type_nests_outermost_dimension_first() {
let type_ = Type::ntuple(2, Type::ntuple(3, Type::Primitive(Primitive::Int32)));
let Type::NTuple { length, element } = &type_ else {
panic!("not an NTuple");
};
assert_eq!(**length, Expr::int(2));
let Type::NTuple { length, .. } = &**element else {
panic!("the element is not an NTuple");
};
assert_eq!(**length, Expr::int(3));
}
#[test]
fn an_optional_puts_the_type_first_and_its_absence_second() {
let type_ = Type::optional(Type::Primitive(Primitive::Int32));
let Type::Union(arms) = &type_ else {
panic!("not a Union");
};
assert_eq!(arms.len(), 2);
assert_eq!(arms[0], Type::Primitive(Primitive::Int32));
assert_eq!(arms[1], Type::Primitive(Primitive::Nothing));
}
#[test]
fn a_struct_carries_no_mutability_and_a_supertype_only_when_set() {
let decl = StructDecl {
body_text: crate::common::BodyText::none(),
doc: Doc::none(),
name: Ident::new("Pose"),
supertype: None,
fields: alloc::vec![Field {
doc: Doc::none(),
name: Ident::new("x"),
type_: Type::Primitive(Primitive::Int32),
}],
};
assert!(decl.supertype.is_none());
assert_eq!(decl.fields.len(), 1);
}
}