Exemple #1
0
        /// <summary>
        /// Evaluate this line and return the value that would be stored
        /// into the lhs.
        /// </summary>
        public Value Evaluate(Context context)
        {
            int rhsATypeInt = rhsA == null ? -1 : rhsA.GetBaseMiniscriptType();

            if (op == Op.AssignA || op == Op.ReturnA || op == Op.AssignImplicit)
            {
                // Assignment is a bit of a special case.  It's EXTREMELY common
                // in TAC, so needs to be efficient, but we have to watch out for
                // the case of a RHS that is a list or map.  This means it was a
                // literal in the source, and may contain references that need to
                // be evaluated now.
                //if (rhsA is ValList || rhsA is ValMap) {
                if (rhsATypeInt == MiniscriptTypeInts.ValListTypeInt || rhsATypeInt == MiniscriptTypeInts.ValMapTypeInt)
                {
                    return(rhsA.FullEval(context));
                }
                else if (rhsA == null)
                {
                    return(null);
                }
                else
                {
                    return(rhsA.Val(context, true));
                }
            }
            if (op == Op.CopyA)
            {
                // This opcode is used for assigning a literal.  We actually have
                // to copy the literal, in the case of a mutable object like a
                // list or map, to ensure that if the same code executes again,
                // we get a new, unique object.
                //if (rhsA is ValList) {
                if (rhsATypeInt == MiniscriptTypeInts.ValListTypeInt)
                {
                    return(((ValList)rhsA).EvalCopy(context));
                    //} else if (rhsA is ValMap) {
                }
                else if (rhsATypeInt == MiniscriptTypeInts.ValMapTypeInt)
                {
                    return(((ValMap)rhsA).EvalCopy(context));
                }
                else if (rhsA == null)
                {
                    return(null);
                }
                else
                {
                    return(rhsA.Val(context, true));
                }
            }

            Value opA = rhsA != null?rhsA.Val(context, false) : null;

            Value opB = rhsB != null?rhsB.Val(context, false) : null;

            int opATypeInt = opA == null ? -1 : opA.GetBaseMiniscriptType();
            int opBTypeInt = opB == null ? -1 : opB.GetBaseMiniscriptType();

            if (op == Op.AisaB)
            {
                if (opA == null)
                {
                    return(ValNumber.Truth(opB == null));
                }
                return(ValNumber.Truth(opA.IsA(opB, context.vm)));
            }

            //if (op == Op.ElemBofA && opB is ValString) {
            if (op == Op.ElemBofA && opBTypeInt == MiniscriptTypeInts.ValStringTypeInt)
            {
                // You can now look for a string in almost anything...
                // and we have a convenient (and relatively fast) method for it:
                ValMap ignored;
                return(ValSeqElem.Resolve(opA, ((ValString)opB).value, context, out ignored));
            }

            // check for special cases of comparison to null (works with any type)
            if (op == Op.AEqualB && (opA == null || opB == null))
            {
                return(ValNumber.Truth(opA == opB));
            }
            if (op == Op.ANotEqualB && (opA == null || opB == null))
            {
                return(ValNumber.Truth(opA != opB));
            }

            // check for implicit coersion of other types to string; this happens
            // when either side is a string and the operator is addition.
            //if ((opA is ValString || opB is ValString) && op == Op.APlusB) {
            if ((opATypeInt == MiniscriptTypeInts.ValStringTypeInt || opBTypeInt == MiniscriptTypeInts.ValStringTypeInt) && op == Op.APlusB)
            {
                string sA = opA != null?opA.ToString(context.vm) : string.Empty;

                string sB = opB != null?opB.ToString(context.vm) : string.Empty;

                if (sA.Length + sB.Length > ValString.maxSize)
                {
                    throw new LimitExceededException("string too large");
                }
                return(ValString.Create(sA + sB));
            }

            //if (opA is ValNumber) {
            if (opATypeInt == MiniscriptTypeInts.ValNumberTypeInt)
            {
                double numA = ((ValNumber)opA).value;
                switch (op)
                {
                case Op.GotoA:
                    context.lineNum = (int)numA;
                    return(null);

                case Op.GotoAifB:
                    if (opB != null && opB.BoolValue())
                    {
                        context.lineNum = (int)numA;
                    }
                    return(null);

                case Op.GotoAifTrulyB:
                {
                    // Unlike GotoAifB, which branches if B has any nonzero
                    // value (including 0.5 or 0.001), this branches only if
                    // B is TRULY true, i.e., its integer value is nonzero.
                    // (Used for short-circuit evaluation of "or".)
                    int i = 0;
                    if (opB != null)
                    {
                        i = opB.IntValue();
                    }
                    if (i != 0)
                    {
                        context.lineNum = (int)numA;
                    }
                    return(null);
                }

                case Op.GotoAifNotB:
                    if (opB == null || !opB.BoolValue())
                    {
                        context.lineNum = (int)numA;
                    }
                    return(null);

                case Op.CallIntrinsicA:
                    // NOTE: intrinsics do not go through NextFunctionContext.  Instead
                    // they execute directly in the current context.  (But usually, the
                    // current context is a wrapper function that was invoked via
                    // Op.CallFunction, so it got a parameter context at that time.)
                    Intrinsic.Result result = Intrinsic.Execute((int)numA, context, context.partialResult);
                    if (result.done)
                    {
                        context.partialResult = default(Intrinsic.Result);
                        return(result.result);
                    }
                    // OK, this intrinsic function is not yet done with its work.
                    // We need to stay on this same line and call it again with
                    // the partial result, until it reports that its job is complete.
                    context.partialResult = result;
                    context.lineNum--;
                    return(null);

                case Op.NotA:
                    return(ValNumber.Create(1.0 - AbsClamp01(numA)));
                }
                //if (opB is ValNumber || opB == null) {
                if (opBTypeInt == MiniscriptTypeInts.ValNumberTypeInt || opB == null)
                {
                    double numB = opB != null ? ((ValNumber)opB).value : 0;
                    switch (op)
                    {
                    case Op.APlusB:
                        return(ValNumber.Create(numA + numB));

                    case Op.AMinusB:
                        return(ValNumber.Create(numA - numB));

                    case Op.ATimesB:
                        return(ValNumber.Create(numA * numB));

                    case Op.ADividedByB:
                        return(ValNumber.Create(numA / numB));

                    case Op.AModB:
                        return(ValNumber.Create(numA % numB));

                    case Op.APowB:
                        return(ValNumber.Create(Math.Pow(numA, numB)));

                    case Op.AEqualB:
                        return(ValNumber.Truth(numA == numB));

                    case Op.ANotEqualB:
                        return(ValNumber.Truth(numA != numB));

                    case Op.AGreaterThanB:
                        return(ValNumber.Truth(numA > numB));

                    case Op.AGreatOrEqualB:
                        return(ValNumber.Truth(numA >= numB));

                    case Op.ALessThanB:
                        return(ValNumber.Truth(numA < numB));

                    case Op.ALessOrEqualB:
                        return(ValNumber.Truth(numA <= numB));

                    case Op.AAndB:
                        //if (!(opB is ValNumber))
                        //numB = opB != null && opB.BoolValue() ? 1 : 0;
                        return(ValNumber.Create(Clamp01(numA * numB)));

                    case Op.AOrB:
                        //if (!(opB is ValNumber))
                        //numB = opB != null && opB.BoolValue() ? 1 : 0;
                        return(ValNumber.Create(Clamp01(numA + numB - numA * numB)));

                    default:
                        break;
                    }
                }
                else if (opBTypeInt == MiniscriptTypeInts.ValCustomTypeInt)
                {
                    // Most types should commute with number
                    // But in case not we have a flag to say if the other is on
                    // the lhs or rhs
                    ValCustom customB = opB as ValCustom;
                    switch (op)
                    {
                    case Op.APlusB:
                        return(customB.APlusB(opA, opATypeInt, context, false));

                    case Op.AMinusB:
                        return(customB.AMinusB(opA, opATypeInt, context, false));

                    case Op.ATimesB:
                        return(customB.ATimesB(opA, opATypeInt, context, false));

                    case Op.ADividedByB:
                        return(customB.ADividedByB(opA, opATypeInt, context, false));
                    }
                }
                // Handle equality testing between a number (opA) and a non-number (opB).
                // These are always considered unequal.
                if (op == Op.AEqualB)
                {
                    return(ValNumber.zero);
                }
                if (op == Op.ANotEqualB)
                {
                    return(ValNumber.one);
                }
                //} else if (opA is ValString) {
            }
            else if (opATypeInt == MiniscriptTypeInts.ValStringTypeInt)
            {
                string strA = ((ValString)opA).value;
                if (op == Op.ATimesB || op == Op.ADividedByB)
                {
                    double factor = 0;
                    if (op == Op.ATimesB)
                    {
                        Check.Type(opB, typeof(ValNumber), "string replication");
                        factor = ((ValNumber)opB).value;
                    }
                    else
                    {
                        Check.Type(opB, typeof(ValNumber), "string division");
                        factor = 1.0 / ((ValNumber)opB).value;
                    }
                    int repeats = (int)factor;
                    if (repeats < 0)
                    {
                        return(ValString.empty);
                    }
                    if (repeats * strA.Length > ValString.maxSize)
                    {
                        throw new LimitExceededException("string too large");
                    }
                    if (_workingStringBuilder == null)
                    {
                        _workingStringBuilder = new StringBuilder();
                    }
                    else
                    {
                        _workingStringBuilder.Clear();
                    }
                    for (int i = 0; i < repeats; i++)
                    {
                        _workingStringBuilder.Append(strA);
                    }
                    int extraChars = (int)(strA.Length * (factor - repeats));
                    if (extraChars > 0)
                    {
                        _workingStringBuilder.Append(strA.Substring(0, extraChars));
                    }
                    return(ValString.Create(_workingStringBuilder.ToString()));
                }
                if (op == Op.ElemBofA || op == Op.ElemBofIterA)
                {
                    int idx = opB.IntValue();
                    Check.Range(idx, -strA.Length, strA.Length - 1, "string index");
                    if (idx < 0)
                    {
                        idx += strA.Length;
                    }
                    return(ValString.Create(strA.Substring(idx, 1)));
                }
                //if (opB == null || opB is ValString) {
                if (opB == null || opBTypeInt == MiniscriptTypeInts.ValStringTypeInt)
                {
                    string sB = (opB == null ? null : opB.ToString(context.vm));
                    switch (op)
                    {
                    case Op.AMinusB: {
                        if (opB == null)
                        {
                            opA.Ref();
                            return(opA);
                        }
                        if (strA.EndsWith(sB))
                        {
                            strA = strA.Substring(0, strA.Length - sB.Length);
                        }
                        return(ValString.Create(strA));
                    }

                    case Op.NotA:
                        return(ValNumber.Truth(string.IsNullOrEmpty(strA)));

                    case Op.AEqualB:
                        return(ValNumber.Truth(string.Equals(strA, sB)));

                    case Op.ANotEqualB:
                        return(ValNumber.Truth(!string.Equals(strA, sB)));

                    case Op.AGreaterThanB:
                        return(ValNumber.Truth(string.Compare(strA, sB, StringComparison.Ordinal) > 0));

                    case Op.AGreatOrEqualB:
                        return(ValNumber.Truth(string.Compare(strA, sB, StringComparison.Ordinal) >= 0));

                    case Op.ALessThanB:
                        int foo = string.Compare(strA, sB, StringComparison.Ordinal);
                        return(ValNumber.Truth(foo < 0));

                    case Op.ALessOrEqualB:
                        return(ValNumber.Truth(string.Compare(strA, sB, StringComparison.Ordinal) <= 0));

                    case Op.LengthOfA:
                        return(ValNumber.Create(strA.Length));

                    default:
                        break;
                    }
                }
                else
                {
                    // RHS is neither null nor a string.
                    // We no longer automatically coerce in all these cases; about
                    // all we can do is equal or unequal testing.
                    // (Note that addition was handled way above here.)
                    if (op == Op.AEqualB)
                    {
                        return(ValNumber.zero);
                    }
                    if (op == Op.ANotEqualB)
                    {
                        return(ValNumber.one);
                    }
                }
                //} else if (opA is ValList) {
            }
            else if (opATypeInt == MiniscriptTypeInts.ValListTypeInt)
            {
                ValList listA = ((ValList)opA);
                if (op == Op.ElemBofA || op == Op.ElemBofIterA)
                {
                    // list indexing
                    int idx = opB.IntValue();
                    Check.Range(idx, -listA.Count, listA.Count - 1, "list index");
                    if (idx < 0)
                    {
                        idx += listA.Count;
                    }
                    Value val = listA[idx];
                    if (val != null)
                    {
                        val.Ref();
                    }
                    return(val);
                }
                else if (op == Op.LengthOfA)
                {
                    return(ValNumber.Create(listA.Count));
                }
                else if (op == Op.AEqualB)
                {
                    return(ValNumber.Truth(((ValList)opA).Equality(opB)));
                }
                else if (op == Op.ANotEqualB)
                {
                    return(ValNumber.Truth(1.0 - ((ValList)opA).Equality(opB)));
                }
                else if (op == Op.APlusB)
                {
                    // list concatenation
                    Check.Type(opB, typeof(ValList), "list concatenation");
                    ValList listB = ((ValList)opB);
                    if (listA.Count + listB.Count > ValList.maxSize)
                    {
                        throw new LimitExceededException("list too large");
                    }
                    ValList result = ValList.Create(listA.Count + listB.Count);
                    for (int i = 0; i < listA.Count; i++)
                    {
                        result.Add(context.ValueInContext(listA[i]));
                    }
                    for (int i = 0; i < listB.Count; i++)
                    {
                        result.Add(context.ValueInContext(listB[i]));
                    }
                    return(result);
                }
                else if (op == Op.ATimesB || op == Op.ADividedByB)
                {
                    // list replication (or division)
                    double factor = 0;
                    if (op == Op.ATimesB)
                    {
                        Check.Type(opB, typeof(ValNumber), "list replication");
                        factor = ((ValNumber)opB).value;
                    }
                    else
                    {
                        Check.Type(opB, typeof(ValNumber), "list division");
                        factor = 1.0 / ((ValNumber)opB).value;
                    }
                    if (factor <= 0)
                    {
                        return(ValList.Create());
                    }
                    int finalCount = (int)(listA.Count * factor);
                    if (finalCount > ValList.maxSize)
                    {
                        throw new LimitExceededException("list too large");
                    }
                    ValList result = ValList.Create(finalCount);
                    for (int i = 0; i < finalCount; i++)
                    {
                        result.Add(listA[i % listA.Count]);
                    }
                    return(result);
                }
                else if (op == Op.NotA)
                {
                    return(ValNumber.Truth(!opA.BoolValue()));
                }
                //} else if (opA is ValMap) {
            }
            else if (opATypeInt == MiniscriptTypeInts.ValMapTypeInt)
            {
                if (op == Op.ElemBofA)
                {
                    // map lookup
                    // (note, cases where opB is a string are handled above, along with
                    // all the other types; so we'll only get here for non-string cases)
                    ValSeqElem se  = ValSeqElem.Create(opA, opB);
                    Value      ret = se.Val(context, true);
                    //if (se != null)
                    //se.Unref();
                    return(ret);
                    // (This ensures we walk the "__isa" chain in the standard way.)
                }
                else if (op == Op.ElemBofIterA)
                {
                    // With a map, ElemBofIterA is different from ElemBofA.  This one
                    // returns a mini-map containing a key/value pair.
                    return(((ValMap)opA).GetKeyValuePair(opB.IntValue()));
                }
                else if (op == Op.LengthOfA)
                {
                    return(ValNumber.Create(((ValMap)opA).Count));
                }
                else if (op == Op.AEqualB)
                {
                    return(ValNumber.Truth(((ValMap)opA).Equality(opB)));
                }
                else if (op == Op.ANotEqualB)
                {
                    return(ValNumber.Truth(1.0 - ((ValMap)opA).Equality(opB)));
                }
                else if (op == Op.APlusB)
                {
                    // map combination
                    Check.Type(opB, typeof(ValMap), "map combination");
                    ValMap result = ValMap.Create();
                    ValMap mapA   = opA as ValMap;
                    var    aKeys  = mapA.Keys;
                    var    aVals  = mapA.Values;
                    for (int i = 0; i < aKeys.Count; i++)
                    {
                        result[aKeys[i]] = context.ValueInContext(aVals[i]);
                    }
                    ValMap mapB  = opB as ValMap;
                    var    bKeys = mapB.Keys;
                    var    bVals = mapB.Values;
                    for (int i = 0; i < bKeys.Count; i++)
                    {
                        result[bKeys[i]] = context.ValueInContext(bVals[i]);
                    }
                    return(result);
                }
                else if (op == Op.NotA)
                {
                    return(ValNumber.Truth(!opA.BoolValue()));
                }
                //} else if (opA is ValFunction && opB is ValFunction) {
            }
            else if (opATypeInt == MiniscriptTypeInts.ValFunctionTypeInt && opBTypeInt == MiniscriptTypeInts.ValFunctionTypeInt)
            {
                Function fA = ((ValFunction)opA).function;
                Function fB = ((ValFunction)opB).function;
                switch (op)
                {
                case Op.AEqualB:
                    return(ValNumber.Truth(fA == fB));

                case Op.ANotEqualB:
                    return(ValNumber.Truth(fA != fB));
                }
            }
            else if (opATypeInt == MiniscriptTypeInts.ValCustomTypeInt)
            {
                ValCustom customA = opA as ValCustom;
                switch (op)
                {
                case Op.APlusB:
                    return(customA.APlusB(opB, opBTypeInt, context, true));

                case Op.AMinusB:
                    return(customA.AMinusB(opB, opBTypeInt, context, true));

                case Op.ATimesB:
                    return(customA.ATimesB(opB, opBTypeInt, context, true));

                case Op.ADividedByB:
                    return(customA.ADividedByB(opB, opBTypeInt, context, true));
                }
            }
            else
            {
                // opA is something else... perhaps null
                switch (op)
                {
                case Op.BindAssignA:
                {
                    if (context.variables == null)
                    {
                        context.variables = ValMap.Create();
                    }
                    ValFunction valFunc = (ValFunction)opA;
                    return(valFunc.BindAndCopy(context.variables));
                    //valFunc.outerVars = context.variables;
                    //return null;
                }

                case Op.NotA:
                    return(opA != null && opA.BoolValue() ? ValNumber.zero : ValNumber.one);
                }
            }


            if (op == Op.AAndB || op == Op.AOrB)
            {
                // We already handled the case where opA was a number above;
                // this code handles the case where opA is something else.
                double numA = opA != null && opA.BoolValue() ? 1 : 0;
                double numB;
                //if (opB is ValNumber) fB = ((ValNumber)opB).value;
                if (opBTypeInt == MiniscriptTypeInts.ValNumberTypeInt)
                {
                    numB = ((ValNumber)opB).value;
                }
                else
                {
                    numB = opB != null && opB.BoolValue() ? 1 : 0;
                }
                double result;
                if (op == Op.AAndB)
                {
                    result = numA * numB;
                }
                else
                {
                    result = 1.0 - (1.0 - AbsClamp01(numA)) * (1.0 - AbsClamp01(numB));
                }
                return(ValNumber.Create(result));
            }
            return(null);
        }
