Beispiel #1
0
        public void SetElem(string index, Value value, bool takeValueRef)
        {
            ValString keyStr = ValString.Create(index);

            SetElem(keyStr, value, takeValueRef);
            keyStr.Unref();
        }
Beispiel #2
0
        public void Insert(int idx, Value value)
        {
            ValString str = value as ValString;

            value?.Ref();
            values.Insert(StartIndex + idx, value);
        }
Beispiel #3
0
        /// <summary>
        /// Look up the given identifier as quickly as possible, without
        /// walking the __isa chain or doing anything fancy.  (This is used
        /// when looking up local variables.)
        /// </summary>
        /// <param name="identifier">identifier to look up</param>
        /// <returns>true if found, false if not</returns>
        public bool TryGetValue(string identifier, out Value value)
        {
            var  idVal  = ValString.Create(identifier);
            bool result = TryGetValue(idVal, out value);

            idVal.Unref();
            return(result);
        }
Beispiel #4
0
        public void Add(Value value, bool takeRef = true)
        {
            ValString str = value as ValString;

            if (takeRef)
            {
                value?.Ref();
            }
            values.Add(value);
        }
Beispiel #5
0
        public Value GetLocal(ValString identifier, Value defaultValue = null)
        {
            Value result;

            if (variables != null && variables.TryGetValue(identifier, out result))
            {
                return(result);
            }
            return(defaultValue);
        }
Beispiel #6
0
        /// <summary>
        /// Convenience method to check whether the map contains a given string key.
        /// </summary>
        /// <param name="identifier">string key to check for</param>
        /// <returns>true if the map contains that key; false otherwise</returns>
        public bool ContainsKey(string identifier)
        {
            if (TryGetInternalBuiltIn(identifier, out Value existing))
            {
                return(existing != null);
            }
            var  idVal  = ValString.Create(identifier);
            bool result = map.ContainsKey(idVal);

            idVal.Unref();
            return(result);
        }
Beispiel #7
0
 /// <summary>
 /// Accessor to get/set on element of this map by a string key, walking
 /// the __isa chain as needed.  (Note that if you want to avoid that, then
 /// simply look up your value in .map directly.)
 /// </summary>
 /// <param name="identifier">string key to get/set</param>
 /// <returns>value associated with that key</returns>
 public Value this [string identifier] {
     //TODO I think we might unecessarily be walking up the _isa chain here
     get {
         var   idVal  = ValString.Create(identifier);
         Value result = Lookup(idVal);
         idVal.Unref();
         return(result);
     }
     set {
         SetElem(identifier, value, true);
     }
 }
        public Value GetElem(Value index)
        {
            var i = index.IntValue();

            if (i < 0)
            {
                i += value.Length;
            }
            if (i < 0 || i >= value.Length)
            {
                throw new IndexException("Index Error (string index " + index + " out of range)");
            }
            return(ValString.Create(value.Substring(i, 1)));
        }
