use crate::common::{Doc, Ident, Literal, Path};
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 usings: Vec<Using>,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Using {
pub doc: Doc,
pub alias: Option<Ident>,
pub static_: bool,
pub global: bool,
pub target: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Item {
Namespace(Namespace),
Type(TypeDecl),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Namespace {
pub doc: Doc,
pub name: Path,
pub usings: Vec<Using>,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeDecl {
Class(Class),
Struct(Struct),
Interface(Interface),
Enum(Enum),
Record(Record),
Delegate(Delegate),
}
impl TypeDecl {
#[must_use]
pub const fn name(&self) -> &Ident {
match self {
Self::Class(node) => &node.name,
Self::Struct(node) => &node.name,
Self::Interface(node) => &node.name,
Self::Enum(node) => &node.name,
Self::Record(node) => &node.name,
Self::Delegate(node) => &node.name,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Class {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub bases: Vec<Type>,
pub constraints: Vec<TypeConstraint>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Struct {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub bases: Vec<Type>,
pub constraints: Vec<TypeConstraint>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interface {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub bases: Vec<Type>,
pub constraints: Vec<TypeConstraint>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub underlying: Option<Predefined>,
pub members: Vec<EnumMember>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumMember {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub name: Ident,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Record {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub kind: RecordKind,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub parameters: Option<Vec<Param>>,
pub bases: Vec<Type>,
pub constraints: Vec<TypeConstraint>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RecordKind {
Class,
Struct,
}
impl RecordKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Class => "record class",
Self::Struct => "record struct",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Delegate {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub returns: Type,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub params: Vec<Param>,
pub constraints: Vec<TypeConstraint>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Member {
Field(Field),
Property(Property),
Method(Method),
Constructor(Constructor),
Nested(Box<TypeDecl>),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub type_: Type,
pub name: Ident,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Property {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub type_: Type,
pub name: Ident,
pub accessors: Vec<Accessor>,
pub expression: Option<Expr>,
pub initialiser: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Accessor {
pub kind: AccessorKind,
pub modifiers: Vec<Modifier>,
pub body: Option<Block>,
pub expression: Option<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AccessorKind {
Get,
Set,
Init,
}
impl AccessorKind {
pub const ALL: [Self; 3] = [Self::Get, Self::Set, Self::Init];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Get => "get",
Self::Set => "set",
Self::Init => "init",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Method {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub returns: Type,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub params: Vec<Param>,
pub constraints: Vec<TypeConstraint>,
pub body: Option<Block>,
pub expression: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Constructor {
pub doc: Doc,
pub attributes: Vec<Attribute>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub params: Vec<Param>,
pub initialiser: Option<ConstructorInitialiser>,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstructorInitialiser {
pub this: bool,
pub args: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub attributes: Vec<Attribute>,
pub modifier: Option<ParamModifier>,
pub type_: Type,
pub name: Ident,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParamModifier {
Ref,
Out,
In,
Params,
This,
}
impl ParamModifier {
pub const ALL: [Self; 5] = [Self::Ref, Self::Out, Self::In, Self::Params, Self::This];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Ref => "ref",
Self::Out => "out",
Self::In => "in",
Self::Params => "params",
Self::This => "this",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Modifier {
Public,
Private,
Protected,
Internal,
Static,
Sealed,
Abstract,
Partial,
Readonly,
Const,
Virtual,
Override,
New,
Extern,
Unsafe,
Async,
Volatile,
Required,
Ref,
File,
}
impl Modifier {
pub const ALL: [Self; 20] = [
Self::Public,
Self::Private,
Self::Protected,
Self::Internal,
Self::Static,
Self::Sealed,
Self::Abstract,
Self::Partial,
Self::Readonly,
Self::Const,
Self::Virtual,
Self::Override,
Self::New,
Self::Extern,
Self::Unsafe,
Self::Async,
Self::Volatile,
Self::Required,
Self::Ref,
Self::File,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Public => "public",
Self::Private => "private",
Self::Protected => "protected",
Self::Internal => "internal",
Self::Static => "static",
Self::Sealed => "sealed",
Self::Abstract => "abstract",
Self::Partial => "partial",
Self::Readonly => "readonly",
Self::Const => "const",
Self::Virtual => "virtual",
Self::Override => "override",
Self::New => "new",
Self::Extern => "extern",
Self::Unsafe => "unsafe",
Self::Async => "async",
Self::Volatile => "volatile",
Self::Required => "required",
Self::Ref => "ref",
Self::File => "file",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
pub target: Option<Ident>,
pub path: Path,
pub args: Vec<AttributeArg>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttributeArg {
Positional(Expr),
Named {
name: Ident,
value: Expr,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeParam {
pub variance: Option<Variance>,
pub name: Ident,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Variance {
In,
Out,
}
impl Variance {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::In => "in",
Self::Out => "out",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeConstraint {
pub param: Ident,
pub bounds: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Predefined(Predefined),
Named {
path: Path,
args: Vec<Type>,
},
Array {
element: Box<Type>,
rank: usize,
},
Nullable(Box<Type>),
Pointer(Box<Type>),
Tuple(Vec<TupleElement>),
Var,
}
impl Type {
#[must_use]
pub fn named(path: Path) -> Self {
Self::Named {
path,
args: Vec::new(),
}
}
#[must_use]
pub fn generic(path: Path, args: Vec<Self>) -> Self {
Self::Named { path, args }
}
#[must_use]
pub fn array(element: Self) -> Self {
Self::Array {
element: Box::new(element),
rank: 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TupleElement {
pub type_: Type,
pub name: Option<Ident>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Predefined {
Bool,
SByte,
Byte,
Short,
UShort,
Int,
UInt,
Long,
ULong,
Char,
Float,
Double,
Decimal,
String,
Object,
Void,
NInt,
NUInt,
Dynamic,
}
impl Predefined {
pub const ALL: [Self; 19] = [
Self::Bool,
Self::SByte,
Self::Byte,
Self::Short,
Self::UShort,
Self::Int,
Self::UInt,
Self::Long,
Self::ULong,
Self::Char,
Self::Float,
Self::Double,
Self::Decimal,
Self::String,
Self::Object,
Self::Void,
Self::NInt,
Self::NUInt,
Self::Dynamic,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bool => "bool",
Self::SByte => "sbyte",
Self::Byte => "byte",
Self::Short => "short",
Self::UShort => "ushort",
Self::Int => "int",
Self::UInt => "uint",
Self::Long => "long",
Self::ULong => "ulong",
Self::Char => "char",
Self::Float => "float",
Self::Double => "double",
Self::Decimal => "decimal",
Self::String => "string",
Self::Object => "object",
Self::Void => "void",
Self::NInt => "nint",
Self::NUInt => "nuint",
Self::Dynamic => "dynamic",
}
}
#[must_use]
pub const fn is_value_type(self) -> bool {
!matches!(
self,
Self::String | Self::Object | Self::Dynamic | Self::Void
)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Block {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Declare {
type_: Type,
name: Ident,
value: Option<Expr>,
},
Expr(Expr),
Return(Option<Expr>),
If {
condition: Expr,
then: Block,
otherwise: Option<Block>,
},
ForEach {
type_: Type,
binding: Ident,
range: Expr,
body: Block,
},
For {
init: Box<Statement>,
condition: Expr,
step: Expr,
body: Block,
},
While {
condition: Expr,
body: Block,
},
Switch {
value: Expr,
sections: Vec<SwitchSection>,
},
Throw(Expr),
Using {
resource: Box<Statement>,
body: Block,
},
Block(Block),
Break,
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwitchSection {
pub labels: Vec<Expr>,
pub body: Block,
pub breaks: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Path(Path),
Invoke {
receiver: Option<Box<Expr>>,
name: Ident,
args: Vec<Expr>,
},
Member {
value: Box<Expr>,
name: Ident,
},
Index {
value: Box<Expr>,
index: Box<Expr>,
},
New {
type_: Box<Type>,
args: Vec<Expr>,
initialiser: Vec<(Ident, Expr)>,
},
NewArray {
element: Box<Type>,
length: Option<Box<Expr>>,
elements: Vec<Expr>,
nested: usize,
},
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
Cast {
type_: Box<Type>,
value: Box<Expr>,
},
Is {
value: Box<Expr>,
type_: Box<Type>,
},
Conditional {
condition: Box<Expr>,
then: Box<Expr>,
otherwise: Box<Expr>,
},
Assign {
target: Box<Expr>,
value: Box<Expr>,
},
Paren(Box<Expr>),
This,
Default(Option<Box<Type>>),
TypeOf(Box<Type>),
NameOf(Box<Expr>),
Lambda {
params: Vec<Ident>,
body: Box<Expr>,
},
}
#[must_use]
pub fn escape_keyword(name: &str) -> String {
if RESERVED.contains(&name) {
let mut out = String::from("@");
out.push_str(name);
return out;
}
name.to_string()
}
#[must_use]
pub fn is_keyword(name: &str) -> bool {
RESERVED.contains(&name)
}
const RESERVED: &[&str] = &[
"abstract",
"as",
"base",
"bool",
"break",
"byte",
"case",
"catch",
"char",
"checked",
"class",
"const",
"continue",
"decimal",
"default",
"delegate",
"do",
"double",
"else",
"enum",
"event",
"explicit",
"extern",
"false",
"finally",
"fixed",
"float",
"for",
"foreach",
"goto",
"if",
"implicit",
"in",
"int",
"interface",
"internal",
"is",
"lock",
"long",
"namespace",
"new",
"null",
"object",
"operator",
"out",
"override",
"params",
"private",
"protected",
"public",
"readonly",
"ref",
"return",
"sbyte",
"sealed",
"short",
"sizeof",
"stackalloc",
"static",
"string",
"struct",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"uint",
"ulong",
"unchecked",
"unsafe",
"ushort",
"using",
"virtual",
"void",
"volatile",
"while",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn every_predefined_type_writes_its_own_spelling() {
let mut seen = Vec::new();
for predefined in Predefined::ALL {
assert!(!seen.contains(&predefined.as_str()));
seen.push(predefined.as_str());
}
assert_eq!(seen.len(), Predefined::ALL.len());
}
#[test]
fn the_signed_and_unsigned_widths_are_both_present() {
for (signed, unsigned) in [
(Predefined::SByte, Predefined::Byte),
(Predefined::Short, Predefined::UShort),
(Predefined::Int, Predefined::UInt),
(Predefined::Long, Predefined::ULong),
] {
assert_ne!(signed.as_str(), unsigned.as_str());
assert!(signed.is_value_type() && unsigned.is_value_type());
}
}
#[test]
fn string_and_object_are_reference_types_and_the_numbers_are_not() {
assert!(!Predefined::String.is_value_type());
assert!(!Predefined::Object.is_value_type());
assert!(Predefined::Int.is_value_type());
assert!(Predefined::Double.is_value_type());
}
#[test]
fn a_keyword_escapes_with_an_at_sign_and_keeps_its_identity() {
assert_eq!(escape_keyword("class"), "@class");
assert_eq!(escape_keyword("string"), "@string");
assert_eq!(escape_keyword("value"), "value");
for contextual in ["var", "record", "async", "await", "value", "nameof"] {
assert_eq!(escape_keyword(contextual), contextual, "{contextual}");
}
}
#[test]
fn the_reserved_list_has_no_duplicates() {
let mut seen: Vec<&str> = Vec::new();
for word in RESERVED {
assert!(!seen.contains(word), "{word} is listed twice");
seen.push(word);
}
assert_eq!(seen.len(), 77, "ECMA-334 6.4.4 lists 77 keywords");
}
#[test]
fn a_type_declaration_answers_for_its_own_name() {
let decl = TypeDecl::Class(Class {
doc: Doc::none(),
attributes: Vec::new(),
modifiers: alloc::vec![Modifier::Public],
name: Ident::new("Pose"),
generics: Vec::new(),
bases: Vec::new(),
constraints: Vec::new(),
members: Vec::new(),
});
assert_eq!(decl.name().as_str(), "Pose");
}
#[test]
fn modifiers_keep_the_order_they_were_written_in() {
let field = Field {
doc: Doc::none(),
attributes: Vec::new(),
modifiers: alloc::vec![Modifier::Public, Modifier::Static, Modifier::Readonly],
type_: Type::Predefined(Predefined::Int),
name: Ident::new("Max"),
value: None,
};
let written: Vec<&str> = field.modifiers.iter().map(|m| m.as_str()).collect();
assert_eq!(written, alloc::vec!["public", "static", "readonly"]);
}
}