public static ValList Create(int capacity = 0) { //Console.WriteLine("ValList create cap = " + capacity + " ID " + _num); if (_valuePool == null) { _valuePool = new ValuePool <ValList>(); } else { ValList existing = _valuePool.GetInstance(); if (existing != null) { existing._refCount = 1; existing.EnsureCapacity(capacity); #if MINISCRIPT_DEBUG existing._id = _num++; #endif return(existing); } } #if MINISCRIPT_DEBUG _numInstancesAllocated++; #endif return(new ValList(capacity, true)); }
public ValList EvalCopy(TAC.Context context) { // Create a copy of this list, evaluating its members as we go. // This is used when a list literal appears in the source, to // ensure that each time that code executes, we get a new, distinct // mutable object, rather than the same object multiple times. var result = new ValList(); for (var i = 0; i < values.Count; i++) { result.values.Add(values[i] == null ? null : values[i].Val(context)); } return(result); }
public ValList EvalCopy(Context context) { // Create a copy of this list, evaluating its members as we go. // This is used when a list literal appears in the source, to // ensure that each time that code executes, we get a new, distinct // mutable object, rather than the same object multiple times. var result = ValList.Create(values.Count); for (var i = StartIndex; i < values.Count; i++) { // Sometimes Val is a ValTemp that returns a value that should be reffed // so we Val without Refing, then Ref during Add result.Add(values[i] == null ? null : values[i].Val(context, false), true); } return(result); }
public override Value FullEval(TAC.Context context) { // Evaluate each of our list elements, and if any of those is // a variable or temp, then resolve those now. // CAUTION: do not mutate our original list! We may need // it in its original form on future iterations. ValList result = null; for (int i = 0; i < values.Count; i++) { bool copied = false; if (values[i] is ValTemp || values[i] is ValVar) { Value newVal = values[i].Val(context); if (newVal != values[i]) { // OK, something changed, so we're going to need a new copy of the list. if (result == null) { result = new ValList(); for (int j = 0; j < i; j++) { result.values.Add(values[j]); } } result.values.Add(newVal); copied = true; } } if (!copied && result != null) { // No change; but we have new results to return, so copy it as-is result.values.Add(values[i]); } } if (result != null) { return(result); } return(this); }
public override double Equality(Value rhs, int recursionDepth = 16) { if (!(rhs is ValList)) { return(0); } ValList rList = rhs as ValList; List <Value> rhl = rList.values; int rStart = rList.StartIndex; if (rhl == values) { return(1); // (same list) } int count = Count; if (count != rhl.Count) { return(0); } if (recursionDepth < 1) { return(0.5); // in too deep } double result = 1; for (var i = 0; i < Count; i++) { result *= values[StartIndex + i].Equality(rhl[rStart + i], recursionDepth - 1); if (result <= 0) { break; } } return(result); }
/// <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); }