blob: 12dcfee0b8946bbf66e680dc4556b3321ef24ffa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.druid.math.expr;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.druid.java.util.common.RE;
import org.apache.druid.testing.InitializedNullHandlingTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
*
*/
public class ParserTest extends InitializedNullHandlingTest
{
@Rule
public ExpectedException expectedException = ExpectedException.none();
VectorExprSanityTest.SettableVectorInputBinding emptyBinding = new VectorExprSanityTest.SettableVectorInputBinding(8);
@Test
public void testSimple()
{
String actual = Parser.parse("1", ExprMacroTable.nil()).toString();
String expected = "1";
Assert.assertEquals(expected, actual);
}
@Test
public void testParseConstants()
{
validateLiteral("null", null, null);
validateLiteral("'hello'", ExprType.STRING, "hello");
validateLiteral("'hello \\uD83E\\uDD18'", ExprType.STRING, "hello \uD83E\uDD18");
validateLiteral("1", ExprType.LONG, 1L);
validateLiteral("1.", ExprType.DOUBLE, 1.0, false);
validateLiteral("1.234", ExprType.DOUBLE, 1.234);
validateLiteral("1e10", ExprType.DOUBLE, 1.0E10, false);
validateLiteral("1e-10", ExprType.DOUBLE, 1.0E-10, false);
validateLiteral("1E10", ExprType.DOUBLE, 1.0E10, false);
validateLiteral("1E-10", ExprType.DOUBLE, 1.0E-10, false);
validateLiteral("1.E10", ExprType.DOUBLE, 1.0E10, false);
validateLiteral("1.E-10", ExprType.DOUBLE, 1.0E-10, false);
validateLiteral("1.e10", ExprType.DOUBLE, 1.0E10, false);
validateLiteral("1.e-10", ExprType.DOUBLE, 1.0E-10, false);
validateLiteral("1.1e10", ExprType.DOUBLE, 1.1E10, false);
validateLiteral("1.1e-10", ExprType.DOUBLE, 1.1E-10, false);
validateLiteral("1.1E10", ExprType.DOUBLE, 1.1E10);
validateLiteral("1.1E-10", ExprType.DOUBLE, 1.1E-10);
validateLiteral("Infinity", ExprType.DOUBLE, Double.POSITIVE_INFINITY);
validateLiteral("NaN", ExprType.DOUBLE, Double.NaN);
}
@Test
public void testSimpleUnaryOps1()
{
String actual = Parser.parse("-x", ExprMacroTable.nil()).toString();
String expected = "-x";
Assert.assertEquals(expected, actual);
actual = Parser.parse("!x", ExprMacroTable.nil()).toString();
expected = "!x";
Assert.assertEquals(expected, actual);
}
@Test
public void testSimpleUnaryOps2()
{
validateFlatten("-1", "-1", "-1");
validateFlatten("--1", "--1", "1");
validateFlatten("-1+2", "(+ -1 2)", "1");
validateFlatten("-1*2", "(* -1 2)", "-2");
validateFlatten("-1^2", "(^ -1 2)", "1");
}
@Test
public void testSimpleLogicalOps1()
{
validateParser("x>y", "(> x y)", ImmutableList.of("x", "y"));
validateParser("x<y", "(< x y)", ImmutableList.of("x", "y"));
validateParser("x<=y", "(<= x y)", ImmutableList.of("x", "y"));
validateParser("x>=y", "(>= x y)", ImmutableList.of("x", "y"));
validateParser("x==y", "(== x y)", ImmutableList.of("x", "y"));
validateParser("x!=y", "(!= x y)", ImmutableList.of("x", "y"));
validateParser("x && y", "(&& x y)", ImmutableList.of("x", "y"));
validateParser("x || y", "(|| x y)", ImmutableList.of("x", "y"));
}
@Test
public void testSimpleAdditivityOp1()
{
validateParser("x+y", "(+ x y)", ImmutableList.of("x", "y"));
validateParser("x-y", "(- x y)", ImmutableList.of("x", "y"));
}
@Test
public void testSimpleAdditivityOp2()
{
validateParser("x+y+z", "(+ (+ x y) z)", ImmutableList.of("x", "y", "z"));
validateParser("x+y-z", "(- (+ x y) z)", ImmutableList.of("x", "y", "z"));
validateParser("x-y+z", "(+ (- x y) z)", ImmutableList.of("x", "y", "z"));
validateParser("x-y-z", "(- (- x y) z)", ImmutableList.of("x", "y", "z"));
validateParser("x-y-x", "(- (- x y) x)", ImmutableList.of("x", "y"), ImmutableSet.of("x", "x_0", "y"));
}
@Test
public void testSimpleMultiplicativeOp1()
{
validateParser("x*y", "(* x y)", ImmutableList.of("x", "y"));
validateParser("x/y", "(/ x y)", ImmutableList.of("x", "y"));
validateParser("x%y", "(% x y)", ImmutableList.of("x", "y"));
}
@Test
public void testSimpleMultiplicativeOp2()
{
validateFlatten("1*2*3", "(* (* 1 2) 3)", "6");
validateFlatten("1*2/3", "(/ (* 1 2) 3)", "0");
validateFlatten("1/2*3", "(* (/ 1 2) 3)", "0");
validateFlatten("1/2/3", "(/ (/ 1 2) 3)", "0");
validateFlatten("1.0*2*3", "(* (* 1.0 2) 3)", "6.0");
validateFlatten("1.0*2/3", "(/ (* 1.0 2) 3)", "0.6666666666666666");
validateFlatten("1.0/2*3", "(* (/ 1.0 2) 3)", "1.5");
validateFlatten("1.0/2/3", "(/ (/ 1.0 2) 3)", "0.16666666666666666");
// partial
validateFlatten("1.0*2*x", "(* (* 1.0 2) x)", "(* 2.0 x)");
validateFlatten("1.0*2/x", "(/ (* 1.0 2) x)", "(/ 2.0 x)");
validateFlatten("1.0/2*x", "(* (/ 1.0 2) x)", "(* 0.5 x)");
validateFlatten("1.0/2/x", "(/ (/ 1.0 2) x)", "(/ 0.5 x)");
// not working yet
validateFlatten("1.0*x*3", "(* (* 1.0 x) 3)", "(* (* 1.0 x) 3)");
}
@Test
public void testSimpleCarrot1()
{
validateFlatten("1^2", "(^ 1 2)", "1");
}
@Test
public void testSimpleCarrot2()
{
validateFlatten("1^2^3", "(^ 1 (^ 2 3))", "1");
}
@Test
public void testMixed()
{
validateFlatten("1+2*3", "(+ 1 (* 2 3))", "7");
validateFlatten("1+(2*3)", "(+ 1 (* 2 3))", "7");
validateFlatten("(1+2)*3", "(* (+ 1 2) 3)", "9");
validateFlatten("1*2+3", "(+ (* 1 2) 3)", "5");
validateFlatten("(1*2)+3", "(+ (* 1 2) 3)", "5");
validateFlatten("1*(2+3)", "(* 1 (+ 2 3))", "5");
validateFlatten("1+2^3", "(+ 1 (^ 2 3))", "9");
validateFlatten("1+(2^3)", "(+ 1 (^ 2 3))", "9");
validateFlatten("(1+2)^3", "(^ (+ 1 2) 3)", "27");
validateFlatten("1^2+3", "(+ (^ 1 2) 3)", "4");
validateFlatten("(1^2)+3", "(+ (^ 1 2) 3)", "4");
validateFlatten("1^(2+3)", "(^ 1 (+ 2 3))", "1");
validateFlatten("1^2*3+4", "(+ (* (^ 1 2) 3) 4)", "7");
validateFlatten("-1^2*-3+-4", "(+ (* (^ -1 2) -3) -4)", "-7");
validateFlatten("max(3, 4)", "(max [3, 4])", "4");
validateFlatten("min(1, max(3, 4))", "(min [1, (max [3, 4])])", "1");
}
@Test
public void testIdentifiers()
{
validateParser("foo", "foo", ImmutableList.of("foo"), ImmutableSet.of());
validateParser("\"foo\"", "foo", ImmutableList.of("foo"), ImmutableSet.of());
validateParser("\"foo bar\"", "foo bar", ImmutableList.of("foo bar"), ImmutableSet.of());
validateParser("\"foo\\\"bar\"", "foo\"bar", ImmutableList.of("foo\"bar"), ImmutableSet.of());
}
@Test
public void testLiterals()
{
validateConstantExpression("\'foo\'", "foo");
validateConstantExpression("\'foo bar\'", "foo bar");
validateConstantExpression("\'föo bar\'", "föo bar");
validateConstantExpression("\'f\\u0040o bar\'", "f@o bar");
validateConstantExpression("\'f\\u000Ao \\'b\\\\\\\"ar\'", "f\no 'b\\\"ar");
}
@Test
public void testLiteralArraysHomogeneousElements()
{
validateConstantExpression("[1.0, 2.345]", new Double[]{1.0, 2.345});
validateConstantExpression("[1, 3]", new Long[]{1L, 3L});
validateConstantExpression("['hello', 'world']", new String[]{"hello", "world"});
}
@Test
public void testLiteralArraysHomogeneousOrNullElements()
{
validateConstantExpression("[1.0, null, 2.345]", new Double[]{1.0, null, 2.345});
validateConstantExpression("[null, 1, 3]", new Long[]{null, 1L, 3L});
validateConstantExpression("['hello', 'world', null]", new String[]{"hello", "world", null});
}
@Test
public void testLiteralArraysEmptyAndAllNullImplicitAreString()
{
validateConstantExpression("[]", new String[0]);
validateConstantExpression("[null, null, null]", new String[]{null, null, null});
}
@Test
public void testLiteralArraysImplicitTypedNumericMixed()
{
// implicit typed numeric arrays with mixed elements are doubles
validateConstantExpression("[1, null, 2000.0]", new Double[]{1.0, null, 2000.0});
validateConstantExpression("[1.0, null, 2000]", new Double[]{1.0, null, 2000.0});
}
@Test
public void testLiteralArraysExplicitTypedEmpties()
{
validateConstantExpression("<STRING>[]", new String[0]);
validateConstantExpression("<DOUBLE>[]", new Double[0]);
validateConstantExpression("<LONG>[]", new Long[0]);
}
@Test
public void testLiteralArraysExplicitAllNull()
{
validateConstantExpression("<DOUBLE>[null, null, null]", new Double[]{null, null, null});
validateConstantExpression("<LONG>[null, null, null]", new Long[]{null, null, null});
validateConstantExpression("<STRING>[null, null, null]", new String[]{null, null, null});
}
@Test
public void testLiteralArraysExplicitTypes()
{
validateConstantExpression("<DOUBLE>[1.0, null, 2000.0]", new Double[]{1.0, null, 2000.0});
validateConstantExpression("<LONG>[3, null, 4]", new Long[]{3L, null, 4L});
validateConstantExpression("<STRING>['foo', 'bar', 'baz']", new String[]{"foo", "bar", "baz"});
}
@Test
public void testLiteralArraysExplicitTypesMixedElements()
{
// explicit typed numeric arrays mixed numeric types should coerce to the correct explicit type
validateConstantExpression("<DOUBLE>[3, null, 4, 2.345]", new Double[]{3.0, null, 4.0, 2.345});
validateConstantExpression("<LONG>[1.0, null, 2000.0]", new Long[]{1L, null, 2000L});
// explicit typed string arrays should accept any literal and convert to string
validateConstantExpression("<STRING>['1', null, 2000, 1.1]", new String[]{"1", null, "2000", "1.1"});
}
@Test
public void testLiteralArrayImplicitStringParseException()
{
// implicit typed string array cannot handle literals thate are not null or string
expectedException.expect(RE.class);
expectedException.expectMessage("Failed to parse array: element 2000 is not a string");
validateConstantExpression("['1', null, 2000, 1.1]", new String[]{"1", null, "2000", "1.1"});
}
@Test
public void testLiteralArraysExplicitLongParseException()
{
// explicit typed long arrays only handle numeric types
expectedException.expect(RE.class);
expectedException.expectMessage("Failed to parse array element '2000' as a long");
validateConstantExpression("<LONG>[1, null, '2000']", new Long[]{1L, null, 2000L});
}
@Test
public void testLiteralArraysExplicitDoubleParseException()
{
// explicit typed double arrays only handle numeric types
expectedException.expect(RE.class);
expectedException.expectMessage("Failed to parse array element '2000.0' as a double");
validateConstantExpression("<DOUBLE>[1.0, null, '2000.0']", new Double[]{1.0, null, 2000.0});
}
@Test
public void testFunctions()
{
validateParser("sqrt(x)", "(sqrt [x])", ImmutableList.of("x"));
validateParser("if(cond,then,else)", "(if [cond, then, else])", ImmutableList.of("cond", "else", "then"));
validateParser("cast(x, 'STRING')", "(cast [x, STRING])", ImmutableList.of("x"));
validateParser("cast(x, 'LONG')", "(cast [x, LONG])", ImmutableList.of("x"));
validateParser("cast(x, 'DOUBLE')", "(cast [x, DOUBLE])", ImmutableList.of("x"));
validateParser(
"cast(x, 'STRING_ARRAY')",
"(cast [x, STRING_ARRAY])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"cast(x, 'LONG_ARRAY')",
"(cast [x, LONG_ARRAY])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"cast(x, 'DOUBLE_ARRAY')",
"(cast [x, DOUBLE_ARRAY])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"array_length(x)",
"(array_length [x])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"array_concat(x, y)",
"(array_concat [x, y])",
ImmutableList.of("x", "y"),
ImmutableSet.of(),
ImmutableSet.of("x", "y")
);
validateParser(
"array_append(x, y)",
"(array_append [x, y])",
ImmutableList.of("x", "y"),
ImmutableSet.of("y"),
ImmutableSet.of("x")
);
validateFlatten("sqrt(4)", "(sqrt [4])", "2.0");
validateFlatten("array_concat([1, 2], [3, 4])", "(array_concat [[1, 2], [3, 4]])", "[1, 2, 3, 4]");
}
@Test
public void testApplyFunctions()
{
validateParser(
"map(() -> 1, x)",
"(map ([] -> 1), [x])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"map((x) -> x + 1, x)",
"(map ([x] -> (+ x 1)), [x])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"x + map((x) -> x + 1, y)",
"(+ x (map ([x] -> (+ x 1)), [y]))",
ImmutableList.of("x", "y"),
ImmutableSet.of("x"),
ImmutableSet.of("y")
);
validateParser(
"x + map((x) -> x + 1, x)",
"(+ x (map ([x] -> (+ x 1)), [x]))",
ImmutableList.of("x"),
ImmutableSet.of("x"),
ImmutableSet.of("x_0")
);
validateParser(
"map((x) -> concat(x, y), z)",
"(map ([x] -> (concat [x, y])), [z])",
ImmutableList.of("y", "z"),
ImmutableSet.of("y"),
ImmutableSet.of("z")
);
// 'y' is accumulator, and currently unknown
validateParser(
"fold((x, acc) -> acc + x, x, y)",
"(fold ([x, acc] -> (+ acc x)), [x, y])",
ImmutableList.of("x", "y"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"fold((x, acc) -> acc + x, map((x) -> x + 1, x), y)",
"(fold ([x, acc] -> (+ acc x)), [(map ([x] -> (+ x 1)), [x]), y])",
ImmutableList.of("x", "y"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateParser(
"array_append(z, fold((x, acc) -> acc + x, map((x) -> x + 1, x), y))",
"(array_append [z, (fold ([x, acc] -> (+ acc x)), [(map ([x] -> (+ x 1)), [x]), y])])",
ImmutableList.of("x", "y", "z"),
ImmutableSet.of(),
ImmutableSet.of("x", "z")
);
validateParser(
"map(z -> z + 1, array_append(z, fold((x, acc) -> acc + x, map((x) -> x + 1, x), y)))",
"(map ([z] -> (+ z 1)), [(array_append [z, (fold ([x, acc] -> (+ acc x)), [(map ([x] -> (+ x 1)), [x]), y])])])",
ImmutableList.of("x", "y", "z"),
ImmutableSet.of(),
ImmutableSet.of("x", "z")
);
validateParser(
"array_append(map(z -> z + 1, array_append(z, fold((x, acc) -> acc + x, map((x) -> x + 1, x), y))), a)",
"(array_append [(map ([z] -> (+ z 1)), [(array_append [z, (fold ([x, acc] -> (+ acc x)), [(map ([x] -> (+ x 1)), [x]), y])])]), a])",
ImmutableList.of("x", "y", "a", "z"),
ImmutableSet.of("a"),
ImmutableSet.of("x", "z")
);
validateFlatten("map((x) -> x + 1, [1, 2, 3, 4])", "(map ([x] -> (+ x 1)), [[1, 2, 3, 4]])", "[2, 3, 4, 5]");
validateFlatten(
"map((x) -> x + z, [1, 2, 3, 4])",
"(map ([x] -> (+ x z)), [[1, 2, 3, 4]])",
"(map ([x] -> (+ x z)), [[1, 2, 3, 4]])"
);
}
@Test
public void testApplyUnapplied()
{
validateApplyUnapplied("x + 1", "(+ x 1)", "(+ x 1)", ImmutableList.of());
validateApplyUnapplied("x + 1", "(+ x 1)", "(+ x 1)", ImmutableList.of("z"));
validateApplyUnapplied("x + y", "(+ x y)", "(map ([x] -> (+ x y)), [x])", ImmutableList.of("x"));
validateApplyUnapplied(
"x + y",
"(+ x y)",
"(cartesian_map ([x, y] -> (+ x y)), [x, y])",
ImmutableList.of("x", "y")
);
validateApplyUnapplied(
"map(x -> x + y, x)",
"(map ([x] -> (+ x y)), [x])",
"(cartesian_map ([x, y] -> (+ x y)), [x, y])",
ImmutableList.of("y")
);
validateApplyUnapplied(
"map(x -> x + 1, x + 1)",
"(map ([x] -> (+ x 1)), [(+ x 1)])",
"(map ([x] -> (+ x 1)), [(map ([x] -> (+ x 1)), [x])])",
ImmutableList.of("x")
);
validateApplyUnapplied(
"fold((x, acc) -> acc + x + y, x, 0)",
"(fold ([x, acc] -> (+ (+ acc x) y)), [x, 0])",
"(cartesian_fold ([x, y, acc] -> (+ (+ acc x) y)), [x, y, 0])",
ImmutableList.of("y")
);
validateApplyUnapplied(
"z + fold((x, acc) -> acc + x + y, x, 0)",
"(+ z (fold ([x, acc] -> (+ (+ acc x) y)), [x, 0]))",
"(+ z (cartesian_fold ([x, y, acc] -> (+ (+ acc x) y)), [x, y, 0]))",
ImmutableList.of("y")
);
validateApplyUnapplied(
"z + fold((x, acc) -> acc + x + y, x, 0)",
"(+ z (fold ([x, acc] -> (+ (+ acc x) y)), [x, 0]))",
"(map ([z] -> (+ z (cartesian_fold ([x, y, acc] -> (+ (+ acc x) y)), [x, y, 0]))), [z])",
ImmutableList.of("y", "z")
);
validateApplyUnapplied(
"array_to_string(concat(x, 'hello'), ',')",
"(array_to_string [(concat [x, hello]), ,])",
"(array_to_string [(map ([x] -> (concat [x, hello])), [x]), ,])",
ImmutableList.of("x", "y")
);
validateApplyUnapplied(
"cast(x, 'LONG')",
"(cast [x, LONG])",
"(map ([x] -> (cast [x, LONG])), [x])",
ImmutableList.of("x")
);
validateApplyUnapplied(
"cartesian_map((x,y) -> x + y, x, y)",
"(cartesian_map ([x, y] -> (+ x y)), [x, y])",
"(cartesian_map ([x, y] -> (+ x y)), [x, y])",
ImmutableList.of("y")
);
validateApplyUnapplied(
"cast(x, 'LONG_ARRAY')",
"(cast [x, LONG_ARRAY])",
"(cast [x, LONG_ARRAY])",
ImmutableList.of("x")
);
validateApplyUnapplied(
"case_searched((x == 'b'),'b',(x == 'g'),'g','Other')",
"(case_searched [(== x b), b, (== x g), g, Other])",
"(map ([x] -> (case_searched [(== x b), b, (== x g), g, Other])), [x])",
ImmutableList.of("x")
);
}
@Test
public void testFoldUnapplied()
{
validateFoldUnapplied("x + __acc", "(+ x __acc)", "(+ x __acc)", ImmutableList.of(), "__acc");
validateFoldUnapplied("x + __acc", "(+ x __acc)", "(+ x __acc)", ImmutableList.of("z"), "__acc");
validateFoldUnapplied(
"x + __acc",
"(+ x __acc)",
"(fold ([x, __acc] -> (+ x __acc)), [x, __acc])",
ImmutableList.of("x"),
"__acc"
);
validateFoldUnapplied(
"x + y + __acc",
"(+ (+ x y) __acc)",
"(cartesian_fold ([x, y, __acc] -> (+ (+ x y) __acc)), [x, y, __acc])",
ImmutableList.of("x", "y"),
"__acc"
);
validateFoldUnapplied(
"__acc + z + fold((x, acc) -> acc + x + y, x, 0)",
"(+ (+ __acc z) (fold ([x, acc] -> (+ (+ acc x) y)), [x, 0]))",
"(fold ([z, __acc] -> (+ (+ __acc z) (fold ([x, acc] -> (+ (+ acc x) y)), [x, 0]))), [z, __acc])",
ImmutableList.of("z"),
"__acc"
);
validateFoldUnapplied(
"__acc + z + fold((x, acc) -> acc + x + y, x, 0)",
"(+ (+ __acc z) (fold ([x, acc] -> (+ (+ acc x) y)), [x, 0]))",
"(fold ([z, __acc] -> (+ (+ __acc z) (cartesian_fold ([x, y, acc] -> (+ (+ acc x) y)), [x, y, 0]))), [z, __acc])",
ImmutableList.of("y", "z"),
"__acc"
);
validateFoldUnapplied(
"__acc + fold((x, acc) -> x + y + acc, x, __acc)",
"(+ __acc (fold ([x, acc] -> (+ (+ x y) acc)), [x, __acc]))",
"(+ __acc (cartesian_fold ([x, y, acc] -> (+ (+ x y) acc)), [x, y, __acc]))",
ImmutableList.of("y"),
"__acc"
);
}
@Test
public void testUniquify()
{
validateParser("x-x", "(- x x)", ImmutableList.of("x"), ImmutableSet.of("x", "x_0"));
validateParser(
"x - x + x",
"(+ (- x x) x)",
ImmutableList.of("x"),
ImmutableSet.of("x", "x_0", "x_1")
);
validateParser(
"map((x) -> x + x, x)",
"(map ([x] -> (+ x x)), [x])",
ImmutableList.of("x"),
ImmutableSet.of(),
ImmutableSet.of("x")
);
validateApplyUnapplied(
"x + x",
"(+ x x)",
"(map ([x] -> (+ x x)), [x])",
ImmutableList.of("x")
);
validateApplyUnapplied(
"x + x + x",
"(+ (+ x x) x)",
"(map ([x] -> (+ (+ x x) x)), [x])",
ImmutableList.of("x")
);
// heh
validateApplyUnapplied(
"x + x + x + y + y + y + y + z + z + z",
"(+ (+ (+ (+ (+ (+ (+ (+ (+ x x) x) y) y) y) y) z) z) z)",
"(cartesian_map ([x, y, z] -> (+ (+ (+ (+ (+ (+ (+ (+ (+ x x) x) y) y) y) y) z) z) z)), [x, y, z])",
ImmutableList.of("x", "y", "z")
);
}
private void validateLiteral(String expr, ExprType type, Object expected)
{
validateLiteral(expr, type, expected, true);
}
private void validateLiteral(String expr, ExprType type, Object expected, boolean roundTrip)
{
Expr parsed = Parser.parse(expr, ExprMacroTable.nil(), false);
Expr parsedFlat = Parser.parse(expr, ExprMacroTable.nil(), true);
Assert.assertTrue(parsed.isLiteral());
Assert.assertTrue(parsedFlat.isLiteral());
Assert.assertEquals(type, parsed.getOutputType(emptyBinding));
Assert.assertEquals(type, parsedFlat.getOutputType(emptyBinding));
Assert.assertEquals(expected, parsed.getLiteralValue());
Assert.assertEquals(expected, parsedFlat.getLiteralValue());
if (roundTrip) {
Assert.assertEquals(expr, parsed.stringify());
Assert.assertEquals(expr, parsedFlat.stringify());
}
}
private void validateFlatten(String expression, String withoutFlatten, String withFlatten)
{
Expr notFlat = Parser.parse(expression, ExprMacroTable.nil(), false);
Expr flat = Parser.parse(expression, ExprMacroTable.nil(), true);
Assert.assertEquals(expression, withoutFlatten, notFlat.toString());
Assert.assertEquals(expression, withFlatten, flat.toString());
Expr notFlatRoundTrip = Parser.parse(notFlat.stringify(), ExprMacroTable.nil(), false);
Expr flatRoundTrip = Parser.parse(flat.stringify(), ExprMacroTable.nil(), true);
Assert.assertEquals(expression, withoutFlatten, notFlatRoundTrip.toString());
Assert.assertEquals(expression, withFlatten, flatRoundTrip.toString());
Assert.assertEquals(notFlat.stringify(), notFlatRoundTrip.stringify());
Assert.assertEquals(flat.stringify(), flatRoundTrip.stringify());
}
private void validateParser(String expression, String expected, List<String> identifiers)
{
validateParser(expression, expected, identifiers, ImmutableSet.copyOf(identifiers), Collections.emptySet());
}
private void validateParser(String expression, String expected, List<String> identifiers, Set<String> scalars)
{
validateParser(expression, expected, identifiers, scalars, Collections.emptySet());
}
private void validateParser(
String expression,
String expected,
List<String> identifiers,
Set<String> scalars,
Set<String> arrays
)
{
final Expr parsed = Parser.parse(expression, ExprMacroTable.nil());
final Expr.BindingAnalysis deets = parsed.analyzeInputs();
Assert.assertEquals(expression, expected, parsed.toString());
Assert.assertEquals(expression, identifiers, deets.getRequiredBindingsList());
Assert.assertEquals(expression, scalars, deets.getScalarVariables());
Assert.assertEquals(expression, arrays, deets.getArrayVariables());
final Expr parsedNoFlatten = Parser.parse(expression, ExprMacroTable.nil(), false);
final Expr roundTrip = Parser.parse(parsedNoFlatten.stringify(), ExprMacroTable.nil());
Assert.assertEquals(parsed.stringify(), roundTrip.stringify());
final Expr.BindingAnalysis roundTripDeets = roundTrip.analyzeInputs();
Assert.assertEquals(expression, identifiers, roundTripDeets.getRequiredBindingsList());
Assert.assertEquals(expression, scalars, roundTripDeets.getScalarVariables());
Assert.assertEquals(expression, arrays, roundTripDeets.getArrayVariables());
}
private void validateApplyUnapplied(
String expression,
String unapplied,
String applied,
List<String> identifiers
)
{
final Expr parsed = Parser.parse(expression, ExprMacroTable.nil());
Expr.BindingAnalysis deets = parsed.analyzeInputs();
Parser.validateExpr(parsed, deets);
final Expr transformed = Parser.applyUnappliedBindings(parsed, deets, identifiers);
Assert.assertEquals(expression, unapplied, parsed.toString());
Assert.assertEquals(applied, applied, transformed.toString());
final Expr parsedNoFlatten = Parser.parse(expression, ExprMacroTable.nil(), false);
final Expr parsedRoundTrip = Parser.parse(parsedNoFlatten.stringify(), ExprMacroTable.nil());
Expr.BindingAnalysis roundTripDeets = parsedRoundTrip.analyzeInputs();
Parser.validateExpr(parsedRoundTrip, roundTripDeets);
final Expr transformedRoundTrip = Parser.applyUnappliedBindings(parsedRoundTrip, roundTripDeets, identifiers);
Assert.assertEquals(expression, unapplied, parsedRoundTrip.toString());
Assert.assertEquals(applied, applied, transformedRoundTrip.toString());
Assert.assertEquals(parsed.stringify(), parsedRoundTrip.stringify());
Assert.assertEquals(transformed.stringify(), transformedRoundTrip.stringify());
}
private void validateFoldUnapplied(
String expression,
String unapplied,
String applied,
List<String> identifiers,
String accumulator
)
{
final Expr parsed = Parser.parse(expression, ExprMacroTable.nil());
Expr.BindingAnalysis deets = parsed.analyzeInputs();
Parser.validateExpr(parsed, deets);
final Expr transformed = Parser.foldUnappliedBindings(parsed, deets, identifiers, accumulator);
Assert.assertEquals(expression, unapplied, parsed.toString());
Assert.assertEquals(applied, applied, transformed.toString());
final Expr parsedNoFlatten = Parser.parse(expression, ExprMacroTable.nil(), false);
final Expr parsedRoundTrip = Parser.parse(parsedNoFlatten.stringify(), ExprMacroTable.nil());
Expr.BindingAnalysis roundTripDeets = parsedRoundTrip.analyzeInputs();
Parser.validateExpr(parsedRoundTrip, roundTripDeets);
final Expr transformedRoundTrip = Parser.foldUnappliedBindings(parsedRoundTrip, roundTripDeets, identifiers, accumulator);
Assert.assertEquals(expression, unapplied, parsedRoundTrip.toString());
Assert.assertEquals(applied, applied, transformedRoundTrip.toString());
Assert.assertEquals(parsed.stringify(), parsedRoundTrip.stringify());
Assert.assertEquals(transformed.stringify(), transformedRoundTrip.stringify());
}
private void validateConstantExpression(String expression, Object expected)
{
Expr parsed = Parser.parse(expression, ExprMacroTable.nil());
Assert.assertEquals(
expression,
expected,
parsed.eval(InputBindings.withMap(ImmutableMap.of())).value()
);
final Expr parsedNoFlatten = Parser.parse(expression, ExprMacroTable.nil(), false);
Expr parsedRoundTrip = Parser.parse(parsedNoFlatten.stringify(), ExprMacroTable.nil());
Assert.assertEquals(
expression,
expected,
parsedRoundTrip.eval(InputBindings.withMap(ImmutableMap.of())).value()
);
Assert.assertEquals(parsed.stringify(), parsedRoundTrip.stringify());
}
private void validateConstantExpression(String expression, Object[] expected)
{
Expr parsed = Parser.parse(expression, ExprMacroTable.nil());
Object evaluated = parsed.eval(InputBindings.withMap(ImmutableMap.of())).value();
Assert.assertArrayEquals(
expression,
expected,
(Object[]) evaluated
);
Assert.assertEquals(expected.getClass(), evaluated.getClass());
final Expr parsedNoFlatten = Parser.parse(expression, ExprMacroTable.nil(), false);
Expr roundTrip = Parser.parse(parsedNoFlatten.stringify(), ExprMacroTable.nil());
Assert.assertArrayEquals(
expression,
expected,
(Object[]) roundTrip.eval(InputBindings.withMap(ImmutableMap.of())).value()
);
Assert.assertEquals(parsed.stringify(), roundTrip.stringify());
}
}