#![allow(missing_docs)]
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Type {
Number,
Bool,
Raster,
Unknown,
}
impl Type {
pub fn is_compatible(&self, other: &Type) -> bool {
matches!(
(self, other),
(Type::Number, Type::Number)
| (Type::Bool, Type::Bool)
| (Type::Raster, Type::Raster)
| (Type::Unknown, _)
| (_, Type::Unknown)
)
}
pub fn common_type(&self, other: &Type) -> Option<Type> {
match (self, other) {
(Type::Number, Type::Number) => Some(Type::Number),
(Type::Bool, Type::Bool) => Some(Type::Bool),
(Type::Raster, Type::Raster) => Some(Type::Raster),
(Type::Raster, Type::Number) | (Type::Number, Type::Raster) => Some(Type::Raster),
(Type::Unknown, t) | (t, Type::Unknown) => Some(*t),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Program {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Statement {
VariableDecl { name: String, value: Box<Expr> },
FunctionDecl {
name: String,
params: Vec<String>,
body: Box<Expr>,
},
Return(Box<Expr>),
Expr(Box<Expr>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expr {
Number(f64),
Band(usize),
Variable(String),
Binary {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
ty: Type,
},
Unary {
op: UnaryOp,
expr: Box<Expr>,
ty: Type,
},
Call {
name: String,
args: Vec<Expr>,
ty: Type,
},
Conditional {
condition: Box<Expr>,
then_expr: Box<Expr>,
else_expr: Box<Expr>,
ty: Type,
},
Block {
statements: Vec<Statement>,
result: Option<Box<Expr>>,
ty: Type,
},
ForLoop {
var: String,
start: Box<Expr>,
end: Box<Expr>,
body: Box<Expr>,
ty: Type,
},
}
impl Expr {
pub fn get_type(&self) -> Type {
match self {
Expr::Number(_) => Type::Number,
Expr::Band(_) => Type::Raster,
Expr::Variable(_) => Type::Unknown,
Expr::Binary { ty, .. }
| Expr::Unary { ty, .. }
| Expr::Call { ty, .. }
| Expr::Conditional { ty, .. }
| Expr::Block { ty, .. }
| Expr::ForLoop { ty, .. } => *ty,
}
}
pub fn set_type(&mut self, new_type: Type) {
match self {
Expr::Binary { ty, .. }
| Expr::Unary { ty, .. }
| Expr::Call { ty, .. }
| Expr::Conditional { ty, .. }
| Expr::Block { ty, .. }
| Expr::ForLoop { ty, .. } => *ty = new_type,
_ => {}
}
}
pub fn is_constant(&self) -> bool {
matches!(self, Expr::Number(_))
}
pub fn is_pure(&self) -> bool {
match self {
Expr::Number(_) | Expr::Band(_) | Expr::Variable(_) => true,
Expr::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
Expr::Unary { expr, .. } => expr.is_pure(),
Expr::Call { args, .. } => args.iter().all(|a| a.is_pure()),
Expr::Conditional {
condition,
then_expr,
else_expr,
..
} => condition.is_pure() && then_expr.is_pure() && else_expr.is_pure(),
Expr::Block { .. } | Expr::ForLoop { .. } => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOp {
Add,
Subtract,
Multiply,
Divide,
Modulo,
Power,
Equal,
NotEqual,
Less,
LessEqual,
Greater,
GreaterEqual,
And,
Or,
}
impl BinaryOp {
pub fn precedence(&self) -> u8 {
match self {
BinaryOp::Or => 1,
BinaryOp::And => 2,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual => 3,
BinaryOp::Add | BinaryOp::Subtract => 4,
BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => 5,
BinaryOp::Power => 6,
}
}
pub fn is_associative(&self) -> bool {
matches!(
self,
BinaryOp::Add | BinaryOp::Multiply | BinaryOp::And | BinaryOp::Or
)
}
pub fn is_commutative(&self) -> bool {
matches!(
self,
BinaryOp::Add
| BinaryOp::Multiply
| BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::And
| BinaryOp::Or
)
}
pub fn result_type(&self, left: Type, right: Type) -> Option<Type> {
match self {
BinaryOp::Add
| BinaryOp::Subtract
| BinaryOp::Multiply
| BinaryOp::Divide
| BinaryOp::Modulo
| BinaryOp::Power => left.common_type(&right),
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual => {
if left.is_compatible(&right) {
Some(Type::Bool)
} else {
None
}
}
BinaryOp::And | BinaryOp::Or => {
if left == Type::Bool && right == Type::Bool {
Some(Type::Bool)
} else {
None
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOp {
Negate,
Not,
Plus,
}
impl UnaryOp {
pub fn result_type(&self, operand: Type) -> Option<Type> {
match self {
UnaryOp::Negate | UnaryOp::Plus => {
if matches!(operand, Type::Number | Type::Raster) {
Some(operand)
} else {
None
}
}
UnaryOp::Not => {
if operand == Type::Bool {
Some(Type::Bool)
} else {
None
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_compatibility() {
assert!(Type::Number.is_compatible(&Type::Number));
assert!(Type::Unknown.is_compatible(&Type::Number));
assert!(!Type::Number.is_compatible(&Type::Bool));
}
#[test]
fn test_common_type() {
assert_eq!(Type::Number.common_type(&Type::Number), Some(Type::Number));
assert_eq!(Type::Raster.common_type(&Type::Number), Some(Type::Raster));
assert_eq!(Type::Number.common_type(&Type::Bool), None);
}
#[test]
fn test_expr_constant() {
let expr = Expr::Number(42.0);
assert!(expr.is_constant());
let expr = Expr::Band(1);
assert!(!expr.is_constant());
}
#[test]
fn test_binary_op_precedence() {
assert!(BinaryOp::Multiply.precedence() > BinaryOp::Add.precedence());
assert!(BinaryOp::Power.precedence() > BinaryOp::Multiply.precedence());
}
#[test]
fn test_binary_op_properties() {
assert!(BinaryOp::Add.is_commutative());
assert!(BinaryOp::Add.is_associative());
assert!(!BinaryOp::Subtract.is_commutative());
assert!(!BinaryOp::Divide.is_associative());
}
}