public void CanEvaluatePrimitives()
        {
            var env = new StandardEnvironment();

            env.DefineValue("foo", "foo");
            env.DefineValue("_foo_bar", "_foo_bar");

            // Symbols
            Assert.AreEqual("foo", Evaluate("foo", env));
            Assert.AreEqual("foo", Evaluate("  foo  ", env));
            Assert.AreEqual("_foo_bar", Evaluate("_foo_bar", env));

            // Strings
            Assert.AreEqual("foo'bar", Evaluate("'foo''bar'", env));
            Assert.AreEqual("foo\"bar\"\n\'/\\", Evaluate(@"""foo\""bar\""\n\'\/\\""", env));

            // Numbers
            Assert.AreEqual(123, Evaluate("123"));
            Assert.AreEqual(1.23, Evaluate("1.23"));
            Assert.AreEqual(2.7e-5, Evaluate("2.7e-5"));

            // null and Booleans
            Assert.AreEqual(null, Evaluate("null"));
            Assert.AreEqual(false, Evaluate("false"));
            Assert.AreEqual(true, Evaluate("true"));

            // Excessive parens...
            Assert.AreEqual(42, Evaluate("(((23+19)))"));
        }
        public void CanEvaluateConditionalOperator()
        {
            Assert.AreEqual("T", Evaluate("true ? 'T' : 'F'"));
            Assert.AreEqual("F", Evaluate("false ? 'T' : 'F'"));
            Assert.AreEqual(null, Evaluate("null ? 'T' : 'F'"));

            var env = new StandardEnvironment();

            env.DefineValue("xyzzy", "Magic");
            env.DefineValue("d", 5);

            Assert.AreEqual(5, Evaluate("xyzzy ? length(xyzzy) : -1", env));
            Assert.AreEqual("Fri", Evaluate("d=0 ? 'Sun' : d=1 ? 'Mon' : d=2 ? 'Tue' : d=3 ? 'Wed' : d=4 ? 'Thu' : d=5 ? 'Fri' : d=6 ? 'Sat' : null", env));
        }
        public void CanEvaluateUnaryOps()
        {
            Assert.AreEqual(5, Evaluate("+5"));
            Assert.AreEqual(-5, Evaluate("+-5"));
            Assert.AreEqual(25, Evaluate("+ + +25"));
            Assert.AreEqual(-0.2, Evaluate(" - 0.2 "));
            Assert.AreEqual(-0.2, Evaluate("- - -0.2"));
            Assert.AreEqual(-1.2e-03, Evaluate("-1.2e-03"));

            Assert.AreEqual(false, Evaluate("not true"));
            Assert.AreEqual(true, Evaluate("not not true"));
            Assert.AreEqual(true, Evaluate("not not 5"));

            var env = new StandardEnvironment();

            env.DefineValue("foo", 42.0);

            Assert.AreEqual(42.0, Evaluate("foo", env));
            Assert.AreEqual(false, Evaluate("not foo", env));
            Assert.AreEqual(true, Evaluate("not not foo", env));
            Assert.AreEqual(false, Evaluate("not not not foo", env));

            Assert.AreEqual(5, Evaluate("+ +5"));
            Catch(() => Evaluate("++5"));

            Assert.AreEqual(25, Evaluate("- - 25"));
            Catch(() => Evaluate("-- 25"));             // -- is one token
        }
        public void CanLookupPrecedence()
        {
            // Lookup precedence for unqualified names:
            //   defined value < standard function < field value
            // Qualified names are always looked up on a row of fields
            // defined with the qualifier. CONCAT is a standard function.

            var          env       = new StandardEnvironment();
            var          row       = new ConstantValueTestRow("row", "CONCAT", "FOO");
            const string qualifier = "qualifier";

            Assert.Catch <EvaluationException>(() => env.Lookup("FOO", null));
            Assert.IsInstanceOf <Function>(env.Lookup("CONCAT", null));
            Assert.Catch <EvaluationException>(() => env.Lookup("CONCAT", qualifier));

            env.DefineFields(row, qualifier);

            Assert.AreEqual("row", env.Lookup("FOO", null));
            Assert.IsInstanceOf <Function>(env.Lookup("CONCAT", null));
            Assert.AreEqual("row", env.Lookup("CONCAT", qualifier));

            env.DefineValue("FOO", "foo");
            env.DefineValue("CONCAT", "value");

            Assert.AreEqual("foo", env.Lookup("FOO", null));
            Assert.AreEqual("value", env.Lookup("CONCAT", null));
            Assert.AreEqual("row", env.Lookup("CONCAT", qualifier));

            env.ForgetValue("CONCAT");
            env.ForgetValue("FOO");

            Assert.AreEqual("row", env.Lookup("FOO", null));
            Assert.IsInstanceOf <Function>(env.Lookup("CONCAT", null));
            Assert.AreEqual("row", env.Lookup("CONCAT", qualifier));

            env.ForgetFields("qualifier");

            Assert.Catch <EvaluationException>(() => env.Lookup("FOO", null));
            Assert.IsInstanceOf <Function>(env.Lookup("CONCAT", null));
            Assert.Catch <EvaluationException>(() => env.Lookup("CONCAT", qualifier));
        }
        public void CanLookupIgnoreCase()
        {
            var envIgnoreCase = new StandardEnvironment();

            envIgnoreCase.DefineValue("foo", "bar");
            Assert.AreEqual("bar", envIgnoreCase.Lookup("foo", null));
            Assert.AreEqual("bar", envIgnoreCase.Lookup("FOO", null));
            var ex1 = Assert.Catch <EvaluationException>(() => envIgnoreCase.Lookup("foo", "qualifier"));

            Console.WriteLine(@"Expected exception: {0}", ex1.Message);

            Assert.IsInstanceOf(typeof(Function), envIgnoreCase.Lookup("TRIM", null));
            Assert.IsInstanceOf(typeof(Function), envIgnoreCase.Lookup("Trim", null));
        }
        public void CanEvaluateNullCoalescingOperator()
        {
            Assert.AreEqual("foo", Evaluate("'foo' ?? 'bar'"));
            Assert.AreEqual("bar", Evaluate("null ?? 'bar'"));

            // Here's how ?? is different from || (logical or):
            Assert.AreEqual(false, Evaluate("false ?? 'bar'"));
            Assert.AreEqual(true, Evaluate("false or 'bar'"));             // "bar"

            // ?? binds tighter than ?:
            var env = new StandardEnvironment();

            env.DefineValue("nothing", null);
            Assert.AreEqual("alt", Evaluate("nothing ?? false ? null ?? 'consequent' : 'alt' ?? 'alternative'", env));
        }
        public void CanLookupQualified()
        {
            var env = new StandardEnvironment(false);

            env.DefineValue("foo", "bar");

            Assert.AreEqual("bar", env.Lookup("foo", null));
            var ex1 = Assert.Catch <EvaluationException>(() => env.Lookup("foo", "qualifier"));

            Console.WriteLine(@"Expected exception: {0}", ex1.Message);

            Assert.IsInstanceOf <Function>(env.Lookup("TRIM", null));
            var ex2 = Assert.Catch <EvaluationException>(() => env.Lookup("TRIM", "qualifier"));

            Console.WriteLine(@"Expected exception: {0}", ex2.Message);
        }
        public void CanEvaluateArithmetic()
        {
            Assert.AreEqual(-7, Evaluate(" 1 - 3 - 5 "));
            Assert.AreEqual(3, Evaluate("1-(3-5)"));
            Assert.AreEqual(2, Evaluate("1-2+3"));

            Assert.AreEqual(-6.28, Evaluate("3.14*-2"));             // *- is two tokens!

            Assert.AreEqual(0, Evaluate("12-3*4"));
            Assert.AreEqual(36, Evaluate("(12-3)*4"));

            var env = new StandardEnvironment();

            env.DefineValue("LB", 0.46);

            Assert.IsTrue(Math.Abs((double)Evaluate("LB/2", env)) - 0.23 < double.Epsilon);
            Assert.IsTrue(Math.Abs((double)Evaluate("0.5*LB", env)) - 0.23 < double.Epsilon);
        }
        public void CanCaseInSensitive()
        {
            const string name = "fAlSe";

            // When ignoring case, "fAlSe" will be recognized as the built-in value false:

            var envIgnoreCase = new StandardEnvironment();

            envIgnoreCase.DefineValue(name, "TheEnv");
            Assert.AreEqual(false, Dump(ExpressionEvaluator.Create("fAlSe")).Evaluate(envIgnoreCase));

            // When respecting case, "fAlSe" is just a name to be looked up in the environment:

            var envRespectCase = new StandardEnvironment(false);

            envRespectCase.DefineValue(name, "TheEnv");
            Assert.AreEqual("TheEnv", Dump(ExpressionEvaluator.Create("fAlSe", false)).Evaluate(envRespectCase));
        }