lashlang 0.1.0-alpha.95

Lashlang: compact CodeAct language for model-authored REPL blocks in the lash agent runtime.
Documentation
use crate::ast::{AssignTarget, Expr, ListComprehensionClause};
use crate::parse_expression;
use crate::source::{canonical_assign_target_source, canonical_expression_source};

use super::{GraphRenderError, WorkflowListComprehensionClause, WorkflowNode};

pub(super) fn expression_text(expression: &Expr) -> String {
    canonical_expression_source(expression)
        .expect("an expression parsed from canonical source must remain sourceable")
}

pub(super) fn assign_target_text(target: &AssignTarget) -> String {
    canonical_assign_target_source(target)
        .expect("an assignment target parsed from canonical source must remain sourceable")
}

pub(super) fn workflow_clause(clause: &ListComprehensionClause) -> WorkflowListComprehensionClause {
    match clause {
        ListComprehensionClause::For { binding, iterable } => {
            WorkflowListComprehensionClause::For {
                binding: binding.to_string(),
                iterable: expression_text(iterable),
            }
        }
        ListComprehensionClause::If { condition } => WorkflowListComprehensionClause::If {
            condition: expression_text(condition),
        },
    }
}

pub(super) fn parse_expression_field(
    node: &WorkflowNode,
    field: &'static str,
    text: &str,
) -> Result<Expr, GraphRenderError> {
    parse_expression(text).map_err(|error| GraphRenderError::InvalidExpression {
        node_id: node.id.to_string(),
        field,
        message: error.to_string(),
    })
}

pub(super) fn parse_assignment_target_field(
    node: &WorkflowNode,
    field: &'static str,
    text: &str,
) -> Result<AssignTarget, GraphRenderError> {
    let expression = parse_expression(&format!("{text} = null")).map_err(|error| {
        GraphRenderError::InvalidAssignmentTarget {
            node_id: node.id.to_string(),
            field,
            message: error.to_string(),
        }
    })?;
    match expression {
        Expr::Assign { target, .. } => Ok(target),
        _ => Err(GraphRenderError::InvalidAssignmentTarget {
            node_id: node.id.to_string(),
            field,
            message: "expected an assignment target".to_string(),
        }),
    }
}

pub(super) fn parse_simple_binding_field(
    node: &WorkflowNode,
    field: &'static str,
    text: &str,
) -> Result<AssignTarget, GraphRenderError> {
    let target = parse_assignment_target_field(node, field, text)?;
    if !target.is_simple() {
        return invalid_payload(node, "this field requires a simple binding target");
    }
    Ok(target)
}

pub(super) fn parse_comprehension_clauses(
    node: &WorkflowNode,
    clauses: &[WorkflowListComprehensionClause],
) -> Result<Vec<ListComprehensionClause>, GraphRenderError> {
    clauses
        .iter()
        .map(|clause| match clause {
            WorkflowListComprehensionClause::For { binding, iterable } => {
                Ok(ListComprehensionClause::For {
                    binding: parse_simple_binding_field(node, "clause binding", binding)?.root,
                    iterable: parse_expression_field(node, "clause iterable", iterable)?,
                })
            }
            WorkflowListComprehensionClause::If { condition } => Ok(ListComprehensionClause::If {
                condition: parse_expression_field(node, "clause condition", condition)?,
            }),
        })
        .collect()
}

pub(super) fn with_assignment(
    node: &WorkflowNode,
    binding: &Option<String>,
    expression: Expr,
    must_be_simple: bool,
) -> Result<Expr, GraphRenderError> {
    match binding {
        Some(text) => {
            let target = parse_assignment_target_field(node, "binding", text)?;
            if must_be_simple && !target.is_simple() {
                return invalid_payload(node, "this node kind requires a simple binding target");
            }
            Ok(Expr::Assign {
                target,
                expr: Box::new(expression),
            })
        }
        None => Ok(expression),
    }
}

fn invalid_payload<T>(node: &WorkflowNode, message: &str) -> Result<T, GraphRenderError> {
    Err(GraphRenderError::InvalidNodePayload {
        node_id: node.id.to_string(),
        message: message.to_string(),
    })
}