use laddu_expr::{
ColorPreset, DisplayColor, ExprGraph, ExprNodeKind, NodeSelector, NodeStyle, NodeStyleRule,
};
use pyo3::{
exceptions::{PyOSError, PyRuntimeError, PyTypeError, PyValueError},
prelude::*,
types::PyAny,
};
#[pyclass(
name = "ExprNodeKind",
module = "laddu",
frozen,
eq,
eq_int,
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PyExprNodeKind {
RealConst,
ComplexConst,
ScalarParam,
EventScalar,
EventP4Component,
Unary,
Binary,
NaryAdd,
NaryMul,
Complex,
Vector,
Matrix,
Component,
MatrixElement,
MatMul,
MatVec,
Dot,
Solve,
}
impl From<PyExprNodeKind> for ExprNodeKind {
fn from(value: PyExprNodeKind) -> Self {
match value {
PyExprNodeKind::RealConst => Self::RealConst,
PyExprNodeKind::ComplexConst => Self::ComplexConst,
PyExprNodeKind::ScalarParam => Self::ScalarParam,
PyExprNodeKind::EventScalar => Self::EventScalar,
PyExprNodeKind::EventP4Component => Self::EventP4Component,
PyExprNodeKind::Unary => Self::Unary,
PyExprNodeKind::Binary => Self::Binary,
PyExprNodeKind::NaryAdd => Self::NaryAdd,
PyExprNodeKind::NaryMul => Self::NaryMul,
PyExprNodeKind::Complex => Self::Complex,
PyExprNodeKind::Vector => Self::Vector,
PyExprNodeKind::Matrix => Self::Matrix,
PyExprNodeKind::Component => Self::Component,
PyExprNodeKind::MatrixElement => Self::MatrixElement,
PyExprNodeKind::MatMul => Self::MatMul,
PyExprNodeKind::MatVec => Self::MatVec,
PyExprNodeKind::Dot => Self::Dot,
PyExprNodeKind::Solve => Self::Solve,
}
}
}
#[pyclass(
name = "DisplayColor",
module = "laddu",
frozen,
get_all,
eq,
from_py_object
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PyDisplayColor {
pub red: u8,
pub green: u8,
pub blue: u8,
}
#[pymethods]
impl PyDisplayColor {
#[new]
fn new(red: u8, green: u8, blue: u8) -> Self {
Self { red, green, blue }
}
fn __repr__(&self) -> String {
format!("DisplayColor({}, {}, {})", self.red, self.green, self.blue)
}
}
impl From<PyDisplayColor> for DisplayColor {
fn from(value: PyDisplayColor) -> Self {
Self::rgb(value.red, value.green, value.blue)
}
}
#[pyclass(
name = "NodeStyle",
module = "laddu",
frozen,
get_all,
eq,
from_py_object
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PyNodeStyle {
pub foreground: Option<PyDisplayColor>,
pub fill: Option<PyDisplayColor>,
pub border: Option<PyDisplayColor>,
}
#[pymethods]
impl PyNodeStyle {
#[new]
#[pyo3(signature = (*, foreground=None, fill=None, border=None))]
fn new(
foreground: Option<PyDisplayColor>,
fill: Option<PyDisplayColor>,
border: Option<PyDisplayColor>,
) -> Self {
Self {
foreground,
fill,
border,
}
}
}
impl From<PyNodeStyle> for NodeStyle {
fn from(value: PyNodeStyle) -> Self {
Self {
foreground: value.foreground.map(Into::into),
fill: value.fill.map(Into::into),
border: value.border.map(Into::into),
}
}
}
#[pyclass(name = "NodeSelector", module = "laddu", frozen, eq, from_py_object)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PyNodeSelector {
pub(crate) inner: NodeSelector,
}
#[pymethods]
impl PyNodeSelector {
#[staticmethod]
#[pyo3(name = "any")]
fn any_selector() -> Self {
Self {
inner: NodeSelector::Any,
}
}
#[staticmethod]
fn kind(kind: PyExprNodeKind) -> Self {
Self {
inner: NodeSelector::Kind(kind.into()),
}
}
#[staticmethod]
fn name(name: String) -> Self {
Self {
inner: NodeSelector::Name(name),
}
}
#[staticmethod]
fn tag(tag: String) -> Self {
Self {
inner: NodeSelector::Tag(tag),
}
}
}
#[pyclass(
name = "NodeStyleRule",
module = "laddu",
frozen,
get_all,
eq,
from_py_object
)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PyNodeStyleRule {
pub selector: PyNodeSelector,
pub style: PyNodeStyle,
}
#[pymethods]
impl PyNodeStyleRule {
#[new]
fn new(selector: PyNodeSelector, style: PyNodeStyle) -> Self {
Self { selector, style }
}
}
impl From<PyNodeStyleRule> for NodeStyleRule {
fn from(value: PyNodeStyleRule) -> Self {
Self::new(value.selector.inner, value.style.into())
}
}
fn color_preset(colors: Option<&Bound<'_, PyAny>>) -> PyResult<Option<ColorPreset>> {
let Some(colors) = colors else {
return Ok(None);
};
if let Ok(value) = colors.extract::<String>() {
return match value.trim().to_ascii_lowercase().as_str() {
"none" => Ok(None),
"light" => Ok(Some(ColorPreset::Light)),
"dark" => Ok(Some(ColorPreset::Dark)),
_ => Err(PyValueError::new_err(
"colors must be 'light', 'dark', 'none', or None",
)),
};
}
Err(PyTypeError::new_err("colors must be a string or None"))
}
pub(crate) fn expression_equation(
graph: &ExprGraph,
colors: Option<&Bound<'_, PyAny>>,
style_rules: Option<Vec<PyNodeStyleRule>>,
) -> PyResult<String> {
let mut display = graph.display_equation();
if let Some(preset) = color_preset(colors)? {
display = display.with_preset(preset);
}
for rule in style_rules.unwrap_or_default() {
display = display.with_style_rule(rule.into());
}
Ok(display.to_string())
}
pub(crate) fn expression_latex(
graph: &ExprGraph,
colors: Option<&Bound<'_, PyAny>>,
style_rules: Option<Vec<PyNodeStyleRule>>,
) -> PyResult<String> {
let mut display = graph.display_latex();
if let Some(preset) = color_preset(colors)? {
display = display.with_preset(preset);
}
for rule in style_rules.unwrap_or_default() {
display = display.with_style_rule(rule.into());
}
Ok(display.to_string())
}
pub(crate) fn expression_tree(
graph: &ExprGraph,
colors: Option<&Bound<'_, PyAny>>,
expand_repeated: bool,
style_rules: Option<Vec<PyNodeStyleRule>>,
) -> PyResult<String> {
let mut display = graph.display_tree().expand_repeated(expand_repeated);
if let Some(preset) = color_preset(colors)? {
display = display.with_preset(preset);
}
for rule in style_rules.unwrap_or_default() {
display = display.with_style_rule(rule.into());
}
Ok(display.to_string())
}
pub(crate) fn expression_dot(
graph: &ExprGraph,
colors: Option<&Bound<'_, PyAny>>,
expand_repeated: bool,
style_rules: Option<Vec<PyNodeStyleRule>>,
) -> PyResult<String> {
let mut display = graph.display_dot().expand_repeated(expand_repeated);
if let Some(preset) = color_preset(colors)? {
display = display.with_preset(preset);
}
for rule in style_rules.unwrap_or_default() {
display = display.with_style_rule(rule.into());
}
Ok(display.to_string())
}
pub(crate) fn expression_svg(
graph: &ExprGraph,
path: &Path,
colors: Option<&Bound<'_, PyAny>>,
expand_repeated: bool,
style_rules: Option<Vec<PyNodeStyleRule>>,
) -> PyResult<()> {
let mut display = graph.display_dot().expand_repeated(expand_repeated);
if let Some(preset) = color_preset(colors)? {
display = display.with_preset(preset);
}
for rule in style_rules.unwrap_or_default() {
display = display.with_style_rule(rule.into());
}
let svg = display.render_svg().map_err(|error| {
PyRuntimeError::new_err(format!("failed to render expression graph as SVG: {error}"))
})?;
fs::write(path, svg).map_err(|error| {
PyOSError::new_err(format!(
"failed to write SVG to '{}': {error}",
path.display()
))
})
}
use std::{fs, path::Path};