/** * Copyright - See the COPYRIGHT that is included with this distribution. * pvxs is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. */ #include #include #include #include "dataimpl.h" #include "utilpvt.h" namespace pvxs { NoField::NoField() :std::runtime_error ("No such field") {} NoField::~NoField() {} NoConvert::NoConvert() :std::runtime_error ("No conversion defined") {} NoConvert::~NoConvert() {} std::shared_ptr Value::Helper::type(const Value& v) { if(v) { return std::shared_ptr(v.store->top->desc, v.desc); } else { return nullptr; } } Value Value::Helper::build(const void* ptr, StoreType type) { TypeCode base{TypeCode::Null}; switch (type) { case StoreType::Bool: base = TypeCode::Bool; break; case StoreType::Integer: base = TypeCode::Int64; break; case StoreType::UInteger: base = TypeCode::UInt64; break; case StoreType::Real: base = TypeCode::Float64; break; case StoreType::String: base = TypeCode::String; break; case StoreType::Array: { auto& arr = *static_cast*>(ptr); switch(arr.original_type()) { #define CASE(TYPE) case ArrayType::TYPE: base = TypeCode::TYPE ## A; break CASE(Bool); CASE(Int8); CASE(Int16); CASE(Int32); CASE(Int64); CASE(UInt8); CASE(UInt16); CASE(UInt32); CASE(UInt64); CASE(Float32); CASE(Float64); CASE(String); #undef CASE case ArrayType::Value: base = TypeCode::AnyA; break; case ArrayType::Null: throw std::logic_error("Unable to infer ArrayType::Null"); } } break; case StoreType::Compound: { auto src = *reinterpret_cast(ptr); if(src) { auto dst = TypeDef(src).create(); dst.assign(src); return dst; } } base = TypeCode::Any; break; case StoreType::Null: throw std::logic_error("Unable to infer ArrayType::Null"); } Value ret(TypeDef(base).create()); ret.copyIn(ptr, type); return ret; } Value::Value(const std::shared_ptr& desc) :desc(nullptr) { if(!desc) return; auto top = std::make_shared(); top->desc = desc; top->members.resize(desc->size()); { auto& root = top->members[0]; root.init(desc->code.storedAs()); root.top = top.get(); } if(desc->code==TypeCode::Struct) { for(auto& pair : desc->mlookup) { auto cfld = desc.get() + pair.second; auto& mem = top->members.at(pair.second); mem.top = top.get(); mem.init(cfld->code.storedAs()); } } this->desc = desc.get(); decltype (store) val(top, top->members.data()); // alias this->store = std::move(val); } Value::Value(const std::shared_ptr& desc, Value& parent) :Value(desc) { store->top->enclosing = parent.store; } Value::~Value() {} Value Value::cloneEmpty() const { Value ret; if(desc) { decltype (store->top->desc) fld(store->top->desc, desc); ret = Value(fld); } return ret; } Value Value::clone() const { Value ret; if(desc) { decltype (store->top->desc) fld(store->top->desc, desc); ret = Value(fld); ret.assign(*this); } return ret; } static bool assignCompatible(const FieldDesc* tdesc, const FieldStorage* tstore, const FieldDesc* sdesc, const FieldStorage* sstore) { if(tdesc==sdesc) { // exact match return true; } else if(!sdesc) { // assignment of NULL is always a noop return true; // from this point we know tdesc!=NULL } else if(tdesc->code.storedAs()!=sdesc->code.storedAs()) { // TODO kind conversion? return false; } switch (tdesc->code.storedAs()) { case StoreType::Bool: case StoreType::Real: case StoreType::String: case StoreType::Integer: case StoreType::UInteger: // simple scalar field return true; case StoreType::Null: // Structure assignment allowed if src has all target fields (extras ignored) for(size_t idx : range(size_t(1u), tdesc->size())) { auto it = sdesc->mlookup.find(tdesc->miter[idx-1u].first); if(it==sdesc->mlookup.end()) { return false; } auto schild = sdesc + it->second; auto tchild = tdesc + idx; if(!assignCompatible(tchild, tstore+idx, schild, sstore+it->second)) return false; } return true; case StoreType::Array: if(tdesc->code!=sdesc->code) { // TODO array type conversion? return false; } else if((tdesc->code.kind()==Kind::Bool) || (tdesc->code.kind()==Kind::Real) || (tdesc->code.kind()==Kind::String) || (tdesc->code.kind()==Kind::Integer) || tdesc->code==TypeCode::AnyA) { return true; } else { // TODO StructS, UnionS return false; } break; case StoreType::Compound: // not implemented (yet?) return false; } return false; } Value& Value::assign(const Value& o) { if(!assignCompatible(desc, store.get(), o.desc, o.store.get())) throw std::runtime_error("assign() not supported for these types"); if(desc && o.desc) { for(size_t bit=0, end=desc->size(); bitvalid) { bit++; continue; } dstore->valid = true; switch(dstore->code) { case StoreType::Real: case StoreType::Bool: case StoreType::Integer: case StoreType::UInteger: dstore->as() = sstore->as(); bit++; break; case StoreType::String: dstore->as() = sstore->as(); bit++; break; case StoreType::Array: dstore->as>() = sstore->as>(); bit++; break; case StoreType::Compound: dstore->as() = sstore->as(); bit++; break; case StoreType::Null: { // copy entire sub-structure auto sdesc = desc + bit; for(auto end2 = bit + sdesc->size(); bitvalid = true; switch(dstore->code) { case StoreType::Real: case StoreType::Bool: case StoreType::Integer: case StoreType::UInteger: dstore->as() = sstore->as(); bit++; break; case StoreType::String: dstore->as() = sstore->as(); bit++; break; case StoreType::Array: dstore->as>() = sstore->as>(); bit++; break; case StoreType::Compound: dstore->as() = sstore->as(); bit++; break; case StoreType::Null: // skip sub-struct nodes, we will copy all leaf nodes break; } } } break; } } } return *this; } Value Value::allocMember() { // allocate member type for Struct[] or Union[] if(!desc || (desc->code!=TypeCode::UnionA && desc->code!=TypeCode::StructA)) throw std::runtime_error("allocMember() only meaningful for Struct[] or Union[]"); decltype (store->top->desc) fld(store->top->desc, desc->members.data()); return Value::Helper::build(fld, *this); } bool Value::isMarked(bool parents, bool children) const { if(!desc) return false; if(store->valid) return true; auto top = store->top; if(children && desc->size()>1u) { // TODO more efficient for(auto bit : range(desc->size())) { auto cstore = store.get() + bit; if(cstore->valid) return true; } } if(parents) { auto pdesc = desc; auto pstore = store.get(); while(pdesc!=top->desc.get()) { pstore -= pdesc->parent_index; pdesc -= pdesc->parent_index; if(pstore->valid) return true; } } return false; } void Value::mark(bool v) { if(!desc) return; store->valid = v; if(!v) return; auto top = store->top; std::shared_ptr enc; while(top && (enc=top->enclosing.lock())) { enc->valid = true; top = enc->top; } } void Value::unmark(bool parents, bool children) { if(!desc) return; store->valid = false; auto top = store->top; if(children && desc->size()>1u) { // TODO more efficient for(auto bit : range(desc->size())) { (store.get() + bit)->valid = false; } } if(parents) { auto pdesc = desc; auto pstore = store.get(); while(pdesc!=top->desc.get()) { pdesc -= pdesc->parent_index; pstore -= pdesc->parent_index; pstore->valid = false; } } } TypeCode Value::type() const { return desc ? desc->code : TypeCode::Null; } StoreType Value::storageType() const { return store ? store->code : StoreType::Null; } const std::string& Value::id() const { if(!desc) throw std::runtime_error("Null Value"); return desc->id; } bool Value::idStartsWith(const std::string& prefix) const { auto ID = this->id(); return ID.size()>=prefix.size() && prefix==ID.substr(0u, prefix.size()); } const std::string &Value::nameOf(const Value& decendent) const { if(!store || !decendent.store) throw NoField(); auto pidx = store->index(); auto didx = decendent.store->index(); if(pidx >= didx || didx >= store->top->members.size()) throw std::logic_error("not a decendent"); // inefficient, but we don't keep a reverse mapping for(auto& it : desc->mlookup) { if(it.second == didx-pidx) return it.first; } throw std::logic_error("missing decendent"); } namespace { // C-style cast between scalar storage types, and print to string (base 10) template bool copyOutScalar(const Src& src, void *ptr, StoreType type) { switch(type) { case StoreType::Real: *reinterpret_cast(ptr) = src; return true; case StoreType::Integer: *reinterpret_cast(ptr) = src; return true; case StoreType::UInteger: *reinterpret_cast(ptr) = src; return true; case StoreType::Bool: *reinterpret_cast(ptr) = src; return true; case StoreType::String: *reinterpret_cast(ptr) = SB()<code) { case StoreType::Real: if(copyOutScalar(store->as(), ptr, type)) return; else break; case StoreType::Integer: if(copyOutScalar(store->as(), ptr, type)) return; else break; case StoreType::UInteger: if(copyOutScalar(store->as(), ptr, type)) return; else break; case StoreType::Bool: { auto& src = store->as(); switch(type) { case StoreType::Bool: *reinterpret_cast(ptr) = src; return; case StoreType::Integer: case StoreType::UInteger: *reinterpret_cast(ptr) = src; return; case StoreType::Real: *reinterpret_cast(ptr) = src; return; case StoreType::String: *reinterpret_cast(ptr) = src ? "true" : "false"; return; default: break; } break; } case StoreType::String: { auto& src = store->as(); switch(type) { case StoreType::String: *reinterpret_cast(ptr) = src; return; case StoreType::Integer: { epicsInt64 temp; if(epicsParseInt64(src.c_str(), &temp, 0, nullptr)==0) { *reinterpret_cast(ptr) = temp; return; } } case StoreType::UInteger: { epicsUInt64 temp; if(epicsParseUInt64(src.c_str(), &temp, 0, nullptr)==0) { *reinterpret_cast(ptr) = temp; return; } } case StoreType::Real: { double temp; if(epicsParseDouble(src.c_str(), &temp, nullptr)==0) { *reinterpret_cast(ptr) = temp; return; } } case StoreType::Bool: { if(src=="true") { *reinterpret_cast(ptr) = true; return; } else if(src=="false") { *reinterpret_cast(ptr) = false; return; } } default: break; } break; } case StoreType::Array: { auto& src = store->as>(); switch (type) { case StoreType::Array: *reinterpret_cast*>(ptr) = src; return; // TODO: print array // extract [0] as scalar? default: break; } break; } case StoreType::Compound: { auto& src = store->as(); if(type==StoreType::Compound) { // extract Value *reinterpret_cast(ptr) = src; return; } else if(src) { // automagic deref and delegate assign src.copyOut(ptr, type); return; } else { throw NoConvert(); } break; } case StoreType::Null: break; } throw NoConvert(); } bool Value::tryCopyOut(void *ptr, StoreType type) const { try { copyOut(ptr, type); return true; }catch(NoField&){ return false; }catch(NoConvert&){ return false; } } namespace { bool parseScalar(double& dest, const std::string& inp) { return 0==epicsParseDouble(inp.c_str(), &dest, nullptr); } bool parseScalar(int64_t& dest, const std::string& inp) { epicsInt64 t; if(epicsParseInt64(inp.c_str(), &t, 0, nullptr)==0) { dest = t; return true; } return false; } bool parseScalar(uint64_t& dest, const std::string& inp) { epicsUInt64 t; if(epicsParseUInt64(inp.c_str(), &t, 0, nullptr)==0) { dest = t; return true; } return false; } // C-style cast between scalar storage types, and print to string (base 10) template bool copyInScalar(Dest& dest, const void *ptr, StoreType type) { switch(type) { case StoreType::Real: dest = *reinterpret_cast(ptr); return true; case StoreType::Integer: dest = *reinterpret_cast(ptr); return true; case StoreType::UInteger: dest = *reinterpret_cast(ptr); return true; case StoreType::Bool: dest = *reinterpret_cast(ptr); return true; case StoreType::String: return parseScalar(dest, *reinterpret_cast(ptr)); case StoreType::Null: case StoreType::Compound: case StoreType::Array: break; } return false; } } void Value::copyIn(const void *ptr, StoreType type) { // control flow should either throw NoField or NoConvert, or update 'store' and // reach the mark() at the end. if(!desc) throw NoField(); switch(store->code) { case StoreType::Real: if(!copyInScalar(store->as(), ptr, type)) throw NoConvert(); break; case StoreType::Integer: if(!copyInScalar(store->as(), ptr, type)) throw NoConvert(); break; case StoreType::UInteger: if(!copyInScalar(store->as(), ptr, type)) throw NoConvert(); break; case StoreType::Bool: { auto& dest = store->as(); switch(type) { case StoreType::Bool: dest = *reinterpret_cast(ptr); break; case StoreType::Integer: case StoreType::UInteger: dest = 0!=*reinterpret_cast(ptr); break; //case StoreType::Real: // TODO pick condition. strict non-zero? fabs()<0.5 ? case StoreType::String: if("true"==*reinterpret_cast(ptr)) { dest = true; break; } else if("false"==*reinterpret_cast(ptr)) { dest = false; break; } // fall through default: throw NoConvert(); } break; } case StoreType::String: { auto& dest = store->as(); switch(type) { case StoreType::String: dest = *reinterpret_cast(ptr); break; case StoreType::Integer: dest = SB()<<*reinterpret_cast(ptr); break; case StoreType::UInteger: dest = SB()<<*reinterpret_cast(ptr); break; case StoreType::Real: dest = SB()<<*reinterpret_cast(ptr); break; case StoreType::Bool: dest = (*reinterpret_cast(ptr)) ? "true" : "false"; break; default: throw NoConvert(); } break; } case StoreType::Array: { auto& dest = store->as>(); switch (type) { case StoreType::Array: { auto& src = *reinterpret_cast*>(ptr); if(src.original_type()==ArrayType::Null || src.empty()) { // assignment from untyped or empty dest.clear(); } else if(src.original_type()==ArrayType::Value && desc->code.kind()==Kind::Compound) { // assign array of Struct/Union/Any auto tsrc = src.castTo(); if(desc->code!=TypeCode::AnyA) { // enforce member type for Struct[] and Union[] for(auto& val : tsrc) { if(val.desc && val.desc!=desc->members.data()) { throw NoConvert(); } } } dest = src; } else if(src.original_type()!=ArrayType::Value && uint8_t(desc->code.code)==uint8_t(src.original_type())) { // assign array of scalar w/o convert dest = src; } else { // TODO: alloc and convert throw NoConvert(); } break; } default: throw NoConvert(); } break; } case StoreType::Compound: if(desc->code==TypeCode::Any) { // assigning variant union. auto& val = store->as(); if(type==StoreType::Compound) { val = *reinterpret_cast(ptr); break; } else { val = Value::Helper::build(ptr, type); break; } } throw NoConvert(); case StoreType::Null: if(type==StoreType::Compound) { auto& src = *reinterpret_cast(ptr); if(src.type()==TypeCode::Struct) { // copy struct to struct // all marked source field may be mapped to destination fields for(auto& sfld : src.imarked()) { if(sfld.type()==TypeCode::Struct) { // entire sub-struct marked for(auto& sfld2 : sfld.iall()) { if(auto dfld = (*this)[src.nameOf(sfld2)]) { dfld.copyIn(&sfld2.store->store, sfld2.store->code); } else { throw NoField(); } } } else { if(auto dfld = (*this)[src.nameOf(sfld)]) { dfld.copyIn(&sfld.store->store, sfld.store->code); } else { throw NoField(); } } } } } throw NoConvert(); } mark(); } bool Value::tryCopyIn(const void *ptr, StoreType type) { try { copyIn(ptr, type); return true; }catch(NoField&){ return false; }catch(NoConvert&){ return false; } } void Value::traverse(const std::string &expr, bool modify) { size_t pos=0; bool maybedot = false; while(desc && postop->desc.get()) { auto pdesc = desc - desc->parent_index; std::shared_ptr pstore(store, store.get() - desc->parent_index); store = std::move(pstore); desc = pdesc; pos++; continue; } else { // at top store.reset(); desc = nullptr; break; } } if(desc->code.code==TypeCode::Struct) { // attempt traverse to member. // expect: [0-9a-zA-Z_.]+[\[-$] // skip a leading dot if(maybedot) { if(expr[pos]!='.') { store.reset(); desc = nullptr; break; } maybedot = false; pos++; } size_t sep = expr.find_first_of("<[-", pos); decltype (desc->mlookup)::const_iterator it; if(sep>0 && (it=desc->mlookup.find(expr.substr(pos, sep-pos)))!=desc->mlookup.end()) { // found it auto next = desc+it->second; decltype(store) value(store, store.get()+it->second); store = std::move(value); desc = next; pos = sep; } else { // no such member store.reset(); desc = nullptr; } } else if(desc->code.code==TypeCode::Union || desc->code.code==TypeCode::Any) { // attempt to traverse to (and maybe select) member // expect: ->[0-9a-zA-Z_]+[.\[-$] maybedot = false; if(expr.size()-pos >= 3 && expr[pos]=='-' && expr[pos+1]=='>') { pos += 2; // skip past "->" if(desc->code.code==TypeCode::Any) { // select member of Any (may be Null) *this = store->as(); } else { // select member of Union size_t sep = expr.find_first_of("<[-.", pos); decltype (desc->mlookup)::const_iterator it; if(sep>0 && (it=desc->mlookup.find(expr.substr(pos, sep-pos)))!=desc->mlookup.end()) { // found it. auto& fld = store->as(); if(modify || fld.desc==&desc->members[it->second]) { // will select, or already selected if(fld.desc!=&desc->members[it->second]) { // select std::shared_ptr mtype(store->top->desc, &desc->members[it->second]); fld = Value(mtype, *this); } pos = sep; *this = fld; maybedot = true; } else { // traversing const Value, can't select Union store.reset(); desc = nullptr; } } } } else { // expected "->" store.reset(); desc = nullptr; } } else if(desc->code.isarray() && desc->code.kind()==Kind::Compound) { // attempt to traverse into array of Struct, Union, or Any // expect: \[[0-9]+\] maybedot = false; size_t sep = expr.find_first_of(']', pos); unsigned long long index=0; if(expr[pos]=='[' && sep!=std::string::npos && sep-pos>=2 && !epicsParseULLong(expr.substr(pos+1, sep-1-pos).c_str(), &index, 0, nullptr)) { auto& varr = store->as>(); shared_array arr; if((varr.original_type()==ArrayType::Value) && index < (arr = varr.castTo()).size()) { *this = arr[index]; pos = sep+1; maybedot = true; } else { // wrong element type or out of range store.reset(); desc = nullptr; } } else { // syntax error store.reset(); desc = nullptr; } } else { // syntax error or wrong field type (can't index scalar array) store.reset(); desc = nullptr; } } } Value Value::operator[](const char *name) { Value ret(*this); ret.traverse(name, true); return ret; } const Value Value::operator[](const char *name) const { Value ret(*this); ret.traverse(name, false); return ret; } void Value::_iter_fl(Value::IterInfo &info, bool first) const { if(!store) throw NoField(); if(desc->code!=TypeCode::Struct) { // TODO implement iteration of Union, or Struct[]/Union[] info.pos = info.nextcheck = 0; } else if(info.depth) { info.pos = info.nextcheck = first ? 1u : desc->size(); if(info.marked) _iter_advance(info); } else { info.pos = info.nextcheck = first ? 0u : desc->miter.size(); } } void Value::_iter_advance(IterInfo& info) const { assert(info.depth); // scan forward to find next non-marked for(auto idx : range(info.pos, desc->size())) { auto S = store.get() + idx; if(S->valid) { auto D = desc + idx; info.pos = idx; info.nextcheck = idx + D->size(); return; } } info.pos = info.nextcheck = desc->size(); } Value Value::_iter_deref(const IterInfo& info) const { auto idx = info.pos; if(!info.depth) idx = desc->miter[idx].second; decltype (store) store2(store, store.get()+idx); Value ret; ret.store = std::move(store2); ret.desc = desc + idx; return ret; } namespace impl { void FieldStorage::init(StoreType code) { this->code = code; switch(code) { case StoreType::Null: return; case StoreType::Bool: as() = false; return; case StoreType::Integer: case StoreType::UInteger: case StoreType::Real: // just zero 8 bytes as() = 0u; return; case StoreType::String: new(&store) std::string(); return; case StoreType::Compound: new(&store) std::shared_ptr(); return; case StoreType::Array: new(&store) shared_array(); return; } throw std::logic_error("FieldStore::init()"); } void FieldStorage::deinit() { switch(code) { case StoreType::Null: case StoreType::Integer: case StoreType::UInteger: case StoreType::Real: case StoreType::Bool: break; case StoreType::Array: as>().~shared_array(); break; case StoreType::String: as().~basic_string(); break; case StoreType::Compound: as().~Value(); break; default: throw std::logic_error("FieldStore::deinit()"); } code = StoreType::Null; } FieldStorage::~FieldStorage() { deinit(); } size_t FieldStorage::index() const { const size_t ret = this-top->members.data(); return ret; } }} // namespace pvxs::impl