Beispiel #9
0
        public Value GetVar(Value identifier)
        {
            // check for a local variable
            Value result;

            if (variables != null && variables.TryGetValue(identifier, out result))
            {
                return(result);
            }

            // check for a module variable
            if (outerVars != null && outerVars.TryGetValue(identifier, out result))
            {
                return(result);
            }

            // OK, we don't have a local or module variable with that name.
            // Check the global scope (if that's not us already).
            if (parent != null)
            {
                Context globals = root;
                if (globals.variables != null && globals.variables.ContainsKey(identifier))
                {
                    return(globals.variables[identifier]);
                }
            }

            // Finally, check intrinsics.
            ValString identStrVal = identifier as ValString;
            string    identStr    = identStrVal == null ? string.Empty : identStrVal.value;

            if (!string.IsNullOrEmpty(identStr))
            {
                Intrinsic intrinsic = Intrinsic.GetByName(identStr);
                if (intrinsic != null)
                {
                    return(intrinsic.GetFunc());
                }
            }

            // No luck there either?  Undefined identifier.
            throw new UndefinedIdentifierException(identStr);
        }
        public override Value Val(Context context, out ValMap valueFoundIn)
        {
            valueFoundIn = null;
            Value     idxVal    = index == null ? null : index.Val(context, false);
            ValString idxValStr = idxVal as ValString;

            if (idxValStr != null)
            {
                return(Resolve(sequence, idxValStr.value, context, out valueFoundIn));
            }
            // Ok, we're searching for something that's not a string;
            // this can only be done in maps and lists (and lists, only with a numeric index).
            Value baseVal        = sequence.Val(context, false);
            int   baseValTypeInt = baseVal.GetBaseMiniscriptType();

            if (baseValTypeInt == MiniscriptTypeInts.ValMapTypeInt)
            {
                Value result = ((ValMap)baseVal).Lookup(idxVal, out valueFoundIn);
                if (valueFoundIn == null)
                {
                    throw new KeyException(idxVal.CodeForm(context.vm, 1));
                }
                return(result);
                //} else if (baseVal is ValList && idxVal is ValNumber) {
            }
            else if (baseValTypeInt == MiniscriptTypeInts.ValListTypeInt && idxVal is ValNumber)
            {
                return(((ValList)baseVal).GetElem(idxVal));
                //} else if (baseVal is ValString && idxVal is ValNumber) {
            }
            else if (baseValTypeInt == MiniscriptTypeInts.ValStringTypeInt && idxVal is ValNumber)
            {
                return(((ValString)baseVal).GetElem(idxVal));
            }
            else if (baseValTypeInt == MiniscriptTypeInts.ValCustomTypeInt)
            {
                ValCustom custom = baseVal as ValCustom;
                return(custom.Lookup(idxVal));
            }

            throw new TypeException("Type Exception: can't index into this type");
        }
Beispiel #11
0
        public void SetVar(string identifier, Value value)
        {
            if (identifier == "globals" || identifier == "locals")
            {
                throw new RuntimeException("can't assign to " + identifier);
            }

            if (variables == null)
            {
                variables = ValMap.Create();
            }
            var identifierStr = ValString.Create(identifier);

            if (variables.assignOverride == null || !variables.assignOverride(identifierStr, value))
            {
                //variables[identifier] = value;
                variables.SetElem(identifier, value, false);
                //variables.SetElem(identifier, value, true);
            }
            identifierStr.Unref();
        }
Beispiel #12
0
        public bool Remove(Value keyVal)
        {
            // Check against common entries first, for perf
            ValString indexStr = keyVal as ValString;
            Value     existing;

            if (indexStr != null)
            {
                if (TrySetInternalBuiltIn(indexStr, null, out existing))
                {
                    // We return true only if we had an existing value
                    if (existing != null)
                    {
                        _isCountDirty  = true;
                        _isKeysDirty   = true;
                        _isValuesDirty = true;
                        return(true);
                    }
                    return(false);
                }
            }
            // Pull the current key/value so that we can unref it
            if (map.TryGetValue(keyVal, out existing))
            {
                existing.Unref();
                // Try to get the key that's there and unref it
                Value existingKey = RemoveBySwap(_mapKeys, keyVal);
                if (existingKey != null)
                {
                    existingKey.Unref();
                }
                map.Remove(keyVal);
                _isCountDirty  = true;
                _isKeysDirty   = true;
                _isValuesDirty = true;
                return(true);
            }
            return(false);
        }
        public override bool Resolve(string identifier, out Value ret)
        {
            switch (identifier)
            {
            case NumAValueName:
                ret = ValNumber.Create(NumA);
                return(true);

            case StringBValueName:
                ret = ValString.Create(StrB);
                return(true);

            case InverseValueName:
                ret = Inverse();
                return(true);

            case IsPotatoFunction:
                ret = _isPotatoFunction.GetFunc();
                return(true);
            }
            ret = null;
            return(false);
        }
        public static void InitializeIntrinsics()
        {
            if (_hasStaticInit)
            {
                return;
            }
            _hasStaticInit = true;
            // Load the constructor
            Intrinsic ctor = Intrinsic.Create("ExampleCustom");

            ctor.AddParam(NumAValueName, 0.0);
            ctor.AddParam(StringBValueName, "");
            ctor.code = (context, partialResult) =>
            {
                ValNumber numA = context.GetVar(NumAValueName) as ValNumber;
                ValString strB = context.GetVar(StringBValueName) as ValString;

                ExampleCustomVal customVal = new ExampleCustomVal(
                    numA != null ? (float)numA.value : 0,
                    strB != null ? strB.value : "");

                return(new Intrinsic.Result(customVal));
            };

            _isPotatoFunction = Intrinsic.Create(IsPotatoFunction, false);
            _isPotatoFunction.AddParam("item");
            _isPotatoFunction.code = (context, partialResult) =>
            {
                ValString str = context.GetVar("item") as ValString;
                if (str != null && str.value == "potato")
                {
                    return(Intrinsic.Result.True);
                }
                return(Intrinsic.Result.False);
            };
        }
