use num_bigint::BigInt;
use crate::token::Span;
mod analysis;
mod dump;
#[cfg(test)]
mod tests;
pub use analysis::{celled_locals, child_funcdefs, free_names};
pub use dump::dump;
#[derive(Debug, Clone, PartialEq)]
pub struct Script {
pub stmts: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub name: String,
pub default: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Params {
pub params: Vec<Param>,
pub vararg: Option<String>,
}
impl Params {
pub fn is_empty(&self) -> bool {
self.params.is_empty() && self.vararg.is_none()
}
pub fn has_vararg(&self) -> bool {
self.vararg.is_some()
}
pub fn required(&self) -> usize {
self.params.iter().filter(|p| p.default.is_none()).count()
}
pub fn max_positional(&self) -> Option<usize> {
if self.has_vararg() {
None
} else {
Some(self.params.len())
}
}
pub fn binding_names(&self) -> Vec<String> {
let mut names: Vec<String> = self.params.iter().map(|p| p.name.clone()).collect();
if let Some(rest) = &self.vararg {
names.push(rest.clone());
}
names
}
pub fn render(&self, callee: &str) -> String {
let mut parts: Vec<String> = self
.params
.iter()
.map(|p| {
if p.default.is_some() {
format!("{} = …", p.name)
} else {
p.name.clone()
}
})
.collect();
if let Some(rest) = &self.vararg {
parts.push(format!("many {rest}"));
}
format!("{callee}({})", parts.join(", "))
}
}
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Decl {
names: Vec<String>,
rest: Option<String>,
expr: Expr,
span: Span,
},
ConstDecl {
name: String,
expr: Expr,
span: Span,
},
Import {
module: String,
path: Option<String>,
span: Span,
},
Assign {
targets: Vec<Expr>,
rest: Option<Expr>,
expr: Expr,
op: Option<BinOp>,
flavored: bool,
span: Span,
},
Bark { expr: Expr, span: Span },
If {
branches: Vec<(Expr, Vec<Stmt>)>,
else_body: Option<Vec<Stmt>>,
span: Span,
},
For {
vars: Vec<String>,
rest: Option<String>,
iter: Expr,
body: Vec<Stmt>,
span: Span,
},
While {
cond: Expr,
body: Vec<Stmt>,
span: Span,
},
FuncDef {
name: String,
params: Params,
body: Vec<Stmt>,
span: Span,
},
ObjDef {
name: String,
parent: Option<String>,
methods: Vec<Stmt>,
span: Span,
},
Try {
body: Vec<Stmt>,
err_name: String,
handler: Vec<Stmt>,
span: Span,
},
Return { expr: Option<Expr>, span: Span },
Bonk { expr: Expr, span: Span },
Amaze {
cond: Expr,
message: Option<Expr>,
span: Span,
},
Bork { span: Span },
Continue { span: Span },
ExprStmt { expr: Expr },
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Int {
value: BigInt,
span: Span,
},
Float {
value: f64,
span: Span,
},
Str {
value: String,
span: Span,
},
Bool {
value: bool,
span: Span,
},
None {
span: Span,
},
Ident {
name: String,
span: Span,
},
List {
items: Vec<Expr>,
span: Span,
},
Dict {
entries: Vec<(Expr, Expr)>,
span: Span,
},
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
span: Span,
},
Unary {
op: UnOp,
operand: Box<Expr>,
span: Span,
},
Call {
callee: Box<Expr>,
args: Vec<Expr>,
kwargs: Vec<(String, Expr)>,
span: Span,
},
Index {
obj: Box<Expr>,
index: Box<Expr>,
span: Span,
},
Slice {
obj: Box<Expr>,
start: Option<Box<Expr>>,
end: Option<Box<Expr>>,
step: Option<Box<Expr>>,
span: Span,
},
Ternary {
cond: Box<Expr>,
then: Box<Expr>,
otherwise: Box<Expr>,
span: Span,
},
Attr {
obj: Box<Expr>,
name: String,
span: Span,
},
SuperCall {
method: String,
args: Vec<Expr>,
span: Span,
},
StrInterp {
parts: Vec<InterpPart>,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum InterpPart {
Lit(String),
Expr(Expr),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
FloorDiv,
Rem,
Pow,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
In,
NotIn,
And,
Or,
}
impl BinOp {
pub fn symbol(self) -> &'static str {
match self {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::FloorDiv => "//",
BinOp::Rem => "%",
BinOp::Pow => "**",
BinOp::BitAnd => "&",
BinOp::BitOr => "|",
BinOp::BitXor => "^",
BinOp::Shl => "<<",
BinOp::Shr => ">>",
BinOp::Eq => "==",
BinOp::NotEq => "!=",
BinOp::Lt => "<",
BinOp::LtEq => "<=",
BinOp::Gt => ">",
BinOp::GtEq => ">=",
BinOp::In => "in",
BinOp::NotIn => "not in",
BinOp::And => "and",
BinOp::Or => "or",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
Not,
Neg,
BitNot,
}
impl UnOp {
pub fn symbol(self) -> &'static str {
match self {
UnOp::Not => "not",
UnOp::Neg => "neg",
UnOp::BitNot => "~",
}
}
}
impl Stmt {
pub fn span(&self) -> Span {
match self {
Stmt::Decl { span, .. }
| Stmt::ConstDecl { span, .. }
| Stmt::Import { span, .. }
| Stmt::Assign { span, .. }
| Stmt::Bark { span, .. }
| Stmt::If { span, .. }
| Stmt::For { span, .. }
| Stmt::While { span, .. }
| Stmt::FuncDef { span, .. }
| Stmt::ObjDef { span, .. }
| Stmt::Try { span, .. }
| Stmt::Return { span, .. }
| Stmt::Bonk { span, .. }
| Stmt::Amaze { span, .. }
| Stmt::Bork { span }
| Stmt::Continue { span } => *span,
Stmt::ExprStmt { expr } => expr.span(),
}
}
}
impl Expr {
pub fn span(&self) -> Span {
match self {
Expr::Int { span, .. }
| Expr::Float { span, .. }
| Expr::Str { span, .. }
| Expr::Bool { span, .. }
| Expr::None { span }
| Expr::Ident { span, .. }
| Expr::List { span, .. }
| Expr::Dict { span, .. }
| Expr::Binary { span, .. }
| Expr::Unary { span, .. }
| Expr::Call { span, .. }
| Expr::Index { span, .. }
| Expr::Slice { span, .. }
| Expr::Ternary { span, .. }
| Expr::Attr { span, .. }
| Expr::SuperCall { span, .. }
| Expr::StrInterp { span, .. } => *span,
}
}
}
pub fn for_each_child_block<'a>(stmt: &'a Stmt, f: &mut impl FnMut(&'a [Stmt])) {
match stmt {
Stmt::If {
branches,
else_body,
..
} => {
for (_, body) in branches {
f(body);
}
if let Some(body) = else_body {
f(body);
}
}
Stmt::For { body, .. } | Stmt::While { body, .. } => f(body),
Stmt::Try { body, handler, .. } => {
f(body);
f(handler);
}
Stmt::Decl { .. }
| Stmt::ConstDecl { .. }
| Stmt::Import { .. }
| Stmt::Assign { .. }
| Stmt::Bark { .. }
| Stmt::FuncDef { .. }
| Stmt::ObjDef { .. }
| Stmt::Return { .. }
| Stmt::Bonk { .. }
| Stmt::Amaze { .. }
| Stmt::Bork { .. }
| Stmt::Continue { .. }
| Stmt::ExprStmt { .. } => {}
}
}
pub fn hoisted_names(stmts: &[Stmt]) -> Vec<String> {
let mut names = Vec::new();
collect_hoisted(stmts, &mut names);
names
}
pub(crate) fn toplevel_hoisted(stmts: &[Stmt]) -> Vec<String> {
let mut names = Vec::new();
for stmt in stmts {
if matches!(stmt, Stmt::FuncDef { .. } | Stmt::ObjDef { .. }) {
continue;
}
collect_hoisted(std::slice::from_ref(stmt), &mut names);
}
names
}
pub(crate) fn collect_hoisted(stmts: &[Stmt], names: &mut Vec<String>) {
for stmt in stmts {
match stmt {
Stmt::ConstDecl { name, .. } | Stmt::FuncDef { name, .. } => push_unique(names, name),
Stmt::Decl {
names: decl_names,
rest,
..
} => {
for name in decl_names {
push_unique(names, name);
}
if let Some(rest) = rest {
push_unique(names, rest);
}
}
Stmt::For { vars, rest, .. } => {
for var in vars {
push_unique(names, var);
}
if let Some(rest) = rest {
push_unique(names, rest);
}
}
Stmt::Try { err_name, .. } => push_unique(names, err_name),
_ => {}
}
for_each_child_block(stmt, &mut |body| collect_hoisted(body, names));
}
}
pub(crate) fn push_unique(names: &mut Vec<String>, name: &str) {
if !names.iter().any(|n| n == name) {
names.push(name.to_string());
}
}