mod span;
pub use span::{Span, Spanned};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Program {
pub statements: Vec<Spanned<Statement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Statement {
Import(ImportStmt),
ValDecl(ValDecl),
Module(ModuleDecl),
Assert(AssertStmt),
HclBlock(HclBlock),
Unsafe(UnsafeStmt),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportStmt {
pub path: Spanned<String>,
pub alias: Option<Spanned<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValDecl {
pub name: Spanned<String>,
pub type_ann: Option<Spanned<TypeExpr>>,
pub value: Spanned<Expr>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleDecl {
pub name: Spanned<String>,
pub body: Vec<Spanned<Statement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssertStmt {
pub condition: Spanned<Expr>,
pub message: Spanned<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HclBlock {
pub reason: Spanned<String>,
pub content: Spanned<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnsafeStmt {
pub reason: Spanned<String>,
pub body: Vec<Spanned<Statement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TypeExpr {
Named(String),
Generic {
name: String,
args: Vec<Spanned<TypeExpr>>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expr {
Literal(Literal),
Identifier(String),
MemberAccess {
object: Box<Spanned<Expr>>,
field: Spanned<String>,
},
FuncCall {
callee: Box<Spanned<Expr>>,
args: Vec<Arg>,
},
Lambda {
params: Vec<Spanned<String>>,
body: Box<Spanned<Expr>>,
},
Binary {
left: Box<Spanned<Expr>>,
op: BinaryOp,
right: Box<Spanned<Expr>>,
},
Unary {
op: UnaryOp,
operand: Box<Spanned<Expr>>,
},
List(Vec<Spanned<Expr>>),
Record(Vec<RecordField>),
Unsafe {
reason: Spanned<String>,
body: Box<Spanned<Expr>>,
},
If {
condition: Box<Spanned<Expr>>,
then_branch: Box<Spanned<Expr>>,
else_branch: Box<Spanned<Expr>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOp {
Add, Sub, Mul, Div,
Eq, NotEq, Lt, LtEq, Gt, GtEq,
And, Or, }
impl BinaryOp {
pub fn precedence(self) -> u8 {
match self {
BinaryOp::Or => 1,
BinaryOp::And => 2,
BinaryOp::Eq | BinaryOp::NotEq => 3,
BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq => 4,
BinaryOp::Add | BinaryOp::Sub => 5,
BinaryOp::Mul | BinaryOp::Div => 6,
}
}
pub fn name(self) -> &'static str {
match self {
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Eq => "==",
BinaryOp::NotEq => "!=",
BinaryOp::Lt => "<",
BinaryOp::LtEq => "<=",
BinaryOp::Gt => ">",
BinaryOp::GtEq => ">=",
BinaryOp::And => "&&",
BinaryOp::Or => "||",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOp {
Not, Neg, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Arg {
pub name: Option<Spanned<String>>,
pub value: Spanned<Expr>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordField {
pub name: Spanned<String>,
pub value: Spanned<Expr>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Literal {
String(String),
Number(f64),
Bool(bool),
}
#[derive(Debug, Clone)]
pub struct UnsafeBlockLocation {
pub line: usize,
pub column: usize,
pub reason: String,
}
pub fn find_unsafe_blocks(program: &Program) -> Vec<UnsafeBlockLocation> {
let mut results = Vec::new();
find_unsafe_in_statements(&program.statements, &mut results);
results
}
fn find_unsafe_in_statements(
statements: &[Spanned<Statement>],
results: &mut Vec<UnsafeBlockLocation>,
) {
for stmt in statements {
match &stmt.node {
Statement::ValDecl(decl) => {
find_unsafe_in_expr(&decl.value, results);
}
Statement::Module(m) => {
find_unsafe_in_statements(&m.body, results);
}
Statement::Assert(a) => {
find_unsafe_in_expr(&a.condition, results);
}
Statement::Import(_) | Statement::HclBlock(_) => {}
Statement::Unsafe(u) => {
results.push(UnsafeBlockLocation {
line: stmt.span.start_line,
column: stmt.span.start_col,
reason: u.reason.node.clone(),
});
find_unsafe_in_statements(&u.body, results);
}
}
}
}
fn find_unsafe_in_expr(expr: &Spanned<Expr>, results: &mut Vec<UnsafeBlockLocation>) {
match &expr.node {
Expr::Unsafe { reason, body } => {
results.push(UnsafeBlockLocation {
line: expr.span.start_line,
column: expr.span.start_col,
reason: reason.node.clone(),
});
find_unsafe_in_expr(body, results);
}
Expr::MemberAccess { object, .. } => {
find_unsafe_in_expr(object, results);
}
Expr::FuncCall { callee, args } => {
find_unsafe_in_expr(callee, results);
for arg in args {
find_unsafe_in_expr(&arg.value, results);
}
}
Expr::Lambda { body, .. } => {
find_unsafe_in_expr(body, results);
}
Expr::Binary { left, right, .. } => {
find_unsafe_in_expr(left, results);
find_unsafe_in_expr(right, results);
}
Expr::Unary { operand, .. } => {
find_unsafe_in_expr(operand, results);
}
Expr::List(elements) => {
for elem in elements {
find_unsafe_in_expr(elem, results);
}
}
Expr::Record(fields) => {
for field in fields {
find_unsafe_in_expr(&field.value, results);
}
}
Expr::If {
condition,
then_branch,
else_branch,
} => {
find_unsafe_in_expr(condition, results);
find_unsafe_in_expr(then_branch, results);
find_unsafe_in_expr(else_branch, results);
}
Expr::Literal(_) | Expr::Identifier(_) => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_unsafe_blocks_empty() {
let program = Program { statements: vec![] };
assert!(find_unsafe_blocks(&program).is_empty());
}
}