Beispiel #15
0
        public void SetElem(Value index, Value value, bool takeValueRef)
        {
            var i = index.IntValue();

            if (i < 0)
            {
                i += Count;
            }
            if (i < 0 || i >= Count)
            {
                throw new IndexException("Index Error (list index " + index + " out of range)");
            }
            i += StartIndex;
            ValString str = value as ValString;

            // Unref existing
            values[i]?.Unref();
            // Ref new
            if (takeValueRef)
            {
                value?.Ref();
            }
            values[i] = value;
        }
Beispiel #16
0
        private bool TryGetInternalBuiltIn(Value identifier, out Value value)
        {
            ValString str = identifier as ValString;

            if (str == null)
            {
                value = null;
                return(false);
            }
            // If the ValString wasn't made by ValString as a built-in, we
            // can exit early
            if (!str.IsBuiltIn)
            {
                value = null;
                return(false);
            }
            // Internally, unity turns string switch case into a dictionary
            // which is fast, but working with the hashes can be sorta slow
            // so we just use the instance ID on the val string, as int
            // comparison is very fast
            //TODO this could be a static BST or a hash map!
            int recvID = str.InstanceID;

            if (recvID == ValString.selfStr.InstanceID)
            {
                value = selfVal;
                return(true);
            }
            if (recvID == ValString.magicIsA.InstanceID)
            {
                value = isaVal;
                return(true);
            }
            if (recvID == ValString.eventsStr.InstanceID)
            {
                value = eventsVal;
                return(true);
            }
            if (recvID == ValString.eventValsStr.InstanceID)
            {
                value = eventValsVal;
                return(true);
            }
            if (recvID == ValString.isAtEndStr.InstanceID)
            {
                value = isAtEndVal;
                return(true);
            }
            if (recvID == ValString.xStr.InstanceID)
            {
                value = xVal;
                return(true);
            }
            if (recvID == ValString.yStr.InstanceID)
            {
                value = yVal;
                return(true);
            }
            if (recvID == ValString.zStr.InstanceID)
            {
                value = zVal;
                return(true);
            }
            if (recvID == ValString.wStr.InstanceID)
            {
                value = wVal;
                return(true);
            }
            if (recvID == ValString.nameStr.InstanceID)
            {
                value = nameVal;
                return(true);
            }
            if (recvID == ValString.positionStr.InstanceID)
            {
                value = positionVal;
                return(true);
            }
            if (recvID == ValString.rotationStr.InstanceID)
            {
                value = rotationVal;
                return(true);
            }
            if (recvID == ValString.velocityStr.InstanceID)
            {
                value = velocityVal;
                return(true);
            }
            if (recvID == ValString.angularVelocityStr.InstanceID)
            {
                value = angularVelocityVal;
                return(true);
            }
            if (recvID == ValString.forwardStr.InstanceID)
            {
                value = forwardVal;
                return(true);
            }
            if (recvID == ValString.rightStr.InstanceID)
            {
                value = rightVal;
                return(true);
            }
            if (recvID == ValString.timeStr.InstanceID)
            {
                value = timeVal;
                return(true);
            }
            if (recvID == ValString.deltaTimeStr.InstanceID)
            {
                value = deltaTimeVal;
                return(true);
            }
            if (recvID == ValString.frameCountStr.InstanceID)
            {
                value = frameCountVal;
                return(true);
            }
            value = null;
            return(false);
        }
