mirror of
https://github.com/xodio/xod.git
synced 2026-03-19 15:16:58 +01:00
feat(xod-arduino,rt/c++): implement view-based lists and strings
This commit is contained in:
@@ -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 T> 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 <class U>
|
||||
intrusive_ptr(intrusive_ptr<U> 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 <class U> intrusive_ptr &operator=(intrusive_ptr<U> 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<intrusive_ptr &&>(rhs)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U> friend class intrusive_ptr;
|
||||
|
||||
template <class U> intrusive_ptr(intrusive_ptr<U> &&rhs) : px(rhs.px) {
|
||||
rhs.px = 0;
|
||||
}
|
||||
|
||||
template <class U> intrusive_ptr &operator=(intrusive_ptr<U> &&rhs) {
|
||||
this_type(static_cast<intrusive_ptr<U> &&>(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 <class T, class U>
|
||||
inline bool operator==(intrusive_ptr<T> const &a, intrusive_ptr<U> const &b) {
|
||||
return a.get() == b.get();
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
inline bool operator!=(intrusive_ptr<T> const &a, intrusive_ptr<U> const &b) {
|
||||
return a.get() != b.get();
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
inline bool operator==(intrusive_ptr<T> const &a, U *b) {
|
||||
return a.get() == b;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
inline bool operator!=(intrusive_ptr<T> const &a, U *b) {
|
||||
return a.get() != b;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
inline bool operator==(T *a, intrusive_ptr<U> const &b) {
|
||||
return a == b.get();
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
inline bool operator!=(T *a, intrusive_ptr<U> 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 <class T>
|
||||
inline bool operator!=(intrusive_ptr<T> const &a, intrusive_ptr<T> const &b) {
|
||||
return a.get() != b.get();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
inline bool operator==(intrusive_ptr<T> const &p, nullptr_t) {
|
||||
return p.get() == 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool operator==(nullptr_t, intrusive_ptr<T> const &p) {
|
||||
return p.get() == 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool operator!=(intrusive_ptr<T> const &p, nullptr_t) {
|
||||
return p.get() != 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool operator!=(nullptr_t, intrusive_ptr<T> const &p) {
|
||||
return p.get() != 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool operator<(intrusive_ptr<T> const &a, intrusive_ptr<T> const &b) {
|
||||
return a.get() < b.get();
|
||||
}
|
||||
|
||||
template <class T> void swap(intrusive_ptr<T> &lhs, intrusive_ptr<T> &rhs) {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
} // namespace boost
|
||||
|
||||
#endif // #ifndef XOD_INTRUSIVE_PTR_H
|
||||
@@ -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 <typename T> 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 <typename T> 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 <typename T> index_t usage() {
|
||||
return (bounds.last - bounds.first + Traits<T>::N) % Traits<T>::N + 1;
|
||||
}
|
||||
|
||||
template <typename T> bool isFull() { return usage<T>() == Traits<T>::N; }
|
||||
|
||||
template <typename T> bool append(T val) {
|
||||
if (isFull<T>())
|
||||
return false;
|
||||
|
||||
appendUnsafe(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> void appendUnsafe(T val) {
|
||||
auto idx = ++bounds.last;
|
||||
*((T *)buffer + idx) = val;
|
||||
}
|
||||
|
||||
template <typename T> bool concat(T *val, index_t len) {
|
||||
if (usage<T>() > Traits<T>::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 <typename T> class ListIterator {
|
||||
typedef List<T> 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 <typename T> void intrusive_ptr_add_ref(List<T> *list) {
|
||||
// TODO: deal with possible overflow
|
||||
++list->_refcount;
|
||||
}
|
||||
|
||||
template <typename T> void intrusive_ptr_release(List<T> *list) {
|
||||
if (--list->_refcount == 0) {
|
||||
delete list;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> class List {
|
||||
typedef boost::intrusive_ptr<detail::Chunk> ChunkPtr;
|
||||
|
||||
public:
|
||||
typedef boost::intrusive_ptr<List> ListPtr;
|
||||
typedef detail::ListIterator<T> 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<T>::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<List *>(this);
|
||||
list->_right = of(val);
|
||||
return list;
|
||||
}
|
||||
|
||||
ListPtr concat(ListPtr other) const {
|
||||
if (isEmpty()) {
|
||||
return other;
|
||||
}
|
||||
|
||||
if (other->isEmpty()) {
|
||||
return ListPtr(const_cast<List *>(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<List *>(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<T>(List *list);
|
||||
friend void intrusive_ptr_release<T>(List *list);
|
||||
detail::refcount_t _refcount;
|
||||
}; // class List<T>
|
||||
|
||||
} // namespace xod
|
||||
|
||||
#endif // #ifndef XOD_LIST_H
|
||||
54
packages/xod-arduino/platform/listFuncs.h
Normal file
54
packages/xod-arduino/platform/listFuncs.h
Normal file
@@ -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<typename T, typename TR>
|
||||
TR foldl(List<T> xs, TR (*func)(TR, T), TR acc) {
|
||||
for (auto it = xs.iterate(); it; ++it)
|
||||
acc = func(acc, *it);
|
||||
return acc;
|
||||
}
|
||||
|
||||
template<typename T> size_t lengthReducer(size_t len, T) {
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes length of a list.
|
||||
*/
|
||||
template<typename T> size_t length(List<T> xs) {
|
||||
return foldl(xs, lengthReducer<T>, (size_t)0);
|
||||
}
|
||||
|
||||
template<typename T> 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<typename T> size_t dump(List<T> xs, T* outBuff) {
|
||||
T* buffEnd = foldl(xs, dumpReducer, outBuff);
|
||||
return buffEnd - outBuff;
|
||||
}
|
||||
|
||||
} // namespace xod
|
||||
|
||||
#endif
|
||||
326
packages/xod-arduino/platform/listViews.h
Normal file
326
packages/xod-arduino/platform/listViews.h
Normal file
@@ -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<typename T> class Cursor {
|
||||
public:
|
||||
virtual ~Cursor() { }
|
||||
virtual bool isValid() const = 0;
|
||||
virtual bool value(T* out) const = 0;
|
||||
virtual void next() = 0;
|
||||
};
|
||||
|
||||
template<typename T> class NilCursor : public Cursor<T> {
|
||||
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<typename T>
|
||||
class Iterator {
|
||||
public:
|
||||
static Iterator<T> nil() {
|
||||
return Iterator<T>(new detail::NilCursor<T>());
|
||||
}
|
||||
|
||||
Iterator(detail::Cursor<T>* 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<T>* _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<typename T> class ListView {
|
||||
public:
|
||||
virtual Iterator<T> 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<typename T> class List {
|
||||
public:
|
||||
constexpr List()
|
||||
: _view(nullptr)
|
||||
{ }
|
||||
|
||||
List(const ListView<T>* view)
|
||||
: _view(view)
|
||||
{ }
|
||||
|
||||
Iterator<T> iterate() const {
|
||||
return _view ? _view->iterate() : Iterator<T>::nil();
|
||||
}
|
||||
|
||||
// pre 0.15.0 backward compatibility
|
||||
List* operator->() __attribute__ ((deprecated)) { return this; }
|
||||
const List* operator->() const __attribute__ ((deprecated)) { return this; }
|
||||
|
||||
private:
|
||||
const ListView<T>* _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<typename T> class PlainListView : public ListView<T> {
|
||||
public:
|
||||
class Cursor : public detail::Cursor<T> {
|
||||
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<T> iterate() const override {
|
||||
return Iterator<T>(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<char> {
|
||||
public:
|
||||
class Cursor : public detail::Cursor<char> {
|
||||
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<char> iterate() const override {
|
||||
return _str ? Iterator<char>(new Cursor(_str)) : Iterator<char>::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<typename T> class ConcatListView : public ListView<T> {
|
||||
public:
|
||||
class Cursor : public detail::Cursor<T> {
|
||||
public:
|
||||
Cursor(Iterator<T>&& left, Iterator<T>&& 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<T> _left;
|
||||
Iterator<T> _right;
|
||||
};
|
||||
|
||||
public:
|
||||
ConcatListView() { }
|
||||
|
||||
ConcatListView(List<T> left, List<T> right)
|
||||
: _left(left)
|
||||
, _right(right)
|
||||
{ }
|
||||
|
||||
ConcatListView& operator=(const ConcatListView& rhs) {
|
||||
_left = rhs._left;
|
||||
_right = rhs._right;
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual Iterator<T> iterate() const override {
|
||||
return Iterator<T>(new Cursor(_left.iterate(), _right.iterate()));
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Cursor;
|
||||
const List<T> _left;
|
||||
const List<T> _right;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Text string helpers
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
using XString = List<char>;
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -141,8 +141,6 @@ typedef uint8_t DirtyFlags;
|
||||
typedef unsigned long TimeMs;
|
||||
typedef void (*EvalFuncPtr)(Context ctx);
|
||||
|
||||
typedef xod::List<char>::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
|
||||
|
||||
20
packages/xod-arduino/platform/stl.h
Normal file
20
packages/xod-arduino/platform/stl.h
Normal file
@@ -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<T&> {typedef T type;};
|
||||
template< class T > struct remove_reference<T&&> {typedef T type;};
|
||||
|
||||
template <class T>
|
||||
typename remove_reference<T>::type&& move(T&& a) {
|
||||
return static_cast<typename remove_reference<T>::type&&>(a);
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
@@ -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<void>', 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<void>'), 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,
|
||||
|
||||
@@ -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<char>::empty()')],
|
||||
[R.is(String), x => `::xod::List<char>::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)),
|
||||
|
||||
@@ -1,268 +1,105 @@
|
||||
|
||||
#include <cstring>
|
||||
#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<char>::empty();
|
||||
char plain[256] = { 0 };
|
||||
TEST_CASE("Plain list", "[list]") {
|
||||
int input[] = { 12, 23, 42 };
|
||||
auto view = PlainListView<int>(input, 3);
|
||||
auto list = List<int>(&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<char>::empty()->concat(List<char>::empty());
|
||||
REQUIRE(nil->length() == 0);
|
||||
REQUIRE(nil->chunkCount() == 0);
|
||||
}
|
||||
|
||||
SECTION("something to empty") {
|
||||
auto rhs = List<char>::of('X');
|
||||
auto list = List<char>::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<char>::of('X');
|
||||
auto list = lhs->concat(List<char>::empty());
|
||||
REQUIRE(list->length() == 1);
|
||||
REQUIRE(list->chunkCount() == 1);
|
||||
|
||||
list->toPlainArrayUnsafe(plain);
|
||||
REQUIRE(strcmp(plain, "X") == 0);
|
||||
}
|
||||
|
||||
SECTION("something to something") {
|
||||
auto lhs = List<char>::of('X');
|
||||
auto rhs = List<char>::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<char>::of('X');
|
||||
auto y = List<char>::of('Y');
|
||||
auto z = List<char>::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<char>::fromPlainArray("ABC", 3);
|
||||
auto rhs = List<char>::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<char>::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<char>::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<char>::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<char>::empty();
|
||||
auto it = list->iterate();
|
||||
REQUIRE((bool)it == false);
|
||||
}
|
||||
|
||||
SECTION("single item list") {
|
||||
auto list = List<char>::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<char>::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<char>::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<char>::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<char>::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<int>(input1, 2);
|
||||
auto view2 = PlainListView<int>(input2, 2);
|
||||
auto view = ConcatListView<int>(
|
||||
List<int>(&view1),
|
||||
List<int>(&view2));
|
||||
auto list = List<int>(&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<char>(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!"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] = {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
struct State {
|
||||
CStringView view;
|
||||
};
|
||||
|
||||
{{ GENERATED_CODE }}
|
||||
|
||||
void evaluate(Context ctx) {
|
||||
auto state = getState(ctx);
|
||||
auto x = getValue<input_IN>(ctx);
|
||||
auto xstr = x
|
||||
? ::xod::List<char>::fromPlainArray("true", 4)
|
||||
: ::xod::List<char>::fromPlainArray("false", 5);
|
||||
emitValue<output_OUT>(ctx, xstr);
|
||||
state->view = CStringView(x ? "true" : "false");
|
||||
emitValue<output_OUT>(ctx, XString(&state->view));
|
||||
}
|
||||
|
||||
@@ -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<input_IN>(ctx);
|
||||
dtostrf(num, 0, 2, str);
|
||||
auto xstr = ::xod::List<char>::fromPlainArray(str, strlen(str));
|
||||
emitValue<output_OUT>(ctx, xstr);
|
||||
dtostrf(num, 0, 2, state->str);
|
||||
emitValue<output_OUT>(ctx, XString(&state->view));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
struct State {
|
||||
ConcatListView<char> view;
|
||||
};
|
||||
|
||||
{{ GENERATED_CODE }}
|
||||
|
||||
void evaluate(Context ctx) {
|
||||
auto state = getState(ctx);
|
||||
auto head = getValue<input_HEAD>(ctx);
|
||||
auto tail = getValue<input_TAIL>(ctx);
|
||||
emitValue<output_STR>(ctx, head->concat(tail));
|
||||
state->view = ConcatListView<char>(head, tail);
|
||||
emitValue<output_STR>(ctx, XString(&state->view));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user