From 7bd49ec5b837729569dc91d8dc80d8cedce39705 Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Mon, 16 Oct 2017 18:59:47 +0300 Subject: [PATCH] feat(xod-arduino,rt/c++): implement view-based lists and strings --- packages/xod-arduino/platform/intrusive_ptr.h | 202 --------- packages/xod-arduino/platform/list.h | 400 ------------------ packages/xod-arduino/platform/listFuncs.h | 54 +++ packages/xod-arduino/platform/listViews.h | 326 ++++++++++++++ .../xod-arduino/platform/patchContext.tpl.cpp | 6 +- packages/xod-arduino/platform/preamble.h | 5 +- packages/xod-arduino/platform/program.tpl.cpp | 6 +- packages/xod-arduino/platform/runtime.cpp | 2 - packages/xod-arduino/platform/stl.h | 20 + packages/xod-arduino/src/templates.js | 70 ++- packages/xod-arduino/src/transpiler.js | 30 +- packages/xod-arduino/test-cpp/list.cpp | 343 ++++----------- .../xod-arduino/test/fixtures/program.cpp | 8 +- packages/xod-arduino/test/templates.spec.js | 6 +- .../xod/core/cast-boolean-to-string/any.cpp | 8 +- .../xod/core/cast-number-to-string/any.cpp | 10 +- workspace/__lib__/xod/core/concat/any.cpp | 5 +- 17 files changed, 594 insertions(+), 907 deletions(-) delete mode 100644 packages/xod-arduino/platform/intrusive_ptr.h delete mode 100644 packages/xod-arduino/platform/list.h create mode 100644 packages/xod-arduino/platform/listFuncs.h create mode 100644 packages/xod-arduino/platform/listViews.h create mode 100644 packages/xod-arduino/platform/stl.h diff --git a/packages/xod-arduino/platform/intrusive_ptr.h b/packages/xod-arduino/platform/intrusive_ptr.h deleted file mode 100644 index b7fa312c..00000000 --- a/packages/xod-arduino/platform/intrusive_ptr.h +++ /dev/null @@ -1,202 +0,0 @@ -// ============================================================================ -// -// Intrusive pointer -// -// ============================================================================ - -// This is a stripped down version of Boost v1.63 intrusive pointer. -// -// Copyright (c) 2001, 2002 Peter Dimov -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -// See http://www.boost.org/libs/smart_ptr/intrusive_ptr.html for -// documentation. - -#ifndef XOD_INTRUSIVE_PTR_H -#define XOD_INTRUSIVE_PTR_H - -namespace boost { -// -// intrusive_ptr -// -// A smart pointer that uses intrusive reference counting. -// -// Relies on unqualified calls to -// -// void intrusive_ptr_add_ref(T * p); -// void intrusive_ptr_release(T * p); -// -// (p != 0) -// -// The object is responsible for destroying itself. -// - -template class intrusive_ptr { - private: - typedef intrusive_ptr this_type; - - public: - typedef T element_type; - - constexpr intrusive_ptr() : px(0) {} - - intrusive_ptr(T *p, bool add_ref = true) : px(p) { - if (px != 0 && add_ref) - intrusive_ptr_add_ref(px); - } - - template - intrusive_ptr(intrusive_ptr const &rhs) : px(rhs.get()) { - if (px != 0) - intrusive_ptr_add_ref(px); - } - - intrusive_ptr(intrusive_ptr const &rhs) : px(rhs.px) { - if (px != 0) - intrusive_ptr_add_ref(px); - } - - ~intrusive_ptr() { - if (px != 0) - intrusive_ptr_release(px); - } - - template intrusive_ptr &operator=(intrusive_ptr const &rhs) { - this_type(rhs).swap(*this); - return *this; - } - - intrusive_ptr(intrusive_ptr &&rhs) : px(rhs.px) { rhs.px = 0; } - - intrusive_ptr &operator=(intrusive_ptr &&rhs) { - this_type(static_cast(rhs)).swap(*this); - return *this; - } - - template friend class intrusive_ptr; - - template intrusive_ptr(intrusive_ptr &&rhs) : px(rhs.px) { - rhs.px = 0; - } - - template intrusive_ptr &operator=(intrusive_ptr &&rhs) { - this_type(static_cast &&>(rhs)).swap(*this); - return *this; - } - - intrusive_ptr &operator=(intrusive_ptr const &rhs) { - this_type(rhs).swap(*this); - return *this; - } - - intrusive_ptr &operator=(T *rhs) { - this_type(rhs).swap(*this); - return *this; - } - - void reset() { this_type().swap(*this); } - - void reset(T *rhs) { this_type(rhs).swap(*this); } - - void reset(T *rhs, bool add_ref) { this_type(rhs, add_ref).swap(*this); } - - T *get() const { return px; } - - T *detach() { - T *ret = px; - px = 0; - return ret; - } - - T &operator*() const { return *px; } - - T *operator->() const { return px; } - - operator bool() const { return px != 0; } - - void swap(intrusive_ptr &rhs) { - T *tmp = px; - px = rhs.px; - rhs.px = tmp; - } - - private: - T *px; -}; - -template -inline bool operator==(intrusive_ptr const &a, intrusive_ptr const &b) { - return a.get() == b.get(); -} - -template -inline bool operator!=(intrusive_ptr const &a, intrusive_ptr const &b) { - return a.get() != b.get(); -} - -template -inline bool operator==(intrusive_ptr const &a, U *b) { - return a.get() == b; -} - -template -inline bool operator!=(intrusive_ptr const &a, U *b) { - return a.get() != b; -} - -template -inline bool operator==(T *a, intrusive_ptr const &b) { - return a == b.get(); -} - -template -inline bool operator!=(T *a, intrusive_ptr const &b) { - return a != b.get(); -} - -#if __GNUC__ == 2 && __GNUC_MINOR__ <= 96 - -// Resolve the ambiguity between our op!= and the one in rel_ops - -template -inline bool operator!=(intrusive_ptr const &a, intrusive_ptr const &b) { - return a.get() != b.get(); -} - -#endif - -template -inline bool operator==(intrusive_ptr const &p, nullptr_t) { - return p.get() == 0; -} - -template -inline bool operator==(nullptr_t, intrusive_ptr const &p) { - return p.get() == 0; -} - -template -inline bool operator!=(intrusive_ptr const &p, nullptr_t) { - return p.get() != 0; -} - -template -inline bool operator!=(nullptr_t, intrusive_ptr const &p) { - return p.get() != 0; -} - -template -inline bool operator<(intrusive_ptr const &a, intrusive_ptr const &b) { - return a.get() < b.get(); -} - -template void swap(intrusive_ptr &lhs, intrusive_ptr &rhs) { - lhs.swap(rhs); -} - -} // namespace boost - -#endif // #ifndef XOD_INTRUSIVE_PTR_H diff --git a/packages/xod-arduino/platform/list.h b/packages/xod-arduino/platform/list.h deleted file mode 100644 index a6689e3f..00000000 --- a/packages/xod-arduino/platform/list.h +++ /dev/null @@ -1,400 +0,0 @@ -// ============================================================================ -// -// Immutable dynamic list -// -// ============================================================================ - -#ifndef XOD_LIST_H -#define XOD_LIST_H - -#ifndef XOD_LIST_CHUNK_SIZE -#define XOD_LIST_CHUNK_SIZE 15 -#endif - -namespace xod { -// forward declaration -template class List; -} - -namespace xod { -namespace detail { - -#if XOD_LIST_CHUNK_SIZE < 256 -typedef uint8_t index_t; -#else -typedef size_t index_t; -#endif - -typedef uint8_t refcount_t; -typedef uint8_t depth_t; - -/* - * Bounds define a used range of data within Chunk’s ring buffer. `first` is - * an index (not byte offset) of the first element in range. `last` is an - * index (not byte offset) of the last filled element. I.e. `last` points to - * an existing element, *not* a slot past end. - * - * Value of `first` can be greater than `last`. It means that the range is - * wrapped arround buffer’s origin. - * - * Examples: - * - * - `first == 0 && last == 0`: chunk have 1 element and it is at buffer[0] - * - `first == 0 && last == 15`: chunk have 16 elements spanning from buffer[0] - * to buffer[15] inclusive - * - `first == 14 && last == 2`: given the chunk size == 16 it has 5 elements: - * buffer[14], buffer[15], buffer[0], buffer[1], buffer[2]. - */ -struct Bounds { -#if XOD_LIST_CHUNK_SIZE < 16 - index_t first : 4; - index_t last : 4; -#else - index_t first; - index_t last; -#endif -}; - -template struct Traits { - enum { N = XOD_LIST_CHUNK_SIZE / sizeof(T) }; -}; - -/* - * Ring buffer - */ -struct Chunk { - char buffer[XOD_LIST_CHUNK_SIZE]; - Bounds bounds; - refcount_t _refcount; - - Chunk() { memset(this, 0, sizeof(Chunk)); } - - /* - * Returns number of elements occupied - */ - template index_t usage() { - return (bounds.last - bounds.first + Traits::N) % Traits::N + 1; - } - - template bool isFull() { return usage() == Traits::N; } - - template bool append(T val) { - if (isFull()) - return false; - - appendUnsafe(val); - return true; - } - - template void appendUnsafe(T val) { - auto idx = ++bounds.last; - *((T *)buffer + idx) = val; - } - - template bool concat(T *val, index_t len) { - if (usage() > Traits::N - len) - return false; - - while (len--) - appendUnsafe(*val++); - - return true; - } -}; - -void intrusive_ptr_add_ref(Chunk *chunk) { - // TODO: deal with possible overflow - ++chunk->_refcount; -} - -void intrusive_ptr_release(Chunk *chunk) { - if (--chunk->_refcount == 0) { - delete chunk; - } -} - -template class ListIterator { - typedef List ListT; - typedef const ListT *ListRawPtr; - - public: - ListIterator(ListRawPtr root) { - _stackSize = 0; - if (root->isEmpty()) { - _stack = 0; - } else { - _stack = new ListRawPtr[root->maxDepth()]; - push(root); - drillDownToLeftmost(); - } - } - - ~ListIterator() { - if (_stack) - delete[] _stack; - } - - /* - * Returns false if iteration is done - */ - operator bool() const { return _stackSize > 0; } - - const T &operator*() const { return chunk()->buffer[_indexInChunk]; } - - ListIterator &operator++() { - if (!_stackSize) - return *this; - - ++_indexInChunk; - - if (_indexInChunk > top()->_rightBounds.last) { - // we’ve runned over whole chunk, move to next one - while (true) { - auto branch = pop(); - - if (!_stackSize) - break; - - auto parent = top(); - if (parent->_left == branch) { - // switch to right branch if we just completed with left one - push(parent->_right.get()); - drillDownToLeftmost(); - break; - } - } - } - - return *this; - } - - private: - ListRawPtr top() const { return _stack[_stackSize - 1]; } - - void push(ListRawPtr list) { _stack[_stackSize++] = list; } - - ListRawPtr pop() { return _stack[_stackSize-- - 1]; } - - void drillDownToLeftmost() { - ListRawPtr left; - while ((left = top()->_left.get())) - push(left); - _indexInChunk = top()->_rightBounds.first; - } - - Chunk *chunk() const { return top()->_chunk.get(); } - - private: - ListRawPtr *_stack; - depth_t _stackSize; - index_t _indexInChunk; -}; -} -} // namespace xod::detail - -namespace xod { - -template void intrusive_ptr_add_ref(List *list) { - // TODO: deal with possible overflow - ++list->_refcount; -} - -template void intrusive_ptr_release(List *list) { - if (--list->_refcount == 0) { - delete list; - } -} - -template class List { - typedef boost::intrusive_ptr ChunkPtr; - - public: - typedef boost::intrusive_ptr ListPtr; - typedef detail::ListIterator Iterator; - - static ListPtr empty() { return ListPtr(new List()); } - - static ListPtr of(T val) { - auto list = empty(); - auto chunk = new detail::Chunk(); - chunk->buffer[0] = val; - list->_chunk = chunk; - return list; - } - - static ListPtr fromPlainArray(const T *buf, size_t len) { - auto list = empty(); - if (!len) - return list; - - if (len <= detail::Traits::N) { - // whole buf can be contained within a single chunk - auto chunk = new detail::Chunk(); - memcpy(chunk->buffer, buf, len); - list->_chunk = chunk; - list->_rightBounds.last = chunk->bounds.last = len - 1; - } else { - // split the buffer into two portions - auto leftLen = len / 2; - list->_left = fromPlainArray(buf, leftLen); - list->_right = fromPlainArray(buf + leftLen, len - leftLen); - } - - return list; - } - - bool isEmpty() const { return length() == 0; } - - size_t length() const { - if (_left == nullptr && _right == nullptr) { - return 0; - } else if (chunk()) { - return _rightBounds.last - _rightBounds.first + 1; - } else { - return _left->length() + _right->length(); - } - } - - size_t chunkCount() const { - if (_left) { - return _left->chunkCount() + _right->chunkCount(); - } else if (_chunk) { - return 1; - } else { - return 0; - } - } - - detail::depth_t maxDepth() const { - if (_left) { - auto leftDepth = _left->maxDepth(); - auto rightDepth = _right->maxDepth(); - return 1 + (leftDepth > rightDepth ? leftDepth : rightDepth); - } else { - return 1; - } - } - - ListPtr append(T val) const { - if (length() == 0) { - return of(val); - } - - auto chunk = this->chunk(); - - if (chunk && isChunkTailFree()) { - bool amend = chunk->append(val); - if (amend) { - auto list = empty(); - list->_chunk = chunk; - list->_rightBounds.last = _rightBounds.last + 1; - return list; - } - } - - auto list = empty(); - list->_left = const_cast(this); - list->_right = of(val); - return list; - } - - ListPtr concat(ListPtr other) const { - if (isEmpty()) { - return other; - } - - if (other->isEmpty()) { - return ListPtr(const_cast(this)); - } - - auto thisChunk = this->chunk(); - auto otherChunk = other->chunk(); - auto otherLen = other->length(); - if (thisChunk && isChunkTailFree() && otherChunk) { - bool amend = thisChunk->concat(otherChunk->buffer, otherLen); - if (amend) { - auto list = empty(); - list->_chunk = thisChunk; - list->_rightBounds.first = _rightBounds.first; - list->_rightBounds.last = thisChunk->bounds.last; - return list; - } - } - - auto list = empty(); - list->_left = const_cast(this); - list->_right = other; - return list; - } - - void toPlainArrayUnsafe(T *buf) const { - auto chunk = this->chunk(); - if (chunk) { - memcpy(buf, chunk->buffer, length() * sizeof(T)); - } else if (_left) { - _left->toPlainArrayUnsafe(buf); - _right->toPlainArrayUnsafe(buf + _left->length()); - } - } - - Iterator iterate() const { return Iterator(this); } - - protected: - ChunkPtr chunk() const { - if (_left == nullptr) { - return _chunk; - } else { - return nullptr; - } - } - - bool isChunkTailFree() const { - return _chunk->bounds.last == _rightBounds.last; - } - - private: - List() { memset(this, 0, sizeof(List)); } - - ~List() { - // _right branch is in union with _chunk. Call a proper destructor - // explicitly - if (_left) { - _right.~ListPtr(); - } else { - _chunk.~ChunkPtr(); - } - } - - friend Iterator; - - // There are two possible conditions of a list. It either: - // - // - concatenation of two children, in that case `_left` and `_right` point - // to branches - // - a leaf with data, in that case `_left == nullptr` and `_chunk` contains - // pointer to the data - // - // Use union to save one pointer size, consider `_left` nullness to - // understand the condition. - ListPtr _left; - union { - ListPtr _right; - ChunkPtr _chunk; - }; - - // Branch bounds inside chunks. In case if this is a leaf, only _rightBounds - // is used. - // - // Note that the bounds will not match bounds in chunks themselves. It’s - // because the chunks are reused across many List’s. - detail::Bounds _leftBounds; - detail::Bounds _rightBounds; - - friend void intrusive_ptr_add_ref(List *list); - friend void intrusive_ptr_release(List *list); - detail::refcount_t _refcount; -}; // class List - -} // namespace xod - -#endif // #ifndef XOD_LIST_H diff --git a/packages/xod-arduino/platform/listFuncs.h b/packages/xod-arduino/platform/listFuncs.h new file mode 100644 index 00000000..84ed63f8 --- /dev/null +++ b/packages/xod-arduino/platform/listFuncs.h @@ -0,0 +1,54 @@ +/*============================================================================= + * + * + * Basic algorithms for XOD lists + * + * + =============================================================================*/ + +#ifndef XOD_LIST_FUNCS_H +#define XOD_LIST_FUNCS_H + +#include "listViews.h" + +namespace xod { + +/* + * Folds a list from left. Also known as "reduce". + */ +template +TR foldl(List xs, TR (*func)(TR, T), TR acc) { + for (auto it = xs.iterate(); it; ++it) + acc = func(acc, *it); + return acc; +} + +template size_t lengthReducer(size_t len, T) { + return len + 1; +} + +/* + * Computes length of a list. + */ +template size_t length(List xs) { + return foldl(xs, lengthReducer, (size_t)0); +} + +template T* dumpReducer(T* buff, T x) { + *buff = x; + return buff + 1; +} + +/* + * Copies a list content into a memory buffer. + * + * It is expected that `outBuff` has enough size to fit all the data. + */ +template size_t dump(List xs, T* outBuff) { + T* buffEnd = foldl(xs, dumpReducer, outBuff); + return buffEnd - outBuff; +} + +} // namespace xod + +#endif diff --git a/packages/xod-arduino/platform/listViews.h b/packages/xod-arduino/platform/listViews.h new file mode 100644 index 00000000..08f44e10 --- /dev/null +++ b/packages/xod-arduino/platform/listViews.h @@ -0,0 +1,326 @@ +/*============================================================================= + * + * + * XOD-specific list/array implementations + * + * + =============================================================================*/ + +#ifndef XOD_LIST_H +#define XOD_LIST_H + +namespace xod { +namespace detail { + +/* + * Cursors are used internaly by iterators and list views. They are not exposed + * directly to a list consumer. + * + * The base `Cursor` is an interface which provides the bare minimum of methods + * to facilitate a single iteration pass. + */ +template class Cursor { + public: + virtual ~Cursor() { } + virtual bool isValid() const = 0; + virtual bool value(T* out) const = 0; + virtual void next() = 0; +}; + +template class NilCursor : public Cursor { + public: + virtual bool isValid() const { return false; } + virtual bool value(T* out) const { return false; } + virtual void next() { } +}; + +} // namespace detail + +/* + * Iterator is an object used to iterate a list once. + * + * Users create new iterators by calling `someList.iterate()`. + * Iterators are created on stack and are supposed to have a + * short live, e.g. for a duration of `for` loop or node’s + * `evaluate` function. Iterators can’t be copied. + * + * Implemented as a pimpl pattern wrapper over the cursor. + * Once created for a cursor, an iterator owns that cursor + * and will delete the cursor object once destroyed itself. + */ +template +class Iterator { + public: + static Iterator nil() { + return Iterator(new detail::NilCursor()); + } + + Iterator(detail::Cursor* cursor) + : _cursor(cursor) + { } + + ~Iterator() { + if (_cursor) + delete _cursor; + } + + Iterator(const Iterator& that) = delete; + Iterator& operator=(const Iterator& that) = delete; + + Iterator(Iterator&& it) + : _cursor(it._cursor) + { + it._cursor = nullptr; + } + + Iterator& operator=(Iterator&& it) { + auto tmp = it._cursor; + it._cursor = _cursor; + _cursor = tmp; + return *this; + } + + operator bool() const { return _cursor->isValid(); } + + bool value(T* out) const { + return _cursor->value(out); + } + + T operator*() const { + T out; + _cursor->value(&out); + return out; + } + + Iterator& operator++() { + _cursor->next(); + return *this; + } + + private: + detail::Cursor* _cursor; +}; + +/* + * An interface for a list view. A particular list view provides a new + * kind of iteration over existing data. This way we can use list slices, + * list concatenations, list rotations, etc without introducing new data + * buffers. We just change the way already existing data is iterated. + * + * ListView is not exposed to a list user directly, it is used internally + * by the List class. However, deriving a new ListView is necessary if you + * make a new list/string processing node. + */ +template class ListView { + public: + virtual Iterator iterate() const = 0; +}; + +/* + * The list as it seen by data consumers. Have a single method `iterate` + * to create a new iterator. + * + * Implemented as pimpl pattern wrapper over a list view. Takes pointer + * to a list view in constructor and expects the view will be alive for + * the whole life time of the list. + */ +template class List { + public: + constexpr List() + : _view(nullptr) + { } + + List(const ListView* view) + : _view(view) + { } + + Iterator iterate() const { + return _view ? _view->iterate() : Iterator::nil(); + } + + // pre 0.15.0 backward compatibility + List* operator->() __attribute__ ((deprecated)) { return this; } + const List* operator->() const __attribute__ ((deprecated)) { return this; } + + private: + const ListView* _view; +}; + +/* + * A list view over an old good plain C array. + * + * Expects the array will be alive for the whole life time of the + * view. + */ +template class PlainListView : public ListView { + public: + class Cursor : public detail::Cursor { + public: + Cursor(const PlainListView* owner) + : _owner(owner) + , _idx(0) + { } + + bool isValid() const override { + return _idx < _owner->_len; + } + + bool value(T* out) const override { + if (!isValid()) + return false; + *out = _owner->_data[_idx]; + return true; + } + + void next() override { ++_idx; } + + private: + const PlainListView* _owner; + size_t _idx; + }; + + public: + PlainListView(const T* data, size_t len) + : _data(data) + , _len(len) + { } + + virtual Iterator iterate() const override { + return Iterator(new Cursor(this)); + } + + private: + friend class Cursor; + const T* _data; + size_t _len; +}; + +/* + * A list view over a null-terminated C-String. + * + * Expects the char buffer will be alive for the whole life time of the view. + * You can use string literals as a buffer, since they are persistent for + * the program execution time. + */ +class CStringView : public ListView { + public: + class Cursor : public detail::Cursor { + public: + Cursor(const char* str) + : _ptr(str) + { } + + bool isValid() const override { + return (bool)*_ptr; + } + + bool value(char* out) const override { + *out = *_ptr; + return (bool)*_ptr; + } + + void next() override { ++_ptr; } + + private: + const char* _ptr; + }; + + public: + CStringView(const char* str = nullptr) + : _str(str) + { } + + CStringView& operator=(const CStringView& rhs) { + _str = rhs._str; + return *this; + } + + virtual Iterator iterate() const override { + return _str ? Iterator(new Cursor(_str)) : Iterator::nil(); + } + + private: + friend class Cursor; + const char* _str; +}; + +/* + * A list view over two other lists (Left and Right) which first iterates the + * left one, and when exhausted, iterates the right one. + * + * Expects both Left and Right to be alive for the whole view life time. + */ +template class ConcatListView : public ListView { + public: + class Cursor : public detail::Cursor { + public: + Cursor(Iterator&& left, Iterator&& right) + : _left(std::move(left)) + , _right(std::move(right)) + { } + + bool isValid() const override { + return _left || _right; + } + + bool value(T* out) const override { + return _left.value(out) || _right.value(out); + } + + void next() override { + _left ? ++_left : ++_right; + } + + private: + Iterator _left; + Iterator _right; + }; + + public: + ConcatListView() { } + + ConcatListView(List left, List right) + : _left(left) + , _right(right) + { } + + ConcatListView& operator=(const ConcatListView& rhs) { + _left = rhs._left; + _right = rhs._right; + return *this; + } + + virtual Iterator iterate() const override { + return Iterator(new Cursor(_left.iterate(), _right.iterate())); + } + + private: + friend class Cursor; + const List _left; + const List _right; +}; + +//---------------------------------------------------------------------------- +// Text string helpers +//---------------------------------------------------------------------------- + +using XString = List; + +/* + * List and list view in a single pack. An utility used to define constant + * string literals in XOD. + */ +class XStringCString : public XString { + public: + XStringCString(const char* str) + : XString(&_view) + , _view(str) + { } + + private: + CStringView _view; +}; + +} // namespace xod + +#endif diff --git a/packages/xod-arduino/platform/patchContext.tpl.cpp b/packages/xod-arduino/platform/patchContext.tpl.cpp index 38f65a9d..4d36ebd4 100644 --- a/packages/xod-arduino/platform/patchContext.tpl.cpp +++ b/packages/xod-arduino/platform/patchContext.tpl.cpp @@ -4,7 +4,7 @@ struct Storage { State state; {{#each outputs}} - {{ type }} output_{{ pinKey }}; + {{ cppType type }} output_{{ pinKey }}; {{/each}} }; @@ -23,9 +23,9 @@ State* getState(NodeId nid) { } {{#each inputs}} -using input_{{ pinKey }} = InputDescriptor<{{ type }}, offsetof(Wiring, input_{{ pinKey }})>; +using input_{{ pinKey }} = InputDescriptor<{{ cppType type }}, offsetof(Wiring, input_{{ pinKey }})>; {{/each}} {{#each outputs}} -using output_{{ pinKey }} = OutputDescriptor<{{ type }}, offsetof(Wiring, output_{{ pinKey }}), offsetof(Storage, output_{{ pinKey }}), {{@index}}>; +using output_{{ pinKey }} = OutputDescriptor<{{ cppType type }}, offsetof(Wiring, output_{{ pinKey }}), offsetof(Storage, output_{{ pinKey }}), {{@index}}>; {{/each}} diff --git a/packages/xod-arduino/platform/preamble.h b/packages/xod-arduino/platform/preamble.h index 0f0297fe..8d6c683e 100644 --- a/packages/xod-arduino/platform/preamble.h +++ b/packages/xod-arduino/platform/preamble.h @@ -5,8 +5,9 @@ // // Rough code overview: // -// - Intrusive pointer (a smart pointer with ref counter) -// - Immutable dynamic list data structure +// - Configuration section +// - STL shim +// - Immutable list classes and functions // - XOD runtime environment // - Native node implementation // - Program graph definition diff --git a/packages/xod-arduino/platform/program.tpl.cpp b/packages/xod-arduino/platform/program.tpl.cpp index f3d43931..3dea3c38 100644 --- a/packages/xod-arduino/platform/program.tpl.cpp +++ b/packages/xod-arduino/platform/program.tpl.cpp @@ -16,10 +16,14 @@ namespace xod { {{#each nodes}} {{mergePins }} // Storage of #{{ id }} {{ patch.owner }}/{{ patch.libName }}/{{ patch.patchName }} + {{#each outputs }} + {{ decltype type value }} node_{{ ../id }}_output_{{ pinKey }} = {{ cppValue type value }}; + {{~/each}} + {{ns patch }}::Storage storage_{{ id }} = { { }, // state {{#each outputs }} - {{ value }}{{#unless @last }},{{/unless }} // output_{{ pinKey }} + node_{{ ../id }}_output_{{ pinKey }} {{#unless @last }},{{/unless }} {{/each}} }; {{/each}} diff --git a/packages/xod-arduino/platform/runtime.cpp b/packages/xod-arduino/platform/runtime.cpp index 405085c5..e5a089e5 100644 --- a/packages/xod-arduino/platform/runtime.cpp +++ b/packages/xod-arduino/platform/runtime.cpp @@ -141,8 +141,6 @@ typedef uint8_t DirtyFlags; typedef unsigned long TimeMs; typedef void (*EvalFuncPtr)(Context ctx); -typedef xod::List::ListPtr XString; - /* * Each input stores a reference to its upstream node so that we can get values * on input pins. Having a direct pointer to the value is not enough because we diff --git a/packages/xod-arduino/platform/stl.h b/packages/xod-arduino/platform/stl.h new file mode 100644 index 00000000..86e3dc90 --- /dev/null +++ b/packages/xod-arduino/platform/stl.h @@ -0,0 +1,20 @@ +/*============================================================================= + * + * + * STL shim. Provides implementation for vital std::* constructs + * + * + =============================================================================*/ + +namespace std { + +template< class T > struct remove_reference {typedef T type;}; +template< class T > struct remove_reference {typedef T type;}; +template< class T > struct remove_reference {typedef T type;}; + +template +typename remove_reference::type&& move(T&& a) { + return static_cast::type&&>(a); +} + +} // namespace std diff --git a/packages/xod-arduino/src/templates.js b/packages/xod-arduino/src/templates.js index 3fe3f441..5505302d 100644 --- a/packages/xod-arduino/src/templates.js +++ b/packages/xod-arduino/src/templates.js @@ -1,6 +1,8 @@ import R from 'ramda'; import Handlebars from 'handlebars'; +import { PIN_TYPE } from 'xod-project'; + import { def } from './types'; import configTpl from '../platform/configuration.tpl.cpp'; @@ -9,8 +11,9 @@ import implListTpl from '../platform/implList.tpl.cpp'; import programTpl from '../platform/program.tpl.cpp'; import preambleH from '../platform/preamble.h'; -import intrusivePtrH from '../platform/intrusive_ptr.h'; -import listH from '../platform/list.h'; +import listViewsH from '../platform/listViews.h'; +import listFuncsH from '../platform/listFuncs.h'; +import stlH from '../platform/stl.h'; import runtimeCpp from '../platform/runtime.cpp'; // ============================================================================= @@ -19,20 +22,27 @@ import runtimeCpp from '../platform/runtime.cpp'; // // ============================================================================= const trimTrailingWhitespace = R.replace(/\s+$/gm, '\n'); + +const omitLocalIncludes = R.replace(/#include ".*$/gm, ''); + const indexByPinKey = R.indexBy(R.prop('pinKey')); + const getPatchPins = direction => R.compose( indexByPinKey, R.path(['patch', direction]) ); + const omitNullValues = R.map(R.when( R.propSatisfies(R.isNil, 'value'), R.omit(['value']) )); + const getNodePins = direction => R.compose( indexByPinKey, omitNullValues, R.prop(direction) ); + const mergeAndListPins = (direction, node) => R.compose( R.values, R.converge( @@ -43,22 +53,60 @@ const mergeAndListPins = (direction, node) => R.compose( ] ) )(node); + +// Converts DataType value to a corresponding C++ storage type +const cppType = def( + 'cppType :: DataType -> String', + R.propOr('unknown_type', R.__, { + [PIN_TYPE.PULSE]: 'Logic', + [PIN_TYPE.BOOLEAN]: 'Logic', + [PIN_TYPE.NUMBER]: 'Number', + [PIN_TYPE.STRING]: 'XString', + }) +); + +// Formats a plain JS string into C++ string object +const cppStringLiteral = def( + 'cppStringLiteral :: String -> String', + R.ifElse( + R.isEmpty, + R.always('XString()'), + str => `XStringCString("${str}")` + ) +); + +// ============================================================================= +// +// Handlebars helpers +// +// ============================================================================= + // Merge patch pins data with node pins data Handlebars.registerHelper('mergePins', function mergePins() { this.inputs = mergeAndListPins('inputs', this); this.outputs = mergeAndListPins('outputs', this); }); + // Generate patch-level namespace name Handlebars.registerHelper('ns', R.compose( R.join('__'), R.props(['owner', 'libName', 'patchName']) )); + +// Returns declaration type specifier for an initial value of an output +Handlebars.registerHelper('decltype', (type, value) => ( + (type === PIN_TYPE.STRING && value !== '') + ? 'static XStringCString' + : `constexpr ${cppType(type)}` +)); + // Check that variable is not undefined Handlebars.registerHelper('exists', function existsHelper(variable, options) { return (typeof variable !== 'undefined') ? options.fn(this) : options.inverse(this); }); + // Temporary switch to global C++ namespace Handlebars.registerHelper('global', function global(options) { return [ @@ -71,6 +119,19 @@ Handlebars.registerHelper('global', function global(options) { ].join('\n'); }); +Handlebars.registerHelper('cppType', type => cppType(type)); + +// Converts JS-typed data value to a string that is valid and expected +// C++ literal representing that value +Handlebars.registerHelper('cppValue', (type, value) => + R.propOr(R.always('unknown_type'), type, { + [PIN_TYPE.PULSE]: R.always('false'), + [PIN_TYPE.BOOLEAN]: R.toString, + [PIN_TYPE.NUMBER]: R.toString, + [PIN_TYPE.STRING]: cppStringLiteral, + })(value) +); + // ============================================================================= // // Templates and settings @@ -147,9 +208,10 @@ export const renderProject = def( return R.join('\n')([ preambleH, - intrusivePtrH, - listH, config, + stlH, + listViewsH, + omitLocalIncludes(listFuncsH), runtimeCpp, impls, program, diff --git a/packages/xod-arduino/src/transpiler.js b/packages/xod-arduino/src/transpiler.js index d2ef08ea..d6a59fc6 100644 --- a/packages/xod-arduino/src/transpiler.js +++ b/packages/xod-arduino/src/transpiler.js @@ -9,12 +9,6 @@ import { renderProject } from './templates'; import { DEFAULT_TRANSPILATION_OPTIONS } from './constants'; const ARDUINO_IMPLS = ['cpp', 'arduino']; -const TYPES_MAP = { - number: 'Number', - pulse: 'Logic', - boolean: 'Logic', - string: 'XString', -}; //----------------------------------------------------------------------------- // @@ -106,19 +100,6 @@ const toposortProject = def( )(path, project) ); -/** - * Converts JS-typed data value to a string that is valid and expected - * C++ literal representing that value - */ -const formatValueLiteral = def( - 'formatValueLiteral :: DataValue -> String', - R.cond([ - [R.equals(''), R.always('::xod::List::empty()')], - [R.is(String), x => `::xod::List::fromPlainArray("${x}", ${x.length})`], - [R.T, R.toString], - ]) -); - //----------------------------------------------------------------------------- // // Transformers @@ -150,10 +131,9 @@ const createTPatches = def( const outputs = R.compose( R.map(R.applySpec({ - type: R.compose(R.prop(R.__, TYPES_MAP), Project.getPinType), + type: Project.getPinType, pinKey: Project.getPinLabel, value: R.compose( - formatValueLiteral, Project.defaultValueOfType, Project.getPinType ), @@ -163,7 +143,7 @@ const createTPatches = def( )(patch); const inputs = R.compose( R.map(R.applySpec({ - type: R.compose(R.prop(R.__, TYPES_MAP), Project.getPinType), + type: Project.getPinType, pinKey: Project.getPinLabel, })), Project.normalizePinLabels, @@ -238,10 +218,8 @@ const getTNodeOutputs = def( R.mapObjIndexed((links, pinKey) => ({ to: getLinksInputNodeIds(links), pinKey: nodePins[pinKey], - value: formatValueLiteral( - Project.getBoundValue(pinKey, node) - .getOrElse(getDefaultPinValue(pinKey, node, project)) - ), + value: Project.getBoundValue(pinKey, node) + .getOrElse(getDefaultPinValue(pinKey, node, project)), })), R.groupBy(Project.getLinkOutputPinKey), R.filter(Project.isLinkOutputNodeIdEquals(nodeId)), diff --git a/packages/xod-arduino/test-cpp/list.cpp b/packages/xod-arduino/test-cpp/list.cpp index 9206201b..40902e33 100644 --- a/packages/xod-arduino/test-cpp/list.cpp +++ b/packages/xod-arduino/test-cpp/list.cpp @@ -1,268 +1,105 @@ -#include #include "catch.hpp" - -#define XOD_LIST_CHUNK_SIZE 4 -#include "../platform/intrusive_ptr.h" -#include "../platform/list.h" +#include "../platform/listViews.h" +#include "../platform/listFuncs.h" using namespace xod; -TEST_CASE("List append", "[list]") { - auto list = List::empty(); - char plain[256] = { 0 }; +TEST_CASE("Plain list", "[list]") { + int input[] = { 12, 23, 42 }; + auto view = PlainListView(input, 3); + auto list = List(&view); - SECTION("zero length for empty") { - REQUIRE(list->length() == 0); - list->toPlainArrayUnsafe(plain); - REQUIRE(strlen(plain) == 0); - } + SECTION("iteration") { + auto it = list.iterate(); - SECTION("single element") { - list = list->append('A'); - REQUIRE(list->length() == 1); + REQUIRE((bool)it); + REQUIRE(*it == 12); - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "A") == 0); - } + ++it; + REQUIRE((bool)it); + REQUIRE(*it == 23); - SECTION("few elements under chunk size") { - list = list->append('A'); - list = list->append('B'); - list = list->append('C'); - REQUIRE(list->length() == 3); + ++it; + REQUIRE((bool)it); + REQUIRE(*it == 42); - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "ABC") == 0); - } - - SECTION("instances are immutable") { - auto list1 = list->append('A'); - auto list2 = list1->append('B'); - auto list3 = list2->append('C'); - REQUIRE(list->length() == 0); - REQUIRE(list1->length() == 1); - REQUIRE(list2->length() == 2); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "") == 0); - - memset(plain, 0, sizeof plain); - list1->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "A") == 0); - - memset(plain, 0, sizeof plain); - list2->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "AB") == 0); - } - - SECTION("number of elements equal to chunk size") { - list = list->append('A'); - list = list->append('B'); - list = list->append('C'); - list = list->append('D'); - REQUIRE(list->length() == 4); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "ABCD") == 0); - } - - SECTION("number of elements exceeding chunk size") { - list = list->append('A'); - list = list->append('B'); - list = list->append('C'); - list = list->append('D'); - list = list->append('E'); - REQUIRE(list->length() == 5); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "ABCDE") == 0); - } -} - -TEST_CASE("List concat", "[list]") { - char plain[256] = { 0 }; - - SECTION("empty to empty") { - auto nil = List::empty()->concat(List::empty()); - REQUIRE(nil->length() == 0); - REQUIRE(nil->chunkCount() == 0); - } - - SECTION("something to empty") { - auto rhs = List::of('X'); - auto list = List::empty()->concat(rhs); - REQUIRE(list->length() == 1); - REQUIRE(list->chunkCount() == 1); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "X") == 0); - } - - SECTION("empty to something") { - auto lhs = List::of('X'); - auto list = lhs->concat(List::empty()); - REQUIRE(list->length() == 1); - REQUIRE(list->chunkCount() == 1); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "X") == 0); - } - - SECTION("something to something") { - auto lhs = List::of('X'); - auto rhs = List::of('Y'); - auto list = lhs->concat(rhs); - REQUIRE(list->length() == 2); - REQUIRE(list->chunkCount() == 1); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "XY") == 0); - } - - SECTION("something to something twice") { - auto x = List::of('X'); - auto y = List::of('Y'); - auto z = List::of('Z'); - auto xy = x->concat(y); - auto xz = x->concat(z); - REQUIRE(xy->length() == 2); - REQUIRE(xz->length() == 2); - REQUIRE(xy->chunkCount() == 1); // should reuse x’s chunk - REQUIRE(xz->chunkCount() == 2); // can’t reuse since occupied by y - - xy->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "XY") == 0); - - xz->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "XZ") == 0); - - auto ity = xy->iterate(); - REQUIRE(*ity == 'X'); - REQUIRE((bool)ity == true); - REQUIRE(*++ity == 'Y'); - REQUIRE((bool)++ity == false); - - auto itz = xz->iterate(); - REQUIRE(*itz == 'X'); - REQUIRE((bool)itz == true); - REQUIRE(*++itz == 'Z'); - REQUIRE((bool)++itz == false); - } - - SECTION("something to something with oversize") { - auto lhs = List::fromPlainArray("ABC", 3); - auto rhs = List::fromPlainArray("XYZ", 3); - auto list = lhs->concat(rhs); - REQUIRE(list->length() == 6); - REQUIRE(list->chunkCount() == 2); - - list->toPlainArrayUnsafe(plain); - REQUIRE(strcmp(plain, "ABCXYZ") == 0); - } -} - -TEST_CASE("List plain buffer roundtrip", "[list]") { - char output[256] = { 0 }; - - SECTION("under chunk size") { - char input[] = "ABC"; - auto list = List::fromPlainArray(input, strlen(input)); - REQUIRE(list->chunkCount() == 1); - - list->toPlainArrayUnsafe(output); - REQUIRE(strcmp(output, input) == 0); - } - - SECTION("two chunks") { - char input[] = "ABCXYZ"; - auto list = List::fromPlainArray(input, strlen(input)); - REQUIRE(list->chunkCount() == 2); - - list->toPlainArrayUnsafe(output); - REQUIRE(strcmp(output, input) == 0); - } - - SECTION("many chunks") { - char input[] = "0123456789ABCDEF"; - auto list = List::fromPlainArray(input, strlen(input)); - REQUIRE(list->chunkCount() == 4); - - list->toPlainArrayUnsafe(output); - REQUIRE(strcmp(output, input) == 0); - } -} - -TEST_CASE("List iterator", "[list]") { - SECTION("empty list") { - auto list = List::empty(); - auto it = list->iterate(); - REQUIRE((bool)it == false); - } - - SECTION("single item list") { - auto list = List::of('A'); - auto it = list->iterate(); - - REQUIRE(*it == 'A'); - REQUIRE((bool)it == true); - - REQUIRE((bool)++it == false); - } - - SECTION("multiple items in a single chunk") { - char input[] = "ABC"; - auto list = List::fromPlainArray(input, strlen(input)); - auto it = list->iterate(); - - REQUIRE(*it == 'A'); - REQUIRE((bool)it == true); - - REQUIRE(*++it == 'B'); - REQUIRE((bool)it == true); - - REQUIRE(*++it == 'C'); - REQUIRE((bool)it == true); - - REQUIRE((bool)++it == false); - } - - SECTION("over two chunks") { - char input[] = "ABCXYZ"; - size_t len = strlen(input); - auto list = List::fromPlainArray(input, len); - auto it = list->iterate(); - - for (size_t i = 0; i < len; ++i) { - REQUIRE(*it == input[i]); - REQUIRE((bool)it == true); - ++it; + ++it; + REQUIRE(!it); } - REQUIRE((bool)it == false); - } - - SECTION("over multiple chunks") { - char input[] = "123456789ABC"; - size_t len = strlen(input); - auto list = List::fromPlainArray(input, len); - auto it = list->iterate(); - - for (size_t i = 0; i < len; ++i) { - REQUIRE(*it == input[i]); - REQUIRE((bool)it == true); - ++it; + SECTION("length") { + auto len = length(list); + REQUIRE(len == 3); } - REQUIRE((bool)it == false); - } - - SECTION("iteration over the end is idempotent") { - auto list = List::of('A'); - auto it = list->iterate(); - ++it; - ++it; - ++it; - REQUIRE((bool)it == false); - } + SECTION("dump") { + int output[10]; + size_t n = dump(list, output); + REQUIRE(n == 3); + REQUIRE(output[0] == 12); + REQUIRE(output[1] == 23); + REQUIRE(output[2] == 42); + } +} + +TEST_CASE("Concat list", "[list]") { + int input1[] = { 12, 23 }; + int input2[] = { 42, 56 }; + auto view1 = PlainListView(input1, 2); + auto view2 = PlainListView(input2, 2); + auto view = ConcatListView( + List(&view1), + List(&view2)); + auto list = List(&view); + + SECTION("iteration") { + auto it = list.iterate(); + + REQUIRE((bool)it); + REQUIRE(*it == 12); + + ++it; + REQUIRE((bool)it); + REQUIRE(*it == 23); + + ++it; + REQUIRE((bool)it); + REQUIRE(*it == 42); + + ++it; + REQUIRE((bool)it); + REQUIRE(*it == 56); + + ++it; + REQUIRE(!it); + } + + SECTION("length") { + auto len = length(list); + REQUIRE(len == 4); + } + + SECTION("dump") { + int output[10]; + size_t n = dump(list, output); + REQUIRE(n == 4); + REQUIRE(output[0] == 12); + REQUIRE(output[1] == 23); + REQUIRE(output[2] == 42); + REQUIRE(output[3] == 56); + } +} + +TEST_CASE("XString", "[list]") { + SECTION("concatenation") { + auto part1 = CStringView("Di"); + auto part2 = CStringView("gun!"); + auto view = ConcatListView(XString(&part1), XString(&part2)); + auto list = XString(&view); + char output[10] = { 0 }; + size_t n = dump(list, output); + REQUIRE(n == 6); + REQUIRE(std::string(output) == std::string("Digun!")); + } } diff --git a/packages/xod-arduino/test/fixtures/program.cpp b/packages/xod-arduino/test/fixtures/program.cpp index 2789019b..e27aa3f6 100644 --- a/packages/xod-arduino/test/fixtures/program.cpp +++ b/packages/xod-arduino/test/fixtures/program.cpp @@ -13,15 +13,19 @@ namespace xod { //------------------------------------------------------------------------- // Storage of #0 xod/math/multiply + constexpr Number node_0_output_OUT = 42; xod__math__multiply::Storage storage_0 = { { }, // state - 42 // output_OUT + node_0_output_OUT + }; // Storage of #1 xod/math/multiply + constexpr Number node_1_output_OUT = 0; xod__math__multiply::Storage storage_1 = { { }, // state - 0 // output_OUT + node_1_output_OUT + }; DirtyFlags g_dirtyFlags[NODE_COUNT] = { diff --git a/packages/xod-arduino/test/templates.spec.js b/packages/xod-arduino/test/templates.spec.js index a6298ecd..6b0cf693 100644 --- a/packages/xod-arduino/test/templates.spec.js +++ b/packages/xod-arduino/test/templates.spec.js @@ -26,18 +26,18 @@ describe('xod-arduino templates', () => { patchName: 'multiply', outputs: [ { - type: 'Number', + type: 'number', pinKey: 'OUT', value: 0, }, ], inputs: [ { - type: 'Number', + type: 'number', pinKey: 'IN1', }, { - type: 'Number', + type: 'number', pinKey: 'IN2', }, ], diff --git a/workspace/__lib__/xod/core/cast-boolean-to-string/any.cpp b/workspace/__lib__/xod/core/cast-boolean-to-string/any.cpp index b705b54a..2beed117 100644 --- a/workspace/__lib__/xod/core/cast-boolean-to-string/any.cpp +++ b/workspace/__lib__/xod/core/cast-boolean-to-string/any.cpp @@ -1,12 +1,12 @@ struct State { + CStringView view; }; {{ GENERATED_CODE }} void evaluate(Context ctx) { + auto state = getState(ctx); auto x = getValue(ctx); - auto xstr = x - ? ::xod::List::fromPlainArray("true", 4) - : ::xod::List::fromPlainArray("false", 5); - emitValue(ctx, xstr); + state->view = CStringView(x ? "true" : "false"); + emitValue(ctx, XString(&state->view)); } diff --git a/workspace/__lib__/xod/core/cast-number-to-string/any.cpp b/workspace/__lib__/xod/core/cast-number-to-string/any.cpp index 71bed130..dbf2476f 100644 --- a/workspace/__lib__/xod/core/cast-number-to-string/any.cpp +++ b/workspace/__lib__/xod/core/cast-number-to-string/any.cpp @@ -1,12 +1,14 @@ struct State { + char str[16]; + CStringView view; + State() : view(str) { } }; {{ GENERATED_CODE }} void evaluate(Context ctx) { - char str[16]; + auto state = getState(ctx); auto num = getValue(ctx); - dtostrf(num, 0, 2, str); - auto xstr = ::xod::List::fromPlainArray(str, strlen(str)); - emitValue(ctx, xstr); + dtostrf(num, 0, 2, state->str); + emitValue(ctx, XString(&state->view)); } diff --git a/workspace/__lib__/xod/core/concat/any.cpp b/workspace/__lib__/xod/core/concat/any.cpp index ce128cdd..49f16cfa 100644 --- a/workspace/__lib__/xod/core/concat/any.cpp +++ b/workspace/__lib__/xod/core/concat/any.cpp @@ -1,10 +1,13 @@ struct State { + ConcatListView view; }; {{ GENERATED_CODE }} void evaluate(Context ctx) { + auto state = getState(ctx); auto head = getValue(ctx); auto tail = getValue(ctx); - emitValue(ctx, head->concat(tail)); + state->view = ConcatListView(head, tail); + emitValue(ctx, XString(&state->view)); }