feat(xod-arduino,rt/c++): implement view-based lists and strings

This commit is contained in:
Victor Nakoryakov
2017-10-16 18:59:47 +03:00
parent 246c1c0497
commit 7bd49ec5b8
17 changed files with 594 additions and 907 deletions

View File

@@ -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

View File

@@ -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 Chunks 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 buffers 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) {
// weve 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. Its
// because the chunks are reused across many Lists.
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

View 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

View 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 nodes
* `evaluate` function. Iterators cant 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

View File

@@ -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}}

View File

@@ -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

View File

@@ -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}}

View File

@@ -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

View 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

View File

@@ -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,

View File

@@ -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)),

View File

@@ -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 xs chunk
REQUIRE(xz->chunkCount() == 2); // cant 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!"));
}
}

View File

@@ -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] = {

View File

@@ -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',
},
],

View File

@@ -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));
}

View File

@@ -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));
}

View File

@@ -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));
}