dec99505a6
This diff refactors some of the VM's logic for iterators (with a focus on
mutable iteration), delivering several improvements:
1) MIterCtx was renamed to MArrayIter, and the m_key and m_val fields
were eliminated.
2) Eliminated the need for MArrayIter to dynamically allocate a
MutableArrayIter object, and removed other layers of indirection as
well.
3) Reduced the size of HPHP::VM::Iter from 64 bytes down to 32 bytes.
4) Removed the "if (siPastEnd())" check when adding a new element to an
HphpArray or a ZendArray.
5) Moved all of the iterator logic into a single .cpp file.
This diff reworks FullPos's to point to current element instead of pointing
to the next element. It also splits up the IterFree instruction into two
instructions (IterFree and MIterFree). These changes allowed various logic
to be simplified and data structures to be reduced in size. There is
definitely more opportunity for refactoring, but I know the JIT helpers for
iteration have been carefully tuned and so I'll leave further refactoring
for future diffs.
Finally, I spent a little time cleaning up the bytecode spec a bit, mostly
with respect to iteration.
630 linhas
17 KiB
C++
630 linhas
17 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <runtime/base/array/array_data.h>
|
|
#include <runtime/base/array/array_init.h>
|
|
#include <runtime/base/array/array_iterator.h>
|
|
#include <runtime/base/type_conversions.h>
|
|
#include <runtime/base/builtin_functions.h>
|
|
#include <runtime/base/complex_types.h>
|
|
#include <runtime/base/variable_serializer.h>
|
|
#include <runtime/base/array/zend_array.h>
|
|
#include <runtime/base/array/vector_array.h>
|
|
#include <runtime/base/runtime_option.h>
|
|
#include <runtime/base/macros.h>
|
|
#include <util/exception.h>
|
|
#include <tbb/concurrent_hash_map.h>
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
typedef tbb::concurrent_hash_map<const StringData *, ArrayData *,
|
|
StringDataHashCompare> ArrayDataMap;
|
|
static ArrayDataMap s_arrayDataMap;
|
|
|
|
ArrayData *ArrayData::GetScalarArray(ArrayData *arr,
|
|
const StringData *key /* = NULL */) {
|
|
if (key == NULL) {
|
|
key = StringData::GetStaticString(f_serialize(arr).get());
|
|
} else {
|
|
assert(key->isStatic());
|
|
assert(key->same(f_serialize(arr).get()));
|
|
}
|
|
ArrayDataMap::accessor acc;
|
|
if (s_arrayDataMap.insert(acc, key)) {
|
|
ArrayData *ad = arr->nonSmartCopy();
|
|
ad->setStatic();
|
|
ad->onSetEvalScalar();
|
|
acc->second = ad;
|
|
}
|
|
return acc->second;
|
|
}
|
|
|
|
// In general, arrays can contain int-valued-strings, even though
|
|
// plain array access converts them to integers. non-int-string
|
|
// assersions should go upstream of the ArrayData api.
|
|
|
|
bool ArrayData::IsValidKey(litstr k) {
|
|
return k != NULL;
|
|
}
|
|
|
|
bool ArrayData::IsValidKey(const StringData* k) {
|
|
return k != NULL;
|
|
}
|
|
|
|
bool ArrayData::IsValidKey(CStrRef k) {
|
|
return IsValidKey(k.get());
|
|
}
|
|
|
|
bool ArrayData::IsValidKey(CVarRef k) {
|
|
return k.isInteger() ||
|
|
(k.isString() && IsValidKey(k.getStringData()));
|
|
}
|
|
|
|
// constructors/destructors
|
|
|
|
HOT_FUNC
|
|
ArrayData *ArrayData::Create() {
|
|
if (enable_vector_array && RuntimeOption::UseVectorArray) {
|
|
return StaticEmptyVectorArray::Get();
|
|
}
|
|
return ArrayInit((ssize_t)0).create();
|
|
}
|
|
|
|
HOT_FUNC
|
|
ArrayData *ArrayData::Create(CVarRef value) {
|
|
if (enable_vector_array && RuntimeOption::UseVectorArray) {
|
|
VectorArray *va = NEW(VectorArray)(1);
|
|
va->VectorArray::append(value, false);
|
|
va->m_pos = 0;
|
|
return va;
|
|
}
|
|
ArrayInit init(1);
|
|
init.set(value);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::Create(CVarRef name, CVarRef value) {
|
|
ArrayInit init(1);
|
|
// There is no toKey() call on name.
|
|
init.set(name, value, true);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::CreateRef(CVarRef value) {
|
|
if (enable_vector_array && RuntimeOption::UseVectorArray) {
|
|
VectorArray *va = NEW(VectorArray)(1);
|
|
va->VectorArray::appendRef(value, false);
|
|
va->m_pos = 0;
|
|
return va;
|
|
}
|
|
ArrayInit init(1);
|
|
init.setRef(value);
|
|
return init.create();
|
|
}
|
|
|
|
ArrayData *ArrayData::CreateRef(CVarRef name, CVarRef value) {
|
|
ArrayInit init(1);
|
|
// There is no toKey() call on name.
|
|
init.setRef(name, value, true);
|
|
return init.create();
|
|
}
|
|
|
|
HOT_FUNC
|
|
ArrayData::~ArrayData() {
|
|
// If there are any strong iterators pointing to this array, they need
|
|
// to be invalidated.
|
|
if (strongIterators()) {
|
|
freeStrongIterators();
|
|
}
|
|
}
|
|
|
|
ArrayData *ArrayData::nonSmartCopy() const {
|
|
throw FatalErrorException("nonSmartCopy not implemented.");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// reads
|
|
|
|
Object ArrayData::toObject() const {
|
|
return hhvm
|
|
? VM::Instance::FromArray(const_cast<ArrayData *>(this))
|
|
: ObjectData::FromArray(const_cast<ArrayData *>(this));
|
|
}
|
|
|
|
bool ArrayData::isVectorData() const {
|
|
for (ssize_t i = 0, n = size(); i < n; i++) {
|
|
if (getIndex(i) != i) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int ArrayData::compare(const ArrayData *v2) const {
|
|
assert(v2);
|
|
|
|
int count1 = size();
|
|
int count2 = v2->size();
|
|
if (count1 < count2) return -1;
|
|
if (count1 > count2) return 1;
|
|
if (count1 == 0) return 0;
|
|
|
|
// prevent circular referenced objects/arrays or deep ones
|
|
DECLARE_THREAD_INFO; check_recursion(info);
|
|
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
Variant key(iter.first());
|
|
if (!v2->exists(key)) return 1;
|
|
|
|
Variant value1(iter.second());
|
|
Variant value2(v2->get(key));
|
|
if (value1.more(value2)) return 1;
|
|
if (value1.less(value2)) return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ArrayData::equal(const ArrayData *v2, bool strict) const {
|
|
assert(v2);
|
|
|
|
int count1 = size();
|
|
int count2 = v2->size();
|
|
if (count1 != count2) return false;
|
|
if (count1 == 0) return true;
|
|
|
|
// prevent circular referenced objects/arrays or deep ones
|
|
DECLARE_THREAD_INFO; check_recursion(info);
|
|
|
|
if (strict) {
|
|
for (ArrayIter iter1(this), iter2(v2); iter1 && iter2; ++iter1, ++iter2) {
|
|
Variant key1(iter1.first());
|
|
Variant key2(iter2.first());
|
|
if (!key1.same(key2)) return false;
|
|
|
|
Variant value1(iter1.second());
|
|
Variant value2(iter2.second());
|
|
if (!value1.same(value2)) return false;
|
|
}
|
|
} else {
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
Variant key(iter.first());
|
|
if (!v2->exists(key)) return false;
|
|
|
|
Variant value1(iter.second());
|
|
Variant value2(v2->get(key));
|
|
if (!value1.equal(value2)) return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ArrayData *ArrayData::lvalPtr(StringData* k, Variant *&ret, bool copy,
|
|
bool create) {
|
|
throw FatalErrorException("Unimplemented ArrayData::lvalPtr");
|
|
}
|
|
|
|
ArrayData *ArrayData::lvalPtr(int64 k, Variant *&ret, bool copy,
|
|
bool create) {
|
|
throw FatalErrorException("Unimplemented ArrayData::lvalPtr");
|
|
}
|
|
|
|
ArrayData *ArrayData::add(int64 k, CVarRef v, bool copy) {
|
|
assert(!exists(k));
|
|
return set(k, v, copy);
|
|
}
|
|
|
|
ArrayData *ArrayData::add(StringData* k, CVarRef v, bool copy) {
|
|
assert(!exists(k));
|
|
return set(k, v, copy);
|
|
}
|
|
|
|
ArrayData *ArrayData::addLval(int64 k, Variant *&ret, bool copy) {
|
|
assert(!exists(k));
|
|
return lval(k, ret, copy);
|
|
}
|
|
|
|
ArrayData *ArrayData::addLval(StringData* k, Variant *&ret, bool copy) {
|
|
assert(!exists(k));
|
|
return lval(k, ret, copy);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// stack and queue operations
|
|
|
|
ArrayData *ArrayData::pop(Variant &value) {
|
|
if (!empty()) {
|
|
ssize_t pos = iter_end();
|
|
value = getValue(pos);
|
|
return remove(getKey(pos), getCount() > 1);
|
|
}
|
|
value = null;
|
|
return NULL;
|
|
}
|
|
|
|
ArrayData *ArrayData::dequeue(Variant &value) {
|
|
if (!empty()) {
|
|
ssize_t pos = iter_begin();
|
|
value = getValue(pos);
|
|
ArrayData *ret = remove(getKey(pos), getCount() > 1);
|
|
|
|
// In PHP, array_shift() will cause all numerically key-ed values re-keyed
|
|
if (ret) {
|
|
ret->renumber();
|
|
} else {
|
|
renumber();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
value = null;
|
|
return NULL;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// MutableArrayIter related functions
|
|
|
|
void ArrayData::newFullPos(FullPos &fp) {
|
|
assert(fp.getContainer() == NULL);
|
|
fp.setContainer(this);
|
|
fp.setNext(strongIterators());
|
|
setStrongIterators(&fp);
|
|
getFullPos(fp);
|
|
}
|
|
|
|
void ArrayData::freeFullPos(FullPos &fp) {
|
|
assert(strongIterators() != 0 && fp.getContainer() == (ArrayData*)this);
|
|
// search for fp in our list, then remove it. Usually its the first one.
|
|
FullPos* p = strongIterators();
|
|
if (p == &fp) {
|
|
setStrongIterators(p->getNext());
|
|
fp.setContainer(NULL);
|
|
return;
|
|
}
|
|
for (; p->getNext(); p = p->getNext()) {
|
|
if (p->getNext() == &fp) {
|
|
p->setNext(p->getNext()->getNext());
|
|
fp.setContainer(NULL);
|
|
return;
|
|
}
|
|
}
|
|
// If the strong iterator list was empty or if fp could not be
|
|
// found in the strong iterator list, then we are in a bad state
|
|
assert(false);
|
|
}
|
|
|
|
bool ArrayData::validFullPos(const FullPos& fp) const {
|
|
assert(fp.getContainer() == (ArrayData*)this);
|
|
return false;
|
|
}
|
|
|
|
void ArrayData::getFullPos(FullPos &fp) {
|
|
assert(fp.getContainer() == (ArrayData*)this);
|
|
fp.m_pos = ArrayData::invalid_index;
|
|
}
|
|
|
|
bool ArrayData::setFullPos(const FullPos &fp) {
|
|
assert(fp.getContainer() == (ArrayData*)this);
|
|
return false;
|
|
}
|
|
|
|
void ArrayData::nextForFullPos() {
|
|
next();
|
|
}
|
|
|
|
void ArrayData::freeStrongIterators() {
|
|
for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) {
|
|
r.front()->setContainer(NULL);
|
|
}
|
|
setStrongIterators(0);
|
|
}
|
|
|
|
void ArrayData::moveStrongIterators(ArrayData* dest, ArrayData* src) {
|
|
for (FullPosRange r(src->strongIterators()); !r.empty(); r.popFront()) {
|
|
r.front()->setContainer(dest);
|
|
}
|
|
// move pointer to list and flag in one copy
|
|
dest->m_strongIterators = src->m_strongIterators;
|
|
src->m_strongIterators = 0;
|
|
}
|
|
|
|
CVarRef ArrayData::currentRef() {
|
|
if (m_pos >= 0 && m_pos < size()) {
|
|
return getValueRef(m_pos);
|
|
}
|
|
throw FatalErrorException("invalid ArrayData::m_pos");
|
|
}
|
|
CVarRef ArrayData::endRef() {
|
|
if (m_pos >= 0 && m_pos < size()) {
|
|
return getValueRef(size() - 1);
|
|
}
|
|
throw FatalErrorException("invalid ArrayData::m_pos");
|
|
}
|
|
ArrayData* ArrayData::escalateForSort() {
|
|
if (getCount() > 1) {
|
|
return copy();
|
|
}
|
|
return this;
|
|
}
|
|
void ArrayData::ksort(int sort_flags, bool ascending) {
|
|
throw FatalErrorException("Unimplemented ArrayData::ksort");
|
|
}
|
|
void ArrayData::sort(int sort_flags, bool ascending) {
|
|
throw FatalErrorException("Unimplemented ArrayData::sort");
|
|
}
|
|
void ArrayData::asort(int sort_flags, bool ascending) {
|
|
throw FatalErrorException("Unimplemented ArrayData::asort");
|
|
}
|
|
void ArrayData::uksort(CVarRef cmp_function) {
|
|
throw FatalErrorException("Unimplemented ArrayData::uksort");
|
|
}
|
|
void ArrayData::usort(CVarRef cmp_function) {
|
|
throw FatalErrorException("Unimplemented ArrayData::usort");
|
|
}
|
|
void ArrayData::uasort(CVarRef cmp_function) {
|
|
throw FatalErrorException("Unimplemented ArrayData::uasort");
|
|
}
|
|
ArrayData* ArrayData::copyWithStrongIterators() const {
|
|
throw FatalErrorException("Unimplemented ArrayData::copyWithStrongIterators");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Default implementation of position-based iterations.
|
|
|
|
Variant ArrayData::reset() { return value(m_pos = 0);}
|
|
Variant ArrayData::prev() { return value(--m_pos);}
|
|
Variant ArrayData::next() { return value(++m_pos);}
|
|
Variant ArrayData::end() { return value(m_pos = size() - 1);}
|
|
|
|
Variant ArrayData::key() const {
|
|
if (m_pos >= 0 && m_pos < size()) {
|
|
return getKey(m_pos);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Variant ArrayData::value(ssize_t &pos) const {
|
|
if (pos >= 0 && pos < size()) {
|
|
return getValue(pos);
|
|
}
|
|
pos = ArrayData::invalid_index;
|
|
return false;
|
|
}
|
|
|
|
Variant ArrayData::current() const {
|
|
if (m_pos >= 0 && m_pos < size()) {
|
|
return getValue(m_pos);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static StaticString s_value("value");
|
|
static StaticString s_key("key");
|
|
|
|
Variant ArrayData::each() {
|
|
if (m_pos >= 0 && m_pos < size()) {
|
|
Array ret;
|
|
Variant key(getKey(m_pos));
|
|
Variant value(getValue(m_pos));
|
|
ret.set(1, value);
|
|
ret.set(s_value, value);
|
|
ret.set(0, key);
|
|
ret.set(s_key, key);
|
|
++m_pos;
|
|
return ret;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_begin() const {
|
|
if (empty()) return ArrayData::invalid_index;
|
|
return 0;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_end() const {
|
|
if (empty()) return ArrayData::invalid_index;
|
|
return size() - 1;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_advance(ssize_t prev) const {
|
|
assert(prev >= 0 && prev < size());
|
|
ssize_t next = prev + 1;
|
|
if (next >= size()) return ArrayData::invalid_index;
|
|
return next;
|
|
}
|
|
|
|
ssize_t ArrayData::iter_rewind(ssize_t prev) const {
|
|
assert(prev >= 0 && prev < size());
|
|
ssize_t next = prev - 1;
|
|
if (next < 0) return ArrayData::invalid_index;
|
|
return next;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers
|
|
|
|
void ArrayData::serializeImpl(VariableSerializer *serializer) const {
|
|
serializer->writeArrayHeader(size(), isVectorData());
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
serializer->writeArrayKey(iter.first());
|
|
serializer->writeArrayValue(iter.secondRef());
|
|
}
|
|
serializer->writeArrayFooter();
|
|
}
|
|
|
|
void ArrayData::serialize(VariableSerializer *serializer,
|
|
bool skipNestCheck /* = false */) const {
|
|
if (size() == 0) {
|
|
serializer->writeArrayHeader(0, isVectorData());
|
|
serializer->writeArrayFooter();
|
|
return;
|
|
}
|
|
if (!skipNestCheck) {
|
|
if (serializer->incNestedLevel((void*)this)) {
|
|
serializer->writeOverflow((void*)this);
|
|
} else {
|
|
serializeImpl(serializer);
|
|
}
|
|
serializer->decNestedLevel((void*)this);
|
|
} else {
|
|
// If isObject, the array is temporary and we should not check or save
|
|
// its pointer.
|
|
serializeImpl(serializer);
|
|
}
|
|
}
|
|
|
|
bool ArrayData::hasInternalReference(PointerSet &vars,
|
|
bool ds /* = false */) const {
|
|
if (isSharedMap()) return false;
|
|
for (ArrayIter iter(this); iter; ++iter) {
|
|
CVarRef var = iter.secondRef();
|
|
if (var.isReferenced()) {
|
|
Variant *pvar = var.getRefData();
|
|
if (vars.find(pvar) != vars.end()) {
|
|
return true;
|
|
}
|
|
vars.insert(pvar);
|
|
}
|
|
if (var.isObject()) {
|
|
ObjectData *pobj = var.getObjectData();
|
|
if (vars.find(pobj) != vars.end()) {
|
|
return true;
|
|
}
|
|
vars.insert(pobj);
|
|
if (ds && pobj->o_instanceof("Serializable")) {
|
|
return true;
|
|
}
|
|
if (pobj->hasInternalReference(vars, ds)) {
|
|
return true;
|
|
}
|
|
} else if (var.isArray() &&
|
|
var.getArrayData()->hasInternalReference(vars, ds)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// nvGet has to search twice when using the ArrayData api so as not to
|
|
// conflate no-key with have-key && value == null_varaint. Subclasses
|
|
// can easily do this with one key search.
|
|
|
|
TypedValue* ArrayData::nvGet(int64 k) const {
|
|
return exists(k) ? (TypedValue*)&get(k, false) :
|
|
NULL;
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGet(const StringData* key) const {
|
|
StrNR k(key);
|
|
return exists(k) ? (TypedValue*)&get(k, false) :
|
|
NULL;
|
|
}
|
|
|
|
void ArrayData::nvGetKey(TypedValue* out, ssize_t pos) {
|
|
Variant k = getKey(pos);
|
|
TypedValue* tv = k.asTypedValue();
|
|
// copy w/out clobbering out->_count.
|
|
out->m_type = tv->m_type;
|
|
out->m_data.num = tv->m_data.num;
|
|
if (tv->m_type != KindOfInt64) out->m_data.pstr->incRefCount();
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetValueRef(ssize_t pos) {
|
|
return const_cast<TypedValue*>(getValueRef(pos).asTypedValue());
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetCell(int64 k) const {
|
|
TypedValue* tv = (TypedValue*)&get(k, false);
|
|
return LIKELY(tv != (TypedValue*)&null_variant) ? tvToCell(tv) :
|
|
nvGetNotFound(k);
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetCell(const StringData* key) const {
|
|
TypedValue* tv = (TypedValue*)&get(StrNR(key), false);
|
|
return LIKELY(tv != (TypedValue*)&null_variant) ? tvToCell(tv) :
|
|
nvGetNotFound(key);
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(int64 k) {
|
|
raise_notice("Undefined index: %"PRId64, k);
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(litstr k) {
|
|
raise_notice("Undefined index: %s", k);
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(CStrRef k) {
|
|
raise_notice("Undefined index: %s", k.data());
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(const StringData* k) {
|
|
raise_notice("Undefined index: %s", k->data());
|
|
return null_variant;
|
|
}
|
|
|
|
CVarRef ArrayData::getNotFound(CVarRef k) {
|
|
raise_notice("Undefined index: %s", k.toString().data());
|
|
return null_variant;
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetNotFound(int64 k) {
|
|
raise_notice("Undefined index: %"PRId64, k);
|
|
return (TypedValue*)&init_null_variant;
|
|
}
|
|
|
|
TypedValue* ArrayData::nvGetNotFound(const StringData* k) {
|
|
raise_notice("Undefined index: %s", k->data());
|
|
return (TypedValue*)&init_null_variant;
|
|
}
|
|
|
|
void ArrayData::dump() {
|
|
string out; dump(out); fwrite(out.c_str(), out.size(), 1, stdout);
|
|
}
|
|
|
|
void ArrayData::dump(std::string &out) {
|
|
VariableSerializer vs(VariableSerializer::VarDump);
|
|
String ret(vs.serialize(Array(this), true));
|
|
out += "ArrayData(";
|
|
out += boost::lexical_cast<string>(_count);
|
|
out += "): ";
|
|
out += string(ret.data(), ret.size());
|
|
}
|
|
|
|
void ArrayData::dump(std::ostream &out) {
|
|
unsigned int i = 0;
|
|
for (ArrayIter iter(this); iter; ++iter, i++) {
|
|
VariableSerializer vs(VariableSerializer::Serialize);
|
|
Variant key(iter.first());
|
|
out << i << " #### " << key.toString()->toCPPString() << " #### ";
|
|
Variant val(iter.second());
|
|
try {
|
|
String valS(vs.serialize(val, true));
|
|
out << valS->toCPPString();
|
|
} catch (const Exception &e) {
|
|
out << "Exception: " << e.what();
|
|
}
|
|
out << std::endl;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|