Beispiel #17
0
        private bool TrySetInternalBuiltIn(Value identifier, Value value, out Value previousVal)
        {
            ValString str = identifier as ValString;

            if (str == null)
            {
                previousVal = null;
                return(false);
            }
            // If the ValString wasn't made by ValString as a built-in, we
            // can exit early
            if (!str.IsBuiltIn)
            {
                previousVal = null;
                return(false);
            }
            int recvID = str.InstanceID;

            if (recvID == ValString.selfStr.InstanceID)
            {
                previousVal = selfVal;
                selfVal?.Unref();
                selfVal = value;
                return(true);
            }
            if (recvID == ValString.magicIsA.InstanceID)
            {
                previousVal = isaVal;
                isaVal?.Unref();
                isaVal = value;
                return(true);
            }
            if (recvID == ValString.eventsStr.InstanceID)
            {
                previousVal = eventsVal;
                eventsVal?.Unref();
                eventsVal = value;
                return(true);
            }
            if (recvID == ValString.eventValsStr.InstanceID)
            {
                previousVal = eventValsVal;
                eventValsVal?.Unref();
                eventValsVal = value;
                return(true);
            }
            if (recvID == ValString.isAtEndStr.InstanceID)
            {
                previousVal = isAtEndVal;
                isAtEndVal?.Unref();
                isAtEndVal = value;
                return(true);
            }
            if (recvID == ValString.xStr.InstanceID)
            {
                previousVal = xVal;
                xVal?.Unref();
                xVal = value;
                return(true);
            }
            if (recvID == ValString.yStr.InstanceID)
            {
                previousVal = yVal;
                yVal?.Unref();
                yVal = value;
                return(true);
            }
            if (recvID == ValString.zStr.InstanceID)
            {
                previousVal = zVal;
                zVal?.Unref();
                zVal = value;
                return(true);
            }
            if (recvID == ValString.wStr.InstanceID)
            {
                previousVal = wVal;
                wVal?.Unref();
                wVal = value;
                return(true);
            }
            if (recvID == ValString.nameStr.InstanceID)
            {
                previousVal = nameVal;
                nameVal?.Unref();
                nameVal = value;
                return(true);
            }
            if (recvID == ValString.positionStr.InstanceID)
            {
                previousVal = positionVal;
                positionVal?.Unref();
                positionVal = value;
                return(true);
            }
            if (recvID == ValString.rotationStr.InstanceID)
            {
                previousVal = rotationVal;
                rotationVal?.Unref();
                rotationVal = value;
                return(true);
            }
            if (recvID == ValString.velocityStr.InstanceID)
            {
                previousVal = velocityVal;
                velocityVal?.Unref();
                velocityVal = value;
                return(true);
            }
            if (recvID == ValString.angularVelocityStr.InstanceID)
            {
                previousVal = angularVelocityVal;
                angularVelocityVal?.Unref();
                angularVelocityVal = value;
                return(true);
            }
            if (recvID == ValString.forwardStr.InstanceID)
            {
                previousVal = forwardVal;
                forwardVal?.Unref();
                forwardVal = value;
                return(true);
            }
            if (recvID == ValString.rightStr.InstanceID)
            {
                previousVal = rightVal;
                rightVal?.Unref();
                rightVal = value;
                return(true);
            }
            if (recvID == ValString.timeStr.InstanceID)
            {
                previousVal = timeVal;
                timeVal?.Unref();
                timeVal = value;
                return(true);
            }
            if (recvID == ValString.deltaTimeStr.InstanceID)
            {
                previousVal = deltaTimeVal;
                deltaTimeVal?.Unref();
                deltaTimeVal = value;
                return(true);
            }
            if (recvID == ValString.frameCountStr.InstanceID)
            {
                previousVal = frameCountVal;
                frameCountVal?.Unref();
                frameCountVal = value;
                return(true);
            }
            previousVal = null;
            return(false);
        }
 public static ValString Str(string value)
 {
     return(ValString.Create(value));
 }