Exemple #2
0
            /// <summary>
            /// Evaluate this line and return the value that would be stored
            /// into the lhs.
            /// </summary>
            public Value Evaluate(Context context)
            {
                if (op == Op.AssignA || op == Op.ReturnA || op == Op.AssignImplicit)
                {
                    // Assignment is a bit of a special case.  It's EXTREMELY common
                    // in TAC, so needs to be efficient, but we have to watch out for
                    // the case of a RHS that is a list or map.  This means it was a
                    // literal in the source, and may contain references that need to
                    // be evaluated now.
                    if (rhsA is ValList || rhsA is ValMap)
                    {
                        return(rhsA.FullEval(context));
                    }
                    else if (rhsA == null)
                    {
                        return(null);
                    }
                    else
                    {
                        return(rhsA.Val(context));
                    }
                }
                if (op == Op.CopyA)
                {
                    // This opcode is used for assigning a literal.  We actually have
                    // to copy the literal, in the case of a mutable object like a
                    // list or map, to ensure that if the same code executes again,
                    // we get a new, unique object.
                    if (rhsA is ValList)
                    {
                        return(((ValList)rhsA).EvalCopy(context));
                    }
                    else if (rhsA is ValMap)
                    {
                        return(((ValMap)rhsA).EvalCopy(context));
                    }
                    else if (rhsA == null)
                    {
                        return(null);
                    }
                    else
                    {
                        return(rhsA.Val(context));
                    }
                }

                Value opA = rhsA != null?rhsA.Val(context) : null;

                Value opB = rhsB != null?rhsB.Val(context) : null;

                if (op == Op.AisaB)
                {
                    return(ValNumber.Truth(opA.IsA(opB, context.vm)));
                }

                if (op == Op.ElemBofA && opB is ValString)
                {
                    // You can now look for a string in almost anything...
                    // and we have a convenient (and relatively fast) method for it:
                    ValMap ignored;
                    return(ValSeqElem.Resolve(opA, ((ValString)opB).value, context, out ignored));
                }

                if (op == Op.AEqualB && (opA == null || opB == null))
                {
                    return(ValNumber.Truth(opA == opB));
                }
                if (op == Op.ANotEqualB && (opA == null || opB == null))
                {
                    return(ValNumber.Truth(opA != opB));
                }

                if (opA is ValNumber)
                {
                    double fA = ((ValNumber)opA).value;
                    switch (op)
                    {
                    case Op.GotoA:
                        context.lineNum = (int)fA;
                        return(null);

                    case Op.GotoAifB:
                        if (opB != null && opB.BoolValue())
                        {
                            context.lineNum = (int)fA;
                        }
                        return(null);

                    case Op.GotoAifTrulyB:
                    {
                        // Unlike GotoAifB, which branches if B has any nonzero
                        // value (including 0.5 or 0.001), this branches only if
                        // B is TRULY true, i.e., its integer value is nonzero.
                        // (Used for short-circuit evaluation of "or".)
                        int i = 0;
                        if (opB != null)
                        {
                            i = opB.IntValue();
                        }
                        if (i != 0)
                        {
                            context.lineNum = (int)fA;
                        }
                        return(null);
                    }

                    case Op.GotoAifNotB:
                        if (opB == null || !opB.BoolValue())
                        {
                            context.lineNum = (int)fA;
                        }
                        return(null);

                    case Op.CallIntrinsicA:
                        // NOTE: intrinsics do not go through NextFunctionContext.  Instead
                        // they execute directly in the current context.  (But usually, the
                        // current context is a wrapper function that was invoked via
                        // Op.CallFunction, so it got a parameter context at that time.)
                        Intrinsic.Result result = Intrinsic.Execute((int)fA, context, context.partialResult);
                        if (result.done)
                        {
                            context.partialResult = null;
                            return(result.result);
                        }
                        // OK, this intrinsic function is not yet done with its work.
                        // We need to stay on this same line and call it again with
                        // the partial result, until it reports that its job is complete.
                        context.partialResult = result;
                        context.lineNum--;
                        return(null);

                    case Op.NotA:
                        return(new ValNumber(1.0 - AbsClamp01(fA)));
                    }
                    if (opB is ValNumber || opB == null)
                    {
                        double fB = opB != null ? ((ValNumber)opB).value : 0;
                        switch (op)
                        {
                        case Op.APlusB:
                            return(new ValNumber(fA + fB));

                        case Op.AMinusB:
                            return(new ValNumber(fA - fB));

                        case Op.ATimesB:
                            return(new ValNumber(fA * fB));

                        case Op.ADividedByB:
                            return(new ValNumber(fA / fB));

                        case Op.AModB:
                            return(new ValNumber(fA % fB));

                        case Op.APowB:
                            return(new ValNumber(Math.Pow(fA, fB)));

                        case Op.AEqualB:
                            return(ValNumber.Truth(fA == fB));

                        case Op.ANotEqualB:
                            return(ValNumber.Truth(fA != fB));

                        case Op.AGreaterThanB:
                            return(ValNumber.Truth(fA > fB));

                        case Op.AGreatOrEqualB:
                            return(ValNumber.Truth(fA >= fB));

                        case Op.ALessThanB:
                            return(ValNumber.Truth(fA < fB));

                        case Op.ALessOrEqualB:
                            return(ValNumber.Truth(fA <= fB));

                        case Op.AAndB:
                            if (!(opB is ValNumber))
                            {
                                fB = opB != null && opB.BoolValue() ? 1 : 0;
                            }
                            return(new ValNumber(Clamp01(fA * fB)));

                        case Op.AOrB:
                            if (!(opB is ValNumber))
                            {
                                fB = opB != null && opB.BoolValue() ? 1 : 0;
                            }
                            return(new ValNumber(Clamp01(fA + fB - fA * fB)));

                        default:
                            break;
                        }
                    }
                    else if (opB is ValString)
                    {
                        // number (op) string
                        string sA = opA.ToString();
                        string sB = opB.ToString();
                        switch (op)
                        {
                        case Op.APlusB:
                            return(new ValString(sA + sB));

                        default:
                            break;
                        }
                    }
                }
                else if (opA is ValString)
                {
                    string sA = ((ValString)opA).value;
                    switch (op)
                    {
                    case Op.APlusB:
                    {
                        if (opB == null)
                        {
                            return(opA);
                        }
                        String sB = opB.ToString(context.vm);
                        if (sA.Length + sB.Length > ValString.maxSize)
                        {
                            throw new LimitExceededException("string too large");
                        }
                        return(new ValString(sA + sB));
                    }

                    case Op.AMinusB:
                    {
                        if (opB == null)
                        {
                            return(opA);
                        }
                        string sB = opB.ToString(context.vm);
                        if (sA.EndsWith(sB))
                        {
                            sA = sA.Substring(0, sA.Length - sB.Length);
                        }
                        return(new ValString(sA));
                    }

                    case Op.ATimesB:
                    case Op.ADividedByB:
                    {
                        double factor = 0;
                        if (op == Op.ATimesB)
                        {
                            Check.Type(opB, typeof(ValNumber), "string replication");
                            factor = ((ValNumber)opB).value;
                        }
                        else
                        {
                            Check.Type(opB, typeof(ValNumber), "string division");
                            factor = 1.0 / ((ValNumber)opB).value;
                        }
                        int repeats = (int)factor;
                        if (repeats < 0)
                        {
                            return(ValString.empty);
                        }
                        if (repeats * sA.Length > ValString.maxSize)
                        {
                            throw new LimitExceededException("string too large");
                        }
                        var result = new System.Text.StringBuilder();
                        for (int i = 0; i < repeats; i++)
                        {
                            result.Append(sA);
                        }
                        int extraChars = (int)(sA.Length * (factor - repeats));
                        if (extraChars > 0)
                        {
                            result.Append(sA.Substring(0, extraChars));
                        }
                        return(new ValString(result.ToString()));
                    }

                    case Op.AEqualB:
                        return(ValNumber.Truth(string.Compare(sA, opB.ToString(context.vm)) == 0));

                    case Op.ANotEqualB:
                        return(ValNumber.Truth(string.Compare(sA, opB.ToString(context.vm)) != 0));

                    case Op.AGreaterThanB:
                        return(ValNumber.Truth(string.Compare(sA, opB.ToString(context.vm)) > 0));

                    case Op.AGreatOrEqualB:
                        return(ValNumber.Truth(string.Compare(sA, opB.ToString(context.vm)) >= 0));

                    case Op.ALessThanB:
                        return(ValNumber.Truth(string.Compare(sA, opB.ToString(context.vm)) < 0));

                    case Op.ALessOrEqualB:
                        return(ValNumber.Truth(string.Compare(sA, opB.ToString(context.vm)) <= 0));

                    case Op.ElemBofA:
                    case Op.ElemBofIterA:
                    {
                        int idx = opB.IntValue();
                        Check.Range(idx, -sA.Length, sA.Length - 1, "string index");
                        if (idx < 0)
                        {
                            idx += sA.Length;
                        }
                        return(new ValString(sA.Substring(idx, 1)));
                    }

                    case Op.LengthOfA:
                        return(new ValNumber(sA.Length));

                    default:
                        break;
                    }
                }
                else if (opA is ValList)
                {
                    List <Value> list = ((ValList)opA).values;
                    if (op == Op.ElemBofA || op == Op.ElemBofIterA)
                    {
                        // list indexing
                        int idx = opB.IntValue();
                        Check.Range(idx, -list.Count, list.Count - 1, "list index");
                        if (idx < 0)
                        {
                            idx += list.Count;
                        }
                        return(list[idx]);
                    }
                    else if (op == Op.LengthOfA)
                    {
                        return(new ValNumber(list.Count));
                    }
                    else if (op == Op.AEqualB)
                    {
                        return(ValNumber.Truth(((ValList)opA).Equality(opB)));
                    }
                    else if (op == Op.ANotEqualB)
                    {
                        return(ValNumber.Truth(1.0 - ((ValList)opA).Equality(opB)));
                    }
                    else if (op == Op.APlusB)
                    {
                        // list concatenation
                        Check.Type(opB, typeof(ValList), "list concatenation");
                        List <Value> list2 = ((ValList)opB).values;
                        if (list.Count + list2.Count > ValList.maxSize)
                        {
                            throw new LimitExceededException("list too large");
                        }
                        List <Value> result = new List <Value>(list.Count + list2.Count);
                        foreach (Value v in list)
                        {
                            result.Add(v == null ? null : v.Val(context));
                        }
                        foreach (Value v in list2)
                        {
                            result.Add(v == null ? null : v.Val(context));
                        }
                        return(new ValList(result));
                    }
                    else if (op == Op.ATimesB || op == Op.ADividedByB)
                    {
                        // list replication (or division)
                        double factor = 0;
                        if (op == Op.ATimesB)
                        {
                            Check.Type(opB, typeof(ValNumber), "list replication");
                            factor = ((ValNumber)opB).value;
                        }
                        else
                        {
                            Check.Type(opB, typeof(ValNumber), "list division");
                            factor = 1.0 / ((ValNumber)opB).value;
                        }
                        if (factor <= 0)
                        {
                            return(new ValList());
                        }
                        int finalCount = (int)(list.Count * factor);
                        if (finalCount > ValList.maxSize)
                        {
                            throw new LimitExceededException("list too large");
                        }
                        List <Value> result = new List <Value>(finalCount);
                        for (int i = 0; i < finalCount; i++)
                        {
                            result.Add(list[i % list.Count]);
                        }
                        return(new ValList(result));
                    }
                    else if (op == Op.NotA)
                    {
                        return(ValNumber.Truth(!opA.BoolValue()));
                    }
                }
                else if (opA is ValMap)
                {
                    if (op == Op.ElemBofA)
                    {
                        // map lookup
                        // (note, cases where opB is a string are handled above, along with
                        // all the other types; so we'll only get here for non-string cases)
                        ValSeqElem se = new ValSeqElem(opA, opB);
                        return(se.Val(context));
                        // (This ensures we walk the "__isa" chain in the standard way.)
                    }
                    else if (op == Op.ElemBofIterA)
                    {
                        // With a map, ElemBofIterA is different from ElemBofA.  This one
                        // returns a mini-map containing a key/value pair.
                        return(((ValMap)opA).GetKeyValuePair(opB.IntValue()));
                    }
                    else if (op == Op.LengthOfA)
                    {
                        return(new ValNumber(((ValMap)opA).Count));
                    }
                    else if (op == Op.AEqualB)
                    {
                        return(ValNumber.Truth(((ValMap)opA).Equality(opB)));
                    }
                    else if (op == Op.ANotEqualB)
                    {
                        return(ValNumber.Truth(1.0 - ((ValMap)opA).Equality(opB)));
                    }
                    else if (op == Op.APlusB)
                    {
                        // map combination
                        Dictionary <Value, Value> map = ((ValMap)opA).map;
                        Check.Type(opB, typeof(ValMap), "map combination");
                        Dictionary <Value, Value> map2 = ((ValMap)opB).map;
                        ValMap result = new ValMap();
                        foreach (KeyValuePair <Value, Value> kv in map)
                        {
                            result.map[kv.Key] = kv.Value.Val(context);
                        }
                        foreach (KeyValuePair <Value, Value> kv in map2)
                        {
                            result.map[kv.Key] = kv.Value.Val(context);
                        }
                        return(result);
                    }
                    else if (op == Op.NotA)
                    {
                        return(ValNumber.Truth(!opA.BoolValue()));
                    }
                }
                else if (opA is ValFunction && opB is ValFunction)
                {
                    Function fA = ((ValFunction)opA).function;
                    Function fB = ((ValFunction)opB).function;
                    switch (op)
                    {
                    case Op.AEqualB:
                        return(ValNumber.Truth(fA == fB));

                    case Op.ANotEqualB:
                        return(ValNumber.Truth(fA != fB));
                    }
                }
                else
                {
                    // opA is something else... perhaps null
                    switch (op)
                    {
                    case Op.NotA:
                        return(opA != null && opA.BoolValue() ? ValNumber.zero : ValNumber.one);
                    }
                }

                if (op == Op.AAndB || op == Op.AOrB)
                {
                    // We already handled the case where opA was a number above;
                    // this code handles the case where opA is something else.
                    double fA = opA != null && opA.BoolValue() ? 1 : 0;
                    double fB;
                    if (opB is ValNumber)
                    {
                        fB = ((ValNumber)opB).value;
                    }
                    else
                    {
                        fB = opB != null && opB.BoolValue() ? 1 : 0;
                    }
                    double result;
                    if (op == Op.AAndB)
                    {
                        result = fA * fB;
                    }
                    else
                    {
                        result = 1.0 - (1.0 - AbsClamp01(fA)) * (1.0 - AbsClamp01(fB));
                    }
                    return(new ValNumber(result));
                }
                return(null);
            }