use crate::common::{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 body: Vec<Stat>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stat {
Declare {
doc: Doc,
kind: VarKind,
name: Ident,
value: Option<Expr>,
},
Assign {
doc: Doc,
place: Expr,
value: Expr,
},
Function(Function),
Return(Option<Expr>),
Expr(Expr),
If {
condition: Expr,
then: Vec<Stat>,
otherwise: Option<Vec<Stat>>,
},
For {
counter: Ident,
bound: Expr,
body: Vec<Stat>,
},
While {
condition: Expr,
body: Vec<Stat>,
},
Break,
Throw(Expr),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VarKind {
Const,
Let,
}
impl VarKind {
pub const ALL: [Self; 2] = [Self::Const, Self::Let];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Const => "const",
Self::Let => "let",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function {
pub doc: Doc,
pub name: Ident,
pub params: Vec<Ident>,
pub body: Vec<Stat>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Undefined,
Name(Ident),
Array(Vec<Expr>),
Object(Vec<(Ident, Expr)>),
New {
callee: Ident,
args: Vec<Expr>,
},
Call {
callee: Box<Expr>,
args: Vec<Expr>,
},
Member {
object: Box<Expr>,
name: Ident,
},
Index {
object: Box<Expr>,
index: Box<Expr>,
},
Paren(Box<Expr>),
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Ternary {
condition: Box<Expr>,
then: Box<Expr>,
otherwise: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
}
impl Expr {
#[must_use]
pub fn int(value: i128) -> Self {
Self::Literal(Literal::int(value))
}
#[must_use]
pub fn string(text: &str) -> Self {
Self::Literal(Literal::string(text))
}
#[must_use]
pub const fn bool(value: bool) -> Self {
Self::Literal(Literal::Bool(value))
}
#[must_use]
pub fn name(text: &str) -> Self {
Self::Name(Ident::new(text))
}
#[must_use]
pub fn member(object: Self, name: &str) -> Self {
Self::Member {
object: Box::new(object),
name: Ident::new(name),
}
}
#[must_use]
pub fn call(callee: &str, args: Vec<Self>) -> Self {
Self::Call {
callee: Box::new(Self::name(callee)),
args,
}
}
}
#[must_use]
pub fn escape_decl(name: &str) -> String {
if RESERVED.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
name.to_string()
}
#[must_use]
pub fn escape_member(name: &str) -> String {
name.to_string()
}
#[must_use]
pub fn is_reserved(name: &str) -> bool {
RESERVED.contains(&name)
}
pub const RESERVED: &[&str] = &[
"await",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"static",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn a_reserved_binding_escapes_with_a_trailing_underscore() {
assert_eq!(escape_decl("class"), "class_");
assert_eq!(escape_decl("delete"), "delete_");
assert!(is_reserved("class"));
assert!(!is_reserved("Pose"));
}
#[test]
fn no_built_in_type_name_is_reserved() {
for name in ["string", "number", "boolean", "any", "unknown", "bigint"] {
assert_eq!(escape_decl(name), name);
assert!(!is_reserved(name));
}
}
#[test]
fn a_member_name_is_never_renamed() {
for name in ["class", "new", "delete", "constructor", "typeof", "x"] {
assert_eq!(escape_member(name), name);
}
}
#[test]
fn the_reserved_list_has_no_duplicate() {
let mut seen: Vec<&str> = Vec::new();
for name in RESERVED {
assert!(!seen.contains(name), "{name} is listed twice");
seen.push(name);
}
}
#[test]
fn a_bigint_literal_carries_its_suffix_in_the_written_form() {
let narrow = Expr::Literal(Literal::Int("2".to_string()));
let wide = Expr::Literal(Literal::Int("2n".to_string()));
assert_ne!(narrow, wide);
}
#[test]
fn a_var_kind_writes_its_own_spelling() {
let mut seen = Vec::new();
for kind in VarKind::ALL {
assert!(!seen.contains(&kind.as_str()));
seen.push(kind.as_str());
}
assert_eq!(seen, alloc::vec!["const", "let"]);
}
}