Beispiel #19
0
        public void SetElem(Value index, Value value, bool takeValueRef, bool takeIndexRef = true)
        {
            ValNumber newNum = value as ValNumber;

            //Console.WriteLine("Map set elem " + index.ToString() + ": " + value.ToString());
            if (takeValueRef)
            {
                value?.Ref();
            }
            if (takeIndexRef)
            {
                index?.Ref();
            }
            if (index == null)
            {
                index = ValNull.instance;
            }

            if (assignOverride == null || !assignOverride(index, value))
            {
                // Check against common entries first, for perf
                ValString indexStr = index as ValString;
                if (indexStr != null)
                {
                    // We want to replicate the behavior of a map, so to
                    // preserve the way that you can set a key to null, we
                    // simply store a ValNull here, and pull out a ValNull
                    // later but just return a null
                    Value builtInVal = value ?? ValNull.instance;
                    if (TrySetInternalBuiltIn(indexStr, builtInVal, out Value oldVal))
                    {
                        // If we're overwriting a value, keep count/keys
                        if (oldVal != null)
                        {
                            _isValuesDirty = true;
                        }
                        else
                        {
                            _isCountDirty  = true;
                            _isKeysDirty   = true;
                            _isValuesDirty = true;
                        }
                        return;
                    }
                }

                if (map.TryGetValue(index, out Value existing))
                {
                    // Unref the value that's currently there
                    if (existing != null)
                    {
                        existing.Unref(); // There can be null entries in this list
                    }
                    // Try to get the key that's there and unref it
                    Value existingKey = RemoveBySwap(_mapKeys, index);
                    map.Remove(existingKey);
                    if (existingKey != null)
                    {
                        existingKey.Unref();
                    }

                    // Overwrote value, count didn't change but keys/values did
                    _isKeysDirty   = true;
                    _isValuesDirty = true;
                }
                else
                {
                    _isCountDirty  = true;
                    _isKeysDirty   = true;
                    _isValuesDirty = true;
                }
                _mapKeys.Add(index);
                map[index] = value;
            }
        }
        //TODO add create with ValString for fast add
        public static ValString Create(string val, bool usePool = true)
        {
            if (!usePool)
            {
                return(new ValString(val, false));
            }

            switch (val)
            {
            case "s":
                return(sStr);

            case "self":
                return(selfStr);

            case "to":
                return(toStr);

            case "from":
                return(fromStr);

            case "__isa":
                return(magicIsA);

            case "seq":
                return(seqStr);

            case "super":
                return(superStr);

            case "len":
                return(lenStr);

            case "__events":
                return(eventsStr);

            case "__eventVals":
                return(eventValsStr);

            case "__isAtEnd":
                return(isAtEndStr);

            case "yield":
                return(yieldStr);

            case " ":
                return(spaceStr);

            case "x":
                return(xStr);

            case "y":
                return(yStr);

            case "z":
                return(zStr);

            case "w":
                return(wStr);

            case "name":
                return(nameStr);

            case "position":
                return(positionStr);

            case "rotation":
                return(rotationStr);

            case "velocity":
                return(velocityStr);

            case "angularVelocity":
                return(angularVelocityStr);

            case "forward":
                return(forwardStr);

            case "right":
                return(rightStr);

            case "time":
                return(timeStr);

            case "deltaTime":
                return(deltaTimeStr);

            case "frameCount":
                return(frameCountStr);
            }

            //Console.WriteLine("Alloc str \"" + val + "\" num " + _num);

            if (_valuePool == null)
            {
                _valuePool = new ValuePool <ValString>();
            }
            else
            {
                ValString valStr = _valuePool.GetInstance();
                if (valStr != null)
                {
                    valStr._refCount = 1;
                    valStr.value     = val;
#if MINISCRIPT_DEBUG
                    valStr._id = _num++;
#endif
                    return(valStr);
                }
            }

#if MINISCRIPT_DEBUG
            _numInstancesAllocated++;
#endif
            return(new ValString(val, true));
        }
