use super::PythonAstParser;
use super::types::PythonValue;
use rez_next_common::RezCoreError;
use rustpython_ast::{BoolOp, CmpOp, Constant, Expr, Operator, UnaryOp};
use std::collections::HashMap;
impl PythonAstParser {
pub(super) fn evaluate_expression(&self, expr: &Expr) -> Result<PythonValue, RezCoreError> {
match expr {
Expr::Constant(constant) => self.evaluate_constant(&constant.value),
Expr::Name(name) => {
if let Some(value) = self.context.variables.get(name.id.as_str()) {
Ok(value.clone())
} else {
Err(RezCoreError::PackageParse(format!(
"Undefined variable: {}",
name.id
)))
}
}
Expr::BinOp(binop) => self.evaluate_binary_operation(binop),
Expr::UnaryOp(unaryop) => self.evaluate_unary_operation(unaryop),
Expr::Compare(compare) => self.evaluate_comparison(compare),
Expr::BoolOp(boolop) => self.evaluate_boolean_operation(boolop),
Expr::List(list) => self.evaluate_list(list),
Expr::Tuple(tuple) => self.evaluate_tuple(tuple),
Expr::Dict(dict) => self.evaluate_dict(dict),
Expr::Call(call) => self.evaluate_function_call(call),
Expr::Attribute(attr) => self.evaluate_attribute(attr),
Expr::Subscript(subscript) => self.evaluate_subscript(subscript),
Expr::IfExp(if_expression) => match self.evaluate_expression(&if_expression.test)? {
PythonValue::Boolean(true) => self.evaluate_expression(&if_expression.body),
PythonValue::Boolean(false) => self.evaluate_expression(&if_expression.orelse),
_ => Err(RezCoreError::PackageParse(
"Conditional expression is not statically boolean".to_string(),
)),
},
_ => Err(RezCoreError::PackageParse(format!(
"Unsupported package.py expression: {expr:?}"
))),
}
}
pub(super) fn evaluate_constant(
&self,
constant: &Constant,
) -> Result<PythonValue, RezCoreError> {
match constant {
Constant::Str(s) => Ok(PythonValue::String(s.clone())),
Constant::Int(i) => {
let int_val = i
.to_string()
.parse::<i64>()
.map_err(|_| RezCoreError::PackageParse("Integer too large".to_string()))?;
Ok(PythonValue::Integer(int_val))
}
Constant::Float(f) => Ok(PythonValue::Float(*f)),
Constant::Bool(b) => Ok(PythonValue::Boolean(*b)),
Constant::None => Ok(PythonValue::None),
_ => Err(RezCoreError::PackageParse(format!(
"Unsupported constant: {:?}",
constant
))),
}
}
fn evaluate_binary_operation(
&self,
binop: &rustpython_ast::ExprBinOp,
) -> Result<PythonValue, RezCoreError> {
let left = self.evaluate_expression(&binop.left)?;
let right = self.evaluate_expression(&binop.right)?;
match (&left, &binop.op, &right) {
(PythonValue::String(l), Operator::Add, PythonValue::String(r)) => {
Ok(PythonValue::String(format!("{}{}", l, r)))
}
(PythonValue::Integer(l), Operator::Add, PythonValue::Integer(r)) => {
Ok(PythonValue::Integer(l + r))
}
(PythonValue::Float(l), Operator::Add, PythonValue::Float(r)) => {
Ok(PythonValue::Float(l + r))
}
(PythonValue::List(l), Operator::Add, PythonValue::List(r)) => {
let mut result = l.clone();
result.extend(r.clone());
Ok(PythonValue::List(result))
}
(PythonValue::Integer(l), Operator::Sub, PythonValue::Integer(r)) => {
Ok(PythonValue::Integer(l - r))
}
(PythonValue::Integer(l), Operator::Mult, PythonValue::Integer(r)) => {
Ok(PythonValue::Integer(l * r))
}
(PythonValue::String(l), Operator::Mult, PythonValue::Integer(r)) => {
Ok(PythonValue::String(l.repeat(*r as usize)))
}
_ => Err(RezCoreError::PackageParse(format!(
"Unsupported binary operation: {left:?} {:?} {right:?}",
binop.op
))),
}
}
fn evaluate_unary_operation(
&self,
unaryop: &rustpython_ast::ExprUnaryOp,
) -> Result<PythonValue, RezCoreError> {
let operand = self.evaluate_expression(&unaryop.operand)?;
match (&unaryop.op, &operand) {
(UnaryOp::Not, PythonValue::Boolean(b)) => Ok(PythonValue::Boolean(!b)),
(UnaryOp::UAdd, PythonValue::Integer(i)) => Ok(PythonValue::Integer(*i)),
(UnaryOp::USub, PythonValue::Integer(i)) => Ok(PythonValue::Integer(-i)),
(UnaryOp::UAdd, PythonValue::Float(f)) => Ok(PythonValue::Float(*f)),
(UnaryOp::USub, PythonValue::Float(f)) => Ok(PythonValue::Float(-f)),
_ => Err(RezCoreError::PackageParse(format!(
"Unsupported unary operation: {:?} {operand:?}",
unaryop.op
))),
}
}
fn evaluate_comparison(
&self,
compare: &rustpython_ast::ExprCompare,
) -> Result<PythonValue, RezCoreError> {
let left = self.evaluate_expression(&compare.left)?;
if compare.ops.len() != compare.comparators.len() {
return Err(RezCoreError::PackageParse(
"Malformed comparison expression".to_string(),
));
}
for (op, comparator) in compare.ops.iter().zip(compare.comparators.iter()) {
let right = self.evaluate_expression(comparator)?;
let result = match (&left, op, &right) {
(PythonValue::Integer(l), CmpOp::Eq, PythonValue::Integer(r)) => *l == *r,
(PythonValue::String(l), CmpOp::Eq, PythonValue::String(r)) => l == r,
(PythonValue::String(l), CmpOp::NotEq, PythonValue::String(r)) => l != r,
(PythonValue::Boolean(l), CmpOp::Eq, PythonValue::Boolean(r)) => l == r,
(PythonValue::Integer(l), CmpOp::Lt, PythonValue::Integer(r)) => l < r,
(PythonValue::Integer(l), CmpOp::Gt, PythonValue::Integer(r)) => l > r,
_ => {
return Err(RezCoreError::PackageParse(format!(
"Unsupported comparison: {left:?} {op:?} {right:?}"
)));
}
};
if !result {
return Ok(PythonValue::Boolean(false));
}
}
Ok(PythonValue::Boolean(true))
}
fn evaluate_boolean_operation(
&self,
boolop: &rustpython_ast::ExprBoolOp,
) -> Result<PythonValue, RezCoreError> {
match &boolop.op {
BoolOp::And => {
for value in &boolop.values {
let result = self.evaluate_expression(value)?;
if let PythonValue::Boolean(false) = result {
return Ok(PythonValue::Boolean(false));
}
}
Ok(PythonValue::Boolean(true))
}
BoolOp::Or => {
for value in &boolop.values {
let result = self.evaluate_expression(value)?;
if let PythonValue::Boolean(true) = result {
return Ok(PythonValue::Boolean(true));
}
}
Ok(PythonValue::Boolean(false))
}
}
}
fn evaluate_list(&self, list: &rustpython_ast::ExprList) -> Result<PythonValue, RezCoreError> {
let mut result = Vec::new();
for elt in &list.elts {
result.push(self.evaluate_expression(elt)?);
}
Ok(PythonValue::List(result))
}
fn evaluate_tuple(
&self,
tuple: &rustpython_ast::ExprTuple,
) -> Result<PythonValue, RezCoreError> {
let mut result = Vec::new();
for elt in &tuple.elts {
result.push(self.evaluate_expression(elt)?);
}
Ok(PythonValue::List(result))
}
fn evaluate_dict(&self, dict: &rustpython_ast::ExprDict) -> Result<PythonValue, RezCoreError> {
let mut result = HashMap::new();
for (key, value) in dict.keys.iter().zip(dict.values.iter()) {
if let Some(key) = key {
let key_val = self.evaluate_expression(key)?;
let value_val = self.evaluate_expression(value)?;
if let PythonValue::String(key_str) = key_val {
result.insert(key_str, value_val);
} else {
return Err(RezCoreError::PackageParse(
"Dictionary keys must be strings".to_string(),
));
}
}
}
Ok(PythonValue::Dict(result))
}
fn evaluate_function_call(
&self,
call: &rustpython_ast::ExprCall,
) -> Result<PythonValue, RezCoreError> {
if let Expr::Attribute(attribute) = &*call.func {
if let Expr::Name(owner) = &*attribute.value
&& owner.id.as_str() == "os"
&& attribute.attr.as_str() == "getenv"
&& let Some(name) = call.args.first()
&& let PythonValue::String(name) = self.evaluate_expression(name)?
{
return Ok(match std::env::var(name) {
Ok(value) => PythonValue::String(value),
Err(_) => call
.args
.get(1)
.map(|default| self.evaluate_expression(default))
.transpose()?
.unwrap_or(PythonValue::None),
});
}
if attribute.attr.as_str() == "lower"
&& let PythonValue::String(value) = self.evaluate_expression(&attribute.value)?
{
return Ok(PythonValue::String(value.to_lowercase()));
}
if attribute.attr.as_str() == "format"
&& let PythonValue::String(value) = self.evaluate_expression(&attribute.value)?
{
return self.format_string(value, &call.args);
}
}
Err(RezCoreError::PackageParse(format!(
"Unsupported function call: {call:?}"
)))
}
fn format_string(
&self,
value: String,
arguments: &[Expr],
) -> Result<PythonValue, RezCoreError> {
const OPEN_BRACE: &str = "\u{e000}";
const CLOSE_BRACE: &str = "\u{e001}";
let mut formatted = value.replace("{{", OPEN_BRACE).replace("}}", CLOSE_BRACE);
for (index, argument) in arguments.iter().enumerate() {
let value = match self.evaluate_expression(argument)? {
PythonValue::String(value) => value,
PythonValue::Integer(value) => value.to_string(),
PythonValue::Float(value) => value.to_string(),
PythonValue::Boolean(value) => value.to_string(),
value => {
return Err(RezCoreError::PackageParse(format!(
"Unsupported format argument: {value:?}"
)));
}
};
formatted = formatted.replace(&format!("{{{index}}}"), &value);
}
Ok(PythonValue::String(
formatted.replace(OPEN_BRACE, "{").replace(CLOSE_BRACE, "}"),
))
}
fn evaluate_attribute(
&self,
attr: &rustpython_ast::ExprAttribute,
) -> Result<PythonValue, RezCoreError> {
if let Expr::Name(owner) = &*attr.value {
match (owner.id.as_str(), attr.attr.as_str()) {
("system", "platform") => {
let platform = if cfg!(windows) {
"windows"
} else if cfg!(target_os = "macos") {
"osx"
} else {
"linux"
};
return Ok(PythonValue::String(platform.to_string()));
}
("os", "name") => {
return Ok(PythonValue::String(
if cfg!(windows) { "nt" } else { "posix" }.to_string(),
));
}
("this", "version") => {
if let Some(version) = self.context.variables.get("version") {
return Ok(version.clone());
}
}
("this", "name") => {
if let Some(name) = self.context.variables.get("name") {
return Ok(name.clone());
}
}
_ => {}
}
}
if let Expr::Attribute(version) = &*attr.value
&& let Expr::Name(owner) = &*version.value
&& owner.id.as_str() == "this"
&& version.attr.as_str() == "version"
&& let Some(PythonValue::String(value)) = self.context.variables.get("version")
{
let component = match attr.attr.as_str() {
"major" => value.split('.').next(),
"minor" => value.split('.').nth(1),
"patch" => value.split('.').nth(2),
_ => None,
};
if let Some(component) = component {
return Ok(PythonValue::String(component.to_string()));
}
}
Err(RezCoreError::PackageParse(format!(
"Unsupported attribute access: {attr:?}"
)))
}
fn evaluate_subscript(
&self,
subscript: &rustpython_ast::ExprSubscript,
) -> Result<PythonValue, RezCoreError> {
Err(RezCoreError::PackageParse(format!(
"Unsupported subscript expression: {subscript:?}"
)))
}
}