From ec5f59dac8d5eca3504ec5fe205dcb7d8fd382a6 Mon Sep 17 00:00:00 2001 From: jsdelfino Date: Mon, 16 Nov 2009 06:00:12 +0000 Subject: Fixed support for nested elements and lists. Improved failure/error reporting monad. Minor code cleanup. git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@880598 13f79535-47bb-0310-9956-ffa450edef68 --- cpp/sca/kernel/element.hpp | 85 +++++++++++++++++++++++++++++++++++------- cpp/sca/kernel/kernel-test.cpp | 3 +- cpp/sca/kernel/list.hpp | 9 +---- cpp/sca/kernel/monad.hpp | 81 +++++++++++++++++++++++++++++++++++++--- cpp/sca/kernel/value.hpp | 44 +++++++++++++++++++++- cpp/sca/kernel/xml.hpp | 26 ++++++------- cpp/sca/modules/atom/atom.hpp | 10 ++--- cpp/sca/modules/http/curl.hpp | 58 ++++++++++++++++++---------- cpp/sca/modules/json/json.hpp | 15 ++++---- 9 files changed, 255 insertions(+), 76 deletions(-) diff --git a/cpp/sca/kernel/element.hpp b/cpp/sca/kernel/element.hpp index d93e90742b..2b5336ba5c 100644 --- a/cpp/sca/kernel/element.hpp +++ b/cpp/sca/kernel/element.hpp @@ -38,6 +38,24 @@ namespace tuscany const value attribute("attribute"); const value element("element"); +/** + * Returns true if a value is an element. + */ +bool isElement(const value& v) { + if (!isList(v) || isNil(v) || element != car(v)) + return false; + return true; +} + +/** + * Returns true if a value is an attribute. + */ +bool isAttribute(const value& v) { + if (!isList(v) || isNil(v) || attribute != car(v)) + return false; + return true; +} + /** * Returns the name of an attribute. */ @@ -99,9 +117,7 @@ const bool elementToValueIsList(const value& v) { if (!isList(v)) return false; const list l = v; - if(isNil(l)) - return true; - return isList(car(l)); + return (isNil(l) || !isSymbol(car(l))); } const value elementToValue(const value& t) { @@ -150,12 +166,10 @@ const bool elementToValueIsSymbol(const value& v) { } const list elementToValueGroupValues(const value& v, const list& l) { - if (isNil(l) || !elementToValueIsSymbol(v) || !elementToValueIsSymbol(car(l))) { + if (isNil(l) || !elementToValueIsSymbol(v) || !elementToValueIsSymbol(car(l))) return cons(v, l); - } - if (car(car(l)) != car(v)) { + if (car(car(l)) != car(v)) return cons(v, l); - } if (!elementToValueIsList(cadr(car(l)))) { const value g = mklist(car(v), mklist(cdr(v), cdr(car(l)))); return elementToValueGroupValues(g, cdr(l)); @@ -167,9 +181,7 @@ const list elementToValueGroupValues(const value& v, const list& l const list elementsToValues(const list& e) { if (isNil(e)) - return list(); - const value v = elementToValue(car(e)); - const list n = elementsToValues(cdr(e)); + return e; return elementToValueGroupValues(elementToValue(car(e)), elementsToValues(cdr(e))); } @@ -207,7 +219,7 @@ const value valueToElement(const value& t) { */ const list valuesToElements(const list& l) { if (isNil(l)) - return list(); + return l; return cons(valueToElement(car(l)), valuesToElements(cdr(l))); } @@ -219,19 +231,19 @@ struct selectorLambda { const list select; selectorLambda(const list& s) : select(s) { } - const bool evalExpr(const list& s, const list v) const { + const bool evalSelect(const list& s, const list v) const { if (isNil(s)) return true; if (isNil(v)) return false; if (car(s) != car(v)) return false; - return evalExpr(cdr(s), cdr(v)); + return evalSelect(cdr(s), cdr(v)); } const bool operator()(const value& v) const { if (!isList(v)) return false; - return evalExpr(select, v); + return evalSelect(select, v); } }; @@ -239,5 +251,50 @@ const lambda selector(const list s) { return selectorLambda(s); } +/** + * Returns the value of the attribute with the given name. + */ +struct filterAttribute { + const value name; + filterAttribute(const value& n) : name(n) { + } + const bool operator()(const value& v) const { + return isAttribute(v) && attributeName((list)v) == name; + } +}; + +const value attributeValue(const value& name, const value& l) { + const list f = filter(filterAttribute(name), list(l)); + if (isNil(f)) + return value(); + return caddr(car(f)); +} + +/** + * Returns child elements with the given name. + */ +struct filterElement { + const value name; + filterElement(const value& n) : name(n) { + } + const bool operator()(const value& v) const { + return isElement(v) && elementName((list)v) == name; + } +}; + +const value elementChildren(const value& name, const value& l) { + return filter(filterElement(name), list(l)); +} + +/** + * Return the child element with the given name. + */ +const value elementChild(const value& name, const value& l) { + const list f = elementChildren(name, l); + if (isNil(f)) + return value(); + return car(f); +} + } #endif /* tuscany_element_hpp */ diff --git a/cpp/sca/kernel/kernel-test.cpp b/cpp/sca/kernel/kernel-test.cpp index 0234f6eb3e..9346e31b71 100644 --- a/cpp/sca/kernel/kernel-test.cpp +++ b/cpp/sca/kernel/kernel-test.cpp @@ -650,7 +650,8 @@ bool testFailableMonad() { assert((m >> success()) == m); assert(m >> failableF >> failableG == m >> failableH); - failable ooops("ooops"); + failable ooops = mkfailure("ooops"); + assert(reason(ooops) == "ooops"); assert(ooops >> failableF >> failableG == ooops); return true; } diff --git a/cpp/sca/kernel/list.hpp b/cpp/sca/kernel/list.hpp index deb4414d98..c21efe173b 100644 --- a/cpp/sca/kernel/list.hpp +++ b/cpp/sca/kernel/list.hpp @@ -174,18 +174,11 @@ template const list rcons(const T& car, const list& cdr) { return cons(car, cdr); } -/** - * Construct a list from a single value. - */ -template const list cons(const T& car) { - return list (car, result(list ())); -} - /** * Construct a list of one value. */ template const list mklist(const T& car) { - return cons(car); + return list (car, result(list ())); } /** diff --git a/cpp/sca/kernel/monad.hpp b/cpp/sca/kernel/monad.hpp index 9fefbb9f60..98eb3799c0 100644 --- a/cpp/sca/kernel/monad.hpp +++ b/cpp/sca/kernel/monad.hpp @@ -67,6 +67,14 @@ private: const V v; }; +/** + * Write an identity monad to a stream. + */ +template std::ostream& operator<<(std::ostream& out, const id& m) { + out << (V)m; + return out; +} + /** * Return an identity monad from a value. */ @@ -133,6 +141,18 @@ private: template friend const bool hasValue(const maybe& m); }; +/** + * Write a maybe monad to a stream. + */ +template std::ostream& operator<<(std::ostream& out, const maybe& m) { + if (!hasValue(m)) { + out << "nothing"; + return out; + } + out << (V)m; + return out; +} + /** * Return a maybe monad with a value in it. */ @@ -174,20 +194,23 @@ template const maybe operator>>(const maybe& m, co */ template class failable { public: + failable() : hasv(false) { + } + failable(const V& v) : hasv(true), v(v) { } - failable(const F& f) : hasv(false), f(f) { + failable(const failable& m) : hasv(m.hasv) { + if (hasv) + v = m.v; + else + f = m.f; } operator const V() const { return v; } - operator const F() const { - return f; - } - const failable& operator=(const failable& m) { if(this == &m) return *this; @@ -212,13 +235,31 @@ public: } private: - const bool hasv; + bool hasv; V v; F f; + failable(const bool hasv, const F& f) : hasv(hasv), f(f) { + } + template friend const bool hasValue(const failable& m); + template friend const B reason(const failable& m); + template friend const failable mkfailure(const B& f); }; +/** + * Write a failable monad to a stream. + */ +template std::ostream& operator<<(std::ostream& out, const failable& m) { + if (!hasValue(m)) { + out << reason(m); + return out; + } + const V v = m; + out << v; + return out; +} + /** * Returns a failable monad with a success value in it. */ @@ -230,6 +271,17 @@ template const lambda(V)> success() { return mksuccess; } +/** + * Returns a failable monad with a failure in it. + */ +template const failable mkfailure(const F& f) { + return failable(false, f); +} + +template const lambda(V)> failure() { + return mkfailure; +} + /** * Returns true if the monad contains a value. */ @@ -237,6 +289,13 @@ template const bool hasValue(const failable& m) { return m.hasv; } +/** + * Returns the reason for failure of a failable monad. + */ +template const F reason(const failable& m) { + return m.f; +} + /** * Bind a function to a failable monad. Passes the success value in the monad to the function * if present, or does nothing if there's no value and a failure instead. @@ -329,6 +388,16 @@ private: const lambda(S)> f; }; +/** + * Write a state monad to a stream. + */ +template std::ostream& operator<<(std::ostream& out, const state& m) { + const S s = m; + const V v = m; + out << '(' << s << ' ' << v << ')'; + return out; +} + /** * Return a state monad carrying a result value. */ diff --git a/cpp/sca/kernel/value.hpp b/cpp/sca/kernel/value.hpp index 618bd7b622..d602b30623 100644 --- a/cpp/sca/kernel/value.hpp +++ b/cpp/sca/kernel/value.hpp @@ -362,7 +362,9 @@ private: }; - +/** + * Write a value to a stream. + */ std::ostream& operator<<(std::ostream& out, const value& v) { switch(v.type) { case value::List: @@ -399,46 +401,86 @@ std::ostream& operator<<(std::ostream& out, const value& v) { } } +/** + * Returns the type of a value. + */ const value::ValueType type(const value& v) { return v.type; } +/** + * Returns true if a value is nil. + */ const bool isNil(const value& value) { return value.type == value::Undefined; } +/** + * Returns true if a value is a lambda. + */ +const bool isLambda(const value& value) { + return value.type == value::Lambda; +} + +/** + * Returns true if a value is a string. + */ const bool isString(const value& value) { return value.type == value::String; } +/** + * Returns true if a value is a symbol. + */ const bool isSymbol(const value& value) { return value.type == value::Symbol; } +/** + * Returns true if a value is a list. + */ const bool isList(const value& value) { return value.type == value::List; } +/** + * Returns true if a value is a number. + */ const bool isNumber(const value& value) { return value.type == value::Number; } +/** + * Returns true if a value is a boolean. + */ const bool isBool(const value& value) { return value.type == value::Bool; } +/** + * Returns true if a value is a character. + */ const bool isChar(const value& value) { return value.type == value::Char; } +/** + * Returns true if a value is a pointer. + */ const bool isPtr(const value& value) { return value.type == value::Ptr; } +/** + * Returns true if a value is a pooled pointer. + */ const bool isPoolPtr(const value& value) { return value.type == value::PoolPtr; } +/** + * Returns true if a value is a tagged list. + */ const bool isTaggedList(const value& exp, value tag) { if(isList(exp) && !isNil((list)exp)) return car((list)exp) == tag; diff --git a/cpp/sca/kernel/xml.hpp b/cpp/sca/kernel/xml.hpp index b7611b8477..8d561557ca 100644 --- a/cpp/sca/kernel/xml.hpp +++ b/cpp/sca/kernel/xml.hpp @@ -144,7 +144,7 @@ const value readAttribute(XMLReader& reader) { */ const value readToken(XMLReader& reader) { const int tokenType = reader.read(); - if (tokenType == XMLReader::End) + if (tokenType == XMLReader::None || tokenType == XMLReader::End) return value(); if (tokenType == XMLReader::Element) return startElement; @@ -228,7 +228,7 @@ const char* encoding = "UTF-8"; */ const list expandElementValues(const value& n, const list& l) { if (isNil(l)) - return list(); + return l; return cons(value(cons(element, cons(n, (list)car(l)))), expandElementValues(n, cdr(l))); } @@ -240,7 +240,7 @@ const failable writeList(const list& l, const xmlTextW const value token(car(l)); if (isTaggedList(token, attribute)) { if (xmlTextWriterWriteAttribute(xml, (const xmlChar*)std::string(attributeName(token)).c_str(), (const xmlChar*)std::string(attributeValue(token)).c_str()) < 0) - return std::string("xmlTextWriterWriteAttribute failed"); + return mkfailure("xmlTextWriterWriteAttribute failed"); } else if (isTaggedList(token, element)) { @@ -257,7 +257,7 @@ const failable writeList(const list& l, const xmlTextW // Write an element with a single value if (xmlTextWriterStartElement(xml, (const xmlChar*)std::string(elementName(token)).c_str()) < 0) - return std::string("xmlTextWriterStartElement failed"); + return mkfailure("xmlTextWriterStartElement failed"); // Write its children const failable w = writeList(elementChildren(token), xml); @@ -265,14 +265,14 @@ const failable writeList(const list& l, const xmlTextW return w; if (xmlTextWriterEndElement(xml) < 0) - return std::string("xmlTextWriterEndElement failed"); + return mkfailure("xmlTextWriterEndElement failed"); } } else { // Write an element if (xmlTextWriterStartElement(xml, (const xmlChar*)std::string(elementName(token)).c_str()) < 0) - return std::string("xmlTextWriterStartElement failed"); + return mkfailure("xmlTextWriterStartElement failed"); // Write its children const failable w = writeList(elementChildren(token), xml); @@ -280,13 +280,13 @@ const failable writeList(const list& l, const xmlTextW return w; if (xmlTextWriterEndElement(xml) < 0) - return std::string("xmlTextWriterEndElement failed"); + return mkfailure("xmlTextWriterEndElement failed"); } } else { // Write XML text if (xmlTextWriterWriteString(xml, (const xmlChar*)std::string(token).c_str()) < 0) - return std::string("xmlTextWriterWriteString failed"); + return mkfailure("xmlTextWriterWriteString failed"); } @@ -299,14 +299,14 @@ const failable writeList(const list& l, const xmlTextW */ const failable write(const list& l, const xmlTextWriterPtr xml) { if (xmlTextWriterStartDocument(xml, NULL, encoding, NULL) < 0) - return std::string("xmlTextWriterStartDocument failed"); + return mkfailure("xmlTextWriterStartDocument failed"); const failable w = writeList(l, xml); if (!hasValue(w)) return w; if (xmlTextWriterEndDocument(xml) < 0) - return std::string("xmlTextWriterEndDocument failed"); + return mkfailure("xmlTextWriterEndDocument failed"); return true; } @@ -337,15 +337,15 @@ template const failable writeXML(const lambda cx(reduce, initial); xmlOutputBufferPtr out = xmlOutputBufferCreateIO(writeCallback, NULL, &cx, NULL); if (out == NULL) - return std::string("xmlOutputBufferCreateIO failed"); + return mkfailure("xmlOutputBufferCreateIO failed"); xmlTextWriterPtr xml = xmlNewTextWriter(out); if (xml == NULL) - return std::string("xmlNewTextWriter failed"); + return mkfailure("xmlNewTextWriter failed"); const failable w = write(l, xml); xmlFreeTextWriter(xml); if (!hasValue(w)) { - return std::string(w); + return mkfailure(reason(w)); } return cx.accum; } diff --git a/cpp/sca/modules/atom/atom.hpp b/cpp/sca/modules/atom/atom.hpp index 2077081320..5054c635a0 100644 --- a/cpp/sca/modules/atom/atom.hpp +++ b/cpp/sca/modules/atom/atom.hpp @@ -52,7 +52,7 @@ const list entryValue(const list& e) { */ const list entriesValues(const list& e) { if (isNil(e)) - return list(); + return e; return cons(entryValue(car(e)), entriesValues(cdr(e))); } @@ -62,7 +62,7 @@ const list entriesValues(const list& e) { const failable, std::string> readEntry(const list& ilist) { const list e = readXML(ilist); if (isNil(e)) - return std::string("Empty entry"); + return mkfailure, std::string>("Empty entry"); return entryValue(car(e)); } @@ -72,7 +72,7 @@ const failable, std::string> readEntry(const list& ilis const failable, std::string> readFeed(const list& ilist) { const list f = readXML(ilist); if (isNil(f)) - return std::string("Empty feed"); + return mkfailure, std::string>("Empty feed"); const list t = filter(selector(mklist(element, "title")), car(f)); const list i = filter(selector(mklist(element, "id")), car(f)); const list e = filter(selector(mklist(element, "entry")), car(f)); @@ -99,7 +99,7 @@ const list entryElement(const list& l) { */ const list entriesElements(const list& l) { if (isNil(l)) - return list(); + return l; return cons(entryElement(car(l)), entriesElements(cdr(l))); } @@ -156,7 +156,7 @@ const list entryValuesToElements(const list val) { */ const list feedValuesToElementsLoop(const list val) { if (isNil(val)) - return list(); + return val; return cons(entryValuesToElements(car(val)), feedValuesToElementsLoop(cdr(val))); } diff --git a/cpp/sca/modules/http/curl.hpp b/cpp/sca/modules/http/curl.hpp index c0d79cef13..5ee3a090b0 100644 --- a/cpp/sca/modules/http/curl.hpp +++ b/cpp/sca/modules/http/curl.hpp @@ -137,13 +137,13 @@ template size_t headerCallback(void *ptr, size_t size, size_t nmemb, * Apply an HTTP verb to a list containing a list of headers and a list of content, and * a reduce function used to process the response. */ -curl_slist* headers(curl_slist* cl, const list h) { +curl_slist* headers(curl_slist* cl, const list& h) { if (isNil(h)) return cl; return headers(curl_slist_append(cl, std::string(car(h)).c_str()), cdr(h)); } -template const failable, std::string> apply(const list > req, const lambda& reduce, const R& initial, const std::string& url, const std::string& verb, CURLHandle& ch) { +template const failable, std::string> apply(const list >& req, const lambda& reduce, const R& initial, const std::string& url, const std::string& verb, const CURLHandle& ch) { // Init the curl session curl_easy_reset(ch); @@ -190,13 +190,13 @@ template const failable, std::string> apply(const list, std::string>(curl_easy_strerror(rc)); long httprc; curl_easy_getinfo (ch, CURLINFO_RESPONSE_CODE, &httprc); if (httprc != 200 && httprc != 201) { std::ostringstream es; es << "HTTP code " << httprc; - return es.str(); + return mkfailure, std::string>(es.str()); } return mklist(hcx.accum, wcx.accum); } @@ -204,13 +204,13 @@ template const failable, std::string> apply(const list evalExpr(const value expr, const std::string& url, CURLHandle& ch) { +const failable evalExpr(const value& expr, const std::string& url, const CURLHandle& ch) { // Convert expression to a JSON-RPC request json::JSONContext cx; const failable, std::string> jsreq = jsonRequest(1, car(expr), cdr(expr), cx); if (!hasValue(jsreq)) - return std::string(jsreq); + return mkfailure(reason(jsreq)); if (logContent) { std::cout<< "content: " << std::endl; @@ -223,7 +223,7 @@ const failable evalExpr(const value expr, const std::string const list h = mklist("Content-Type: application/json-rpc"); const failable >, std::string> res = apply >(mklist >(h, jsreq), rcons, list(), url, "POST", ch); if (!hasValue(res)) - return std::string(res); + return mkfailure(reason(res)); // Return result if (logContent) { @@ -238,7 +238,7 @@ const failable evalExpr(const value expr, const std::string /** * HTTP GET, return the resource at the given URL. */ -template const failable, std::string> get(const lambda& reduce, const R& initial, const std::string& url, CURLHandle& ch) { +template const failable, std::string> get(const lambda& reduce, const R& initial, const std::string& url, const CURLHandle& ch) { const list > req = mklist(list(), list()); return apply(req, reduce, initial, url, "GET", ch); } @@ -246,17 +246,17 @@ template const failable, std::string> get(const lambda get(const std::string& url, CURLHandle& ch) { +const failable get(const std::string& url, const CURLHandle& ch) { // Get the contents of the resource at the given URL const failable >, std::string> res = get >(rcons, list(), url, ch); if (!hasValue(res)) - return std::string(res); + return mkfailure(reason(res)); const list > ls = res; - // TODO Return an ATOM feed const std::string ct; if (ct.find("application/atom+xml") != std::string::npos) { + // TODO Return an ATOM feed } // Return the content as a string value @@ -268,12 +268,12 @@ const failable get(const std::string& url, CURLHandle& ch) { /** * HTTP POST. */ -const failable post(const value& val, const std::string& url, CURLHandle& ch) { +const failable post(const value& val, const std::string& url, const CURLHandle& ch) { // Convert value to an ATOM entry const failable, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); if (!hasValue(entry)) - return std::string(entry); + return mkfailure(reason(entry)); if (logContent) { std::cout << "content:" << std::endl; write(list(entry), std::cout); @@ -285,45 +285,63 @@ const failable post(const value& val, const std::string& url const list > req = mklist >(h, entry); const failable >, std::string> res = apply >(req, rcons, list(), url, "POST", ch); if (!hasValue(res)) - return std::string(res); + return mkfailure(reason(res)); return value(true); } /** * HTTP PUT. */ -const failable put(const value& val, const std::string& url, CURLHandle& ch) { +const failable put(const value& val, const std::string& url, const CURLHandle& ch) { // Convert value to an ATOM entry const failable, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); if (!hasValue(entry)) - return std::string(entry); + return mkfailure(reason(entry)); if (logContent) { std::cout << "content:" << std::endl; write(list(entry), std::cout); std::cout << std::endl; } - // POST it to the URL + // PUT it to the URL const list h = mklist("Content-Type: application/atom+xml"); const list > req = mklist >(h, entry); const failable >, std::string> res = apply >(req, rcons, list(), url, "PUT", ch); if (!hasValue(res)) - return std::string(res); + return mkfailure(reason(res)); return value(true); } /** * HTTP DELETE. */ -const failable del(const std::string& url, CURLHandle& ch) { +const failable del(const std::string& url, const CURLHandle& ch) { const list > req = mklist(list(), list()); const failable >, std::string> res = apply >(req, rcons, list(), url, "DELETE", ch); if (!hasValue(res)) - return std::string(res); + return mkfailure(reason(res)); return value(true); } +/** + * HTTP client proxy function. + */ +struct proxy { + proxy(const std::string& url, const CURLHandle& ch) : url(url), ch(ch) { + } + + const value operator()(const list& args) const { + failable val = evalExpr(args, url, ch); + if (!hasValue(val)) + return value(); + return val; + } + + const std::string url; + const CURLHandle& ch; +}; + } } diff --git a/cpp/sca/modules/json/json.hpp b/cpp/sca/modules/json/json.hpp index 0d21cfe359..f6c8eb5fe8 100644 --- a/cpp/sca/modules/json/json.hpp +++ b/cpp/sca/modules/json/json.hpp @@ -200,7 +200,7 @@ failable consume(JSONParser* parser, const list& return true; JSString* jstr = JS_NewStringCopyZ(cx, car(ilist).c_str()); if(!JS_ConsumeJSONText(cx, parser, JS_GetStringChars(jstr), JS_GetStringLength(jstr))) - return "JS_ConsumeJSONText failed"; + return mkfailure("JS_ConsumeJSONText failed"); return consume(parser, cdr(ilist), cx); } @@ -211,14 +211,14 @@ const failable, std::string> readJSON(const list& ilist jsval val; JSONParser* parser = JS_BeginJSONParse(cx, &val); if(parser == NULL) - return std::string("JS_BeginJSONParse failed"); + return mkfailure, std::string>("JS_BeginJSONParse failed"); const failable consumed = consume(parser, ilist, cx); if(!JS_FinishJSONParse(cx, parser, JSVAL_NULL)) - return std::string("JS_FinishJSONParse failed"); + return mkfailure, std::string>("JS_FinishJSONParse failed"); if(!hasValue(consumed)) - return std::string(consumed); + return mkfailure, std::string>(reason(consumed)); return list(jsValToValue(val, cx)); } @@ -357,11 +357,11 @@ template const failable writeJSON(const lambda w = writeList(l, o, cx); if (!hasValue(w)) - return std::string(w); + return mkfailure(reason(w)); WriteContext wcx(reduce, initial, cx); if (!JS_Stringify(cx, &val, NULL, JSVAL_NULL, writeCallback, &wcx)) - return std::string("JS_Stringify failed"); + return mkfailure("JS_Stringify failed"); return wcx.accum; } @@ -380,8 +380,7 @@ const failable, std::string> writeJSON(const list& l, c */ const failable, std::string> jsonRequest(const value& id, const value& func, const value& params, json::JSONContext& cx) { const list r = mklist(mklist("id", id), mklist("method", func), mklist("params", params)); - failable, std::string> ls = writeJSON(valuesToElements(r), cx); - return ls; + return writeJSON(valuesToElements(r), cx); } /** -- cgit v1.2.3