public DekiScriptLiteral Visit(DekiScriptVar expr, DekiScriptEnv env)
        {
            if (expr.Name.EqualsInvariant(DekiScriptRuntime.ENV_ID))
            {
                DekiScriptMap vars = new DekiScriptMap();
                vars.AddRange(env.Globals);
                vars.AddRange(env.Locals);
                return(vars);
            }

            // check if variable exists
            DekiScriptLiteral result = env[expr.Name];

            if (result == null)
            {
                throw new DekiScriptUndefinedNameException(expr.Line, expr.Column, expr.Name);
            }
            result = DekiScriptRuntime.EvaluateProperty(result, env);
            return(result);
        }
        public DekiScriptOutputBuffer.Range Visit(DekiScriptVar expr, DekiScriptExpressionEvaluationState state)
        {
            if (expr.Name.EqualsInvariant(DekiScriptRuntime.ENV_ID))
            {
                DekiScriptMap vars = new DekiScriptMap();
                vars.AddRange(state.Env.Vars);
                return(state.Push(vars));
            }

            // check if variable exists
            DekiScriptLiteral result;

            if (!state.Env.Vars.TryGetValue(expr.Name, out result))
            {
                result = state.Runtime.ResolveMissingName(expr.Name);
            }

            if (result == null)
            {
                throw new DekiScriptUndefinedNameException(expr.Location, expr.Name);
            }
            result = state.Runtime.EvaluateProperty(expr.Location, result, state.Env);
            return(state.Push(result));
        }
        public DekiScriptOutputBuffer.Range Visit(DekiScriptBinary expr, DekiScriptExpressionEvaluationState state)
        {
            DekiScriptExpression Left  = expr.Left;
            DekiScriptExpression Right = expr.Right;

            switch (expr.OpCode)
            {
            case DekiScriptBinary.Op.LeftValue:
                return(Left.VisitWith(this, state));

            case DekiScriptBinary.Op.IdentityEqual: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                return(state.Push(DekiScriptBinary.IdentityEqual(left, right)));
            }

            case DekiScriptBinary.Op.IdentityNotEqual: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                return(state.Push(DekiScriptBinary.IdentityNotEqual(left, right)));
            }

            case DekiScriptBinary.Op.IsType: {
                DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state));
                string            type = ((DekiScriptString)Right).Value;
                return(state.Push(DekiScriptExpression.Constant(type.EqualsInvariantIgnoreCase("any") || left.ScriptTypeName.EqualsInvariantIgnoreCase(type))));
            }

            case DekiScriptBinary.Op.Equal: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                return(state.Push(DekiScriptBinary.Equal(left, right)));
            }

            case DekiScriptBinary.Op.NotEqual: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                return(state.Push(DekiScriptBinary.NotEqual(left, right)));
            }

            case DekiScriptBinary.Op.GreaterOrEqual: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right))
                {
                    switch (left.ScriptType)
                    {
                    case DekiScriptType.BOOL:
                    case DekiScriptType.NUM:
                        return(state.Push(DekiScriptExpression.Constant(left.AsNumber() >= right.AsNumber())));

                    case DekiScriptType.STR:
                        return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) >= 0)));

                    default:
                        return(DekiScriptOutputBuffer.Range.Empty);
                    }
                }
                else
                {
                    return(DekiScriptOutputBuffer.Range.Empty);
                }
            }

            case DekiScriptBinary.Op.GreaterThan: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right))
                {
                    switch (left.ScriptType)
                    {
                    case DekiScriptType.BOOL:
                    case DekiScriptType.NUM:
                        return(state.Push(DekiScriptExpression.Constant(left.AsNumber() > right.AsNumber())));

                    case DekiScriptType.STR:
                        return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) > 0)));

                    default:
                        return(DekiScriptOutputBuffer.Range.Empty);
                    }
                }
                else
                {
                    return(DekiScriptOutputBuffer.Range.Empty);
                }
            }

            case DekiScriptBinary.Op.LessOrEqual: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right))
                {
                    switch (left.ScriptType)
                    {
                    case DekiScriptType.BOOL:
                    case DekiScriptType.NUM:
                        return(state.Push(DekiScriptExpression.Constant(left.AsNumber() <= right.AsNumber())));

                    case DekiScriptType.STR:
                        return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) <= 0)));

                    default:
                        return(DekiScriptOutputBuffer.Range.Empty);
                    }
                }
                else
                {
                    return(DekiScriptOutputBuffer.Range.Empty);
                }
            }

            case DekiScriptBinary.Op.LessThan: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right))
                {
                    switch (left.ScriptType)
                    {
                    case DekiScriptType.BOOL:
                    case DekiScriptType.NUM:
                        return(state.Push(DekiScriptExpression.Constant(left.AsNumber() < right.AsNumber())));

                    case DekiScriptType.STR:
                        return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) < 0)));

                    default:
                        return(DekiScriptOutputBuffer.Range.Empty);
                    }
                }
                else
                {
                    return(DekiScriptOutputBuffer.Range.Empty);
                }
            }

            case DekiScriptBinary.Op.LogicalAnd: {
                DekiScriptLiteral result = state.Pop(Left.VisitWith(this, state));
                if (!result.IsNilFalseZero)
                {
                    return(Right.VisitWith(this, state));
                }
                return(state.Push(result));
            }

            case DekiScriptBinary.Op.LogicalOr: {
                DekiScriptLiteral result = state.Pop(Left.VisitWith(this, state));
                if (result.IsNilFalseZero)
                {
                    return(Right.VisitWith(this, state));
                }
                return(state.Push(result));
            }

            case DekiScriptBinary.Op.Addition:
                return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() + state.Pop(Right.VisitWith(this, state)).AsNumber())));

            case DekiScriptBinary.Op.Division:
                return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() / state.Pop(Right.VisitWith(this, state)).AsNumber())));

            case DekiScriptBinary.Op.Modulo: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if ((left is DekiScriptString) && ((right is DekiScriptMap) || (right is DekiScriptList)))
                {
                    // NOTE (steveb): string.format shorthand notation: "abc = $abc" % { abc: 123 } -OR- "0 = $0" % [ 123 ]
                    return(state.Push(DekiScriptLiteral.FromNativeValue(DekiScriptLibrary.StringFormat(((DekiScriptString)left).Value, right.NativeValue))));
                }
                else
                {
                    return(state.Push(DekiScriptExpression.Constant(left.AsNumber() % right.AsNumber())));
                }
            }

            case DekiScriptBinary.Op.Multiplication:
                return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() * state.Pop(Right.VisitWith(this, state)).AsNumber())));

            case DekiScriptBinary.Op.Subtraction:
                return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() - state.Pop(Right.VisitWith(this, state)).AsNumber())));

            case DekiScriptBinary.Op.NullCoalesce: {
                DekiScriptLiteral result = state.Pop(Left.VisitWith(this, state));
                if (result.IsNil)
                {
                    return(Right.VisitWith(this, state));
                }
                return(state.Push(result));
            }

            case DekiScriptBinary.Op.Concat: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if (left is DekiScriptNil)
                {
                    return(state.Push(right));
                }
                else if (right is DekiScriptNil)
                {
                    return(state.Push(left));
                }
                else if ((left is DekiScriptMap) && (right is DekiScriptMap))
                {
                    // left and right expressions are maps, merge them
                    DekiScriptMap result = new DekiScriptMap();
                    result.AddRange((DekiScriptMap)left);
                    result.AddRange((DekiScriptMap)right);
                    return(state.Push(result));
                }
                else if ((left is DekiScriptList) && (right is DekiScriptList))
                {
                    // left and right expressions are lists, concatenate them
                    DekiScriptList result = new DekiScriptList();
                    result.AddRange((DekiScriptList)left);
                    result.AddRange((DekiScriptList)right);
                    return(state.Push(result));
                }
                else
                {
                    // treat left and right expressions as strings
                    string leftText  = left.AsString();
                    string rightText = right.AsString();
                    if ((leftText != null) && (rightText != null))
                    {
                        return(state.Push(DekiScriptExpression.Constant(leftText + rightText)));
                    }
                    else if (leftText != null)
                    {
                        return(state.Push(DekiScriptExpression.Constant(leftText)));
                    }
                    else if (rightText != null)
                    {
                        return(state.Push(DekiScriptExpression.Constant(rightText)));
                    }
                    else
                    {
                        return(DekiScriptOutputBuffer.Range.Empty);
                    }
                }
            }

            case DekiScriptBinary.Op.UriAppend: {
                // TODO (steveb): we should throw an exception when the LHS is not a valid string or uri

                XUri   left   = XUri.TryParse(state.Pop(Left.VisitWith(this, state)).AsString());
                string result = null;
                if (left != null)
                {
                    DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                    if (right is DekiScriptString)
                    {
                        result = DekiScriptLibrary.UriBuild(left, right.AsString(), null);
                    }
                    else if (right is DekiScriptMap)
                    {
                        result = DekiScriptLibrary.UriBuild(left, null, (Hashtable)right.NativeValue);
                    }
                    else
                    {
                        result = left.ToString();
                    }
                }
                return(state.Push(DekiScriptLiteral.FromNativeValue(result)));
            }

            case DekiScriptBinary.Op.InCollection: {
                DekiScriptLiteral left  = state.Pop(Left.VisitWith(this, state));
                DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state));
                if (right is DekiScriptList)
                {
                    foreach (DekiScriptLiteral item in ((DekiScriptList)right).Value)
                    {
                        if (!DekiScriptBinary.Equal(left, item).IsNilFalseZero)
                        {
                            return(state.Push(DekiScriptBool.True));
                        }
                    }
                    return(state.Push(DekiScriptBool.False));
                }
                else if (right is DekiScriptMap)
                {
                    foreach (DekiScriptLiteral item in ((DekiScriptMap)right).Value.Values)
                    {
                        if (!DekiScriptBinary.Equal(left, item).IsNilFalseZero)
                        {
                            return(state.Push(DekiScriptBool.True));
                        }
                    }
                    return(state.Push(DekiScriptBool.False));
                }
                else
                {
                    return(state.Push(DekiScriptBool.False));
                }
            }
            }
            throw new InvalidOperationException("invalid op code:" + expr.OpCode);
        }
        public DekiScriptOutputBuffer.Range Visit(DekiScriptCall expr, DekiScriptExpressionEvaluationState state)
        {
            state.ThrowIfTimedout();

            // evaluate prefix
            DekiScriptLiteral prefix = state.Pop(expr.Prefix.VisitWith(this, state));

            if (prefix.ScriptType != DekiScriptType.URI)
            {
                if (prefix.ScriptType == DekiScriptType.NIL)
                {
                    throw new DekiScriptUndefinedNameException(expr.Location, expr.Prefix.ToString());
                }
                else
                {
                    throw new DekiScriptBadTypeException(expr.Location, prefix.ScriptType, new[] { DekiScriptType.URI });
                }
            }

            // evaluate arguments
            DekiScriptLiteral arguments = state.Pop(expr.Arguments.VisitWith(this, state));

            if ((arguments.ScriptType != DekiScriptType.MAP) && (arguments.ScriptType != DekiScriptType.LIST))
            {
                throw new DekiScriptBadTypeException(expr.Location, arguments.ScriptType, new[] { DekiScriptType.MAP, DekiScriptType.LIST });
            }

            // check if the URI was curried
            DekiScriptUri uri = (DekiScriptUri)prefix;

            if (!uri.Arguments.IsNil)
            {
                switch (uri.Arguments.ScriptType)
                {
                case DekiScriptType.LIST:

                    // append argument to list
                    DekiScriptList list = new DekiScriptList((DekiScriptList)uri.Arguments);
                    list.Add(arguments);
                    arguments = list;
                    break;

                case DekiScriptType.MAP:
                    if (arguments.ScriptType == DekiScriptType.MAP)
                    {
                        // concatenate both maps
                        DekiScriptMap map = new DekiScriptMap();
                        map.AddRange((DekiScriptMap)uri.Arguments);
                        map.AddRange((DekiScriptMap)arguments);
                        arguments = map;
                    }
                    else if ((arguments.ScriptType != DekiScriptType.LIST) || ((DekiScriptList)arguments).Value.Count > 0)
                    {
                        // we can't append a list to a map
                        throw new DekiScriptBadTypeException(expr.Location, arguments.ScriptType, new[] { DekiScriptType.MAP });
                    }
                    break;

                default:
                    throw new DekiScriptBadTypeException(expr.Location, arguments.ScriptType, new[] { DekiScriptType.MAP, DekiScriptType.LIST });
                }
            }

            // check if this is an invocation or curry operation
            if (expr.IsCurryOperation)
            {
                return(state.Push(new DekiScriptUri(uri.Value, arguments)));
            }

            // invoke function
            try {
                return(state.Push(state.Runtime.Invoke(expr.Location, uri.Value, arguments, state.Env)));
            } catch (DekiScriptFatalException) {
                throw;
            } catch (Exception e) {
                var descriptor = state.Runtime.ResolveRegisteredFunctionUri(uri.Value);
                throw new DekiScriptInvokeException(expr.Location, uri.Value, (descriptor != null) ? descriptor.Name : uri.Value.ToString(), e);
            }
        }
        public DekiScriptLiteral Visit(DekiScriptCall expr, DekiScriptEnv env)
        {
            // evaluate prefix
            DekiScriptLiteral prefix = expr.Prefix.VisitWith(this, env);

            if (prefix.ScriptType != DekiScriptType.URI)
            {
                if (prefix.ScriptType == DekiScriptType.NIL)
                {
                    throw new DekiScriptUndefinedNameException(expr.Line, expr.Column, expr.Prefix.ToString());
                }
                else
                {
                    throw new DekiScriptBadTypeException(expr.Line, expr.Column, prefix.ScriptType, new DekiScriptType[] { DekiScriptType.URI });
                }
            }

            // evaluate arguments
            DekiScriptLiteral arguments = expr.Arguments.VisitWith(this, env);

            if ((arguments.ScriptType != DekiScriptType.MAP) && (arguments.ScriptType != DekiScriptType.LIST))
            {
                throw new DekiScriptBadTypeException(expr.Line, expr.Column, arguments.ScriptType, new DekiScriptType[] { DekiScriptType.MAP, DekiScriptType.LIST });
            }

            // check if the URI was curried
            DekiScriptUri uri = (DekiScriptUri)prefix;

            if (!uri.Arguments.IsNil)
            {
                switch (uri.Arguments.ScriptType)
                {
                case DekiScriptType.LIST:

                    // append argument to list
                    DekiScriptList list = new DekiScriptList((DekiScriptList)uri.Arguments);
                    list.Add(arguments);
                    arguments = list;
                    break;

                case DekiScriptType.MAP:
                    if (arguments.ScriptType == DekiScriptType.MAP)
                    {
                        // concatenate both maps
                        DekiScriptMap map = new DekiScriptMap();
                        map.AddRange((DekiScriptMap)uri.Arguments);
                        map.AddRange((DekiScriptMap)arguments);
                        arguments = map;
                    }
                    else if ((arguments.ScriptType != DekiScriptType.LIST) || ((DekiScriptList)arguments).Value.Count > 0)
                    {
                        // we can't append a list to a map
                        throw new DekiScriptBadTypeException(expr.Line, expr.Column, arguments.ScriptType, new DekiScriptType[] { DekiScriptType.MAP });
                    }
                    break;

                default:
                    throw new DekiScriptBadTypeException(expr.Line, expr.Column, arguments.ScriptType, new DekiScriptType[] { DekiScriptType.MAP, DekiScriptType.LIST });
                }
            }

            // check if this is an invocation or curry operation
            if (expr.IsCurryOperation)
            {
                return(new DekiScriptUri(uri.Value, arguments));
            }
            else
            {
                // invoke function
                return(Coroutine.Invoke(DekiScriptRuntime.Invoke, uri.Value, arguments, env, new Result <DekiScriptLiteral>()).Wait());
            }
        }