Beispiel #21
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);
        }
        /// <summary>
        /// Look up the given identifier in the given sequence, walking the type chain
        /// until we either find it, or fail.
        /// </summary>
        /// <param name="sequence">Sequence (object) to look in.</param>
        /// <param name="identifier">Identifier to look for.</param>
        /// <param name="context">Context.</param>
        public static Value Resolve(Value sequence, string identifier, Context context, out ValMap valueFoundIn)
        {
            var includeMapType = true;

            valueFoundIn = null;
            int loopsLeft = 1000;                       // (max __isa chain depth)

            while (sequence != null)
            {
                int seqTypeInt = sequence.GetBaseMiniscriptType();

                //if (sequence is ValTemp || sequence is ValVar)
                if (seqTypeInt == MiniscriptTypeInts.ValTempTypeInt || seqTypeInt == MiniscriptTypeInts.ValVarTypeInt)
                {
                    sequence   = sequence.Val(context, false);
                    seqTypeInt = sequence == null ? -1 : sequence.GetBaseMiniscriptType();
                }
                //if (sequence is ValMap) {
                if (seqTypeInt == MiniscriptTypeInts.ValMapTypeInt)
                {
                    // If the map contains this identifier, return its value.
                    ValMap seqMap = sequence as ValMap;
                    Value  result = null;
                    var    idVal  = ValString.Create(identifier);
                    bool   found  = seqMap.TryGetValue(idVal, out result);
                    idVal.Unref();
                    if (found)
                    {
                        valueFoundIn = seqMap;
                        return(result);
                    }

                    // Otherwise, if we have an __isa, try that next.
                    if (loopsLeft < 0)
                    {
                        return(null);        // (unless we've hit the loop limit)
                    }
                    if (!seqMap.TryGetValue(ValString.magicIsA, out sequence))
                    {
                        // ...and if we don't have an __isa, try the generic map type if allowed
                        if (!includeMapType)
                        {
                            throw new KeyException(identifier);
                        }
                        sequence       = context.vm.mapType ?? Intrinsics.MapType();
                        includeMapType = false;
                    }
                    //} else if (sequence is ValList) {
                }
                else if (seqTypeInt == MiniscriptTypeInts.ValListTypeInt)
                {
                    sequence       = context.vm.listType ?? Intrinsics.ListType();
                    includeMapType = false;
                    //} else if (sequence is ValString) {
                }
                else if (seqTypeInt == MiniscriptTypeInts.ValStringTypeInt)
                {
                    sequence       = context.vm.stringType ?? Intrinsics.StringType();
                    includeMapType = false;
                    //} else if (sequence is ValNumber) {
                }
                else if (seqTypeInt == MiniscriptTypeInts.ValNumberTypeInt)
                {
                    sequence       = context.vm.numberType ?? Intrinsics.NumberType();
                    includeMapType = false;
                    //} else if (sequence is ValFunction) {
                }
                else if (seqTypeInt == MiniscriptTypeInts.ValFunctionTypeInt)
                {
                    sequence       = context.vm.functionType ?? Intrinsics.FunctionType();
                    includeMapType = false;
                }
                else if (seqTypeInt == MiniscriptTypeInts.ValCustomTypeInt)
                {
                    ValCustom custom = sequence as ValCustom;
                    if (custom.Resolve(identifier, out Value result))
                    {
                        //valueFoundIn
                        return(result);
                    }
                    return(null);
                }
                else
                {
                    throw new TypeException("Type Error (while attempting to look up " + identifier + ")");
                }
                loopsLeft--;
            }
            return(null);
        }