use super::{
check_ok_needed_outputs, gather_operands, next_node_specific_function, ParseNode,
ParseNodeFunctional,
};
use crate::ast_node::{ASTContext, Node, TokenSlice};
use crate::operations::Operation;
use crate::syntax_error::SyntaxError;
use crate::tokens::Token;
use crate::types::{FunctionSignature, IJType};
use anyhow::Result;
use std::rc::Rc;
pub struct Reduction;
impl ParseNode for Reduction {
fn next_node(
_op: Token,
slice: TokenSlice,
context: &mut ASTContext,
) -> Result<(Rc<Node>, TokenSlice)> {
let (function, rest) =
next_node_specific_function(slice, context, FunctionSignature::number_function(2))?;
let signature = function.output_type.extract_signature().unwrap();
let output_number_type = signature.output.extract_number_type().unwrap_or_default();
let input_number_type = signature.input[0].extract_number_type().unwrap_or_default();
let (operands, rest) =
gather_operands(vec![vec![IJType::Tensor(input_number_type)]], rest, context)?;
let operand = operands.into_iter().next().unwrap();
Ok((
Rc::new(Node::new(
Operation::Reduce,
vec![function.output_type.clone(), operand.output_type.clone()],
IJType::Number(output_number_type),
vec![function, operand],
context,
)?),
rest,
))
}
}
impl ParseNodeFunctional for Reduction {
fn next_node_functional_impl(
_op: Token,
slice: TokenSlice,
context: &mut ASTContext,
needed_outputs: Option<&[IJType]>,
) -> Result<(Vec<Rc<Node>>, TokenSlice)> {
let actual_outputs = IJType::Number(None);
if !check_ok_needed_outputs(needed_outputs, &actual_outputs) {
return Err(SyntaxError::FunctionSignatureMismatch(
format!("{:?}", needed_outputs),
format!("{:?}", actual_outputs),
)
.into());
}
let (function, rest) = next_node_specific_function(
slice.move_start(1)?,
context,
FunctionSignature::number_function(2),
)?;
let signature = function.output_type.extract_signature().unwrap();
let output_number_type = signature.output.extract_number_type().unwrap_or_default();
let input_number_type = signature.input[0].extract_number_type().unwrap_or_default();
let node = Rc::new(Node::new(
Operation::Reduce,
vec![function.output_type.clone()],
IJType::Function(FunctionSignature::new(
vec![IJType::Tensor(input_number_type)],
IJType::Number(output_number_type),
)),
vec![function],
context,
)?);
Ok((vec![node], rest))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::test_utils::tokenize_str_no_context;
use crate::parser::{parse_str, parse_str_no_context};
#[test]
fn test_reduce_plus_with_tensor() {
let result = parse_str_no_context("/+ [1,2]");
assert!(result.is_ok());
let (node, _) = result.unwrap();
assert_eq!(node.op, Operation::Reduce);
assert_eq!(
node.input_types,
vec![IJType::number_function(2), IJType::Tensor(None)]
);
assert_eq!(node.output_type, IJType::Number(None));
}
#[test]
fn test_reduce_with_function_and_tensor() {
let mut context = ASTContext::new();
let var_declaration = parse_str("var f: Fn(N,N->N)", &mut context);
assert!(var_declaration.is_ok());
let result = parse_str("/f [1,2]", &mut context);
assert!(result.is_ok());
let node = result.unwrap();
assert_eq!(node.op, Operation::Reduce);
assert_eq!(
node.input_types,
vec![IJType::number_function(2), IJType::Tensor(None)]
);
assert_eq!(node.output_type, IJType::Number(None));
}
#[test]
fn test_next_node_functional() -> Result<()> {
let (mut context, slice) = tokenize_str_no_context("/+ [1,2]")?;
let result = Reduction::next_node_functional_impl(
Token::Reduction,
slice,
&mut context,
Some(&[IJType::Number(None)]),
)?;
println!("{:?}", result);
Ok(())
}
#[test]
fn test_reduction_number_type() -> Result<()> {
let mut context = ASTContext::new();
parse_str("var f: Fn(N<a>,N<a>->N<a>)", &mut context)?;
let node = parse_str("/f [1,2]<a>", &mut context)?;
assert_eq!(node.op, Operation::Reduce);
assert_eq!(node.output_type, IJType::Number(Some("a".to_string())));
let mut context = ASTContext::new();
parse_str("var f: Fn(N<a>,N<a>->N<b>)", &mut context)?;
let node = parse_str("/f [1,2]<a>", &mut context)?;
assert_eq!(node.op, Operation::Reduce);
assert_eq!(node.output_type, IJType::Number(Some("b".to_string())));
Ok(())
}
#[test]
fn test_reduction_functional_number_type() -> Result<()> {
let mut context = ASTContext::new();
parse_str("var f: Fn(N<a>,N<a>->N<a>)", &mut context)?;
let node = parse_str("~/f", &mut context)?;
assert_eq!(node.op, Operation::Reduce);
assert_eq!(
node.output_type,
IJType::Function(FunctionSignature::new(
vec![IJType::Tensor(Some("a".to_string()))],
IJType::Number(Some("a".to_string()))
))
);
let mut context = ASTContext::new();
parse_str("var f: Fn(N<a>,N<a>->N<b>)", &mut context)?;
let node = parse_str("~/f", &mut context)?;
assert_eq!(node.op, Operation::Reduce);
assert_eq!(
node.output_type,
IJType::Function(FunctionSignature::new(
vec![IJType::Tensor(Some("a".to_string()))],
IJType::Number(Some("b".to_string()))
))
);
Ok(())
}
#[test]
fn test_with_composition() -> Result<()> {
let (node, _) = parse_str_no_context("@(/+, -, +) [1,2] [3,4]")?;
assert_eq!(node.op, Operation::FunctionComposition(3));
assert_eq!(node.output_type, IJType::Number(None));
Ok(())
}
}