xod-arduino
Advanced tools
Comparing version 0.15.2 to 0.19.0
@@ -12,3 +12,3 @@ 'use strict'; | ||
var _ramda2 = _interopRequireDefault(_ramda); | ||
var R = _interopRequireWildcard(_ramda); | ||
@@ -25,13 +25,15 @@ var _handlebars = require('handlebars'); | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
/* babel-plugin-inline-import '../platform/configuration.tpl.cpp' */var configTpl = '{{!-- Template for program configuration block --}}\n{{!-- Accepts the Config context --}}\n\n/*=============================================================================\n *\n *\n * Configuration\n *\n *\n =============================================================================*/\n\n#define NODE_COUNT {{ NODE_COUNT }}\n#define DEFER_NODE_COUNT {{ DEFER_NODE_COUNT }}\n#define MAX_OUTPUT_COUNT {{ MAX_OUTPUT_COUNT }}\n\n// Uncomment to turn on debug of the program\n{{#unless XOD_DEBUG}}//{{/unless}}#define XOD_DEBUG\n\n// Uncomment to trace the program runtime in the Serial Monitor\n//#define XOD_DEBUG_ENABLE_TRACE\n'; | ||
/* babel-plugin-inline-import '../platform/patchContext.tpl.cpp' */var patchContextTpl = '{{!-- Template for GENERATED_CODE token inside each patch implementation --}}\n{{!-- Accepts the Node context --}}\n\nstruct Storage {\n State state;\n {{#each outputs}}\n {{ cppType type }} output_{{ pinKey }};\n {{/each}}\n};\n\nstruct Wiring {\n EvalFuncPtr eval;\n {{#each inputs}}\n UpstreamPinRef input_{{ pinKey }};\n {{/each}}\n {{#each outputs}}\n const NodeId* output_{{ pinKey }};\n {{/each}}\n};\n\nState* getState(NodeId nid) {\n return reinterpret_cast<State*>(getStoragePtr(nid, 0));\n}\n\n{{#each inputs}}\nusing input_{{ pinKey }} = InputDescriptor<{{ cppType type }}, offsetof(Wiring, input_{{ pinKey }})>;\n{{/each}}\n\n{{#each outputs}}\nusing output_{{ pinKey }} = OutputDescriptor<{{ cppType type }}, offsetof(Wiring, output_{{ pinKey }}), offsetof(Storage, output_{{ pinKey }}), {{@index}}>;\n{{/each}}\n'; | ||
/* babel-plugin-inline-import '../platform/implList.tpl.cpp' */var implListTpl = '{{!-- Template for program graph --}}\n{{!-- Accepts the context with list of Nodes --}}\n/*=============================================================================\n *\n *\n * Native node implementations\n *\n *\n =============================================================================*/\n\nnamespace xod {\n\n{{#each this}}\n//-----------------------------------------------------------------------------\n// {{ owner }}/{{ libName }}/{{ patchName }} implementation\n//-----------------------------------------------------------------------------\nnamespace {{ owner }}__{{ libName }}__{{ patchName }} {\n\n{{ implementation }}\n\n} // namespace {{ owner }}__{{ libName }}__{{ patchName }}\n\n{{/each}}\n} // namespace xod\n'; | ||
/* babel-plugin-inline-import '../platform/program.tpl.cpp' */var programTpl = '{{!-- Template for program graph --}}\n{{!-- Accepts the context with list of Nodes --}}\n/*=============================================================================\n *\n *\n * Program graph\n *\n *\n =============================================================================*/\n\nnamespace xod {\n\n //-------------------------------------------------------------------------\n // Dynamic data\n //-------------------------------------------------------------------------\n {{#each nodes}}\n {{mergePins }}\n // Storage of #{{ id }} {{ patch.owner }}/{{ patch.libName }}/{{ patch.patchName }}\n {{#each outputs }}\n {{ decltype type value }} node_{{ ../id }}_output_{{ pinKey }} = {{ cppValue type value }};\n {{~/each}}\n\n {{ns patch }}::Storage storage_{{ id }} = {\n { }, // state\n {{#each outputs }}\n node_{{ ../id }}_output_{{ pinKey }} {{#unless @last }},{{/unless }}\n {{/each}}\n };\n {{/each}}\n\n DirtyFlags g_dirtyFlags[NODE_COUNT] = {\n {{#each nodes}}DirtyFlags({{ dirtyFlags }}){{#unless @last}},\n {{/unless}}{{/each}}\n };\n\n TimeMs g_schedule[NODE_COUNT] = { 0 };\n\n //-------------------------------------------------------------------------\n // Static (immutable) data\n //-------------------------------------------------------------------------\n {{#each nodes}}\n {{mergePins }}\n // Wiring of #{{ id }} {{ patch.owner }}/{{ patch.libName }}/{{ patch.patchName }}\n {{#each outputs }}\n const NodeId outLinks_{{ ../id }}_{{ pinKey }}[] PROGMEM = { {{#each to }}{{ this }}, {{/each}}NO_NODE };\n {{/each}}\n const {{ns patch }}::Wiring wiring_{{ id }} PROGMEM = {\n &{{ns patch }}::evaluate,\n // inputs (UpstreamPinRef\u2019s initializers)\n {{#each inputs }}\n {{#exists nodeId }}\n { NodeId({{ nodeId }}),\n {{ns patch }}::output_{{ fromPinKey }}::INDEX,\n {{ns patch }}::output_{{ fromPinKey }}::STORAGE_OFFSET }, // input_{{ pinKey }}\n {{else }}\n { NO_NODE, 0, 0 }, // input_{{ pinKey }}\n {{/exists }}\n {{/each}}\n // outputs (NodeId list binding)\n {{#each outputs }}\n outLinks_{{ ../id }}_{{ pinKey }}{{#unless @last }},{{/unless }} // output_{{ pinKey }}\n {{/each}}\n };\n {{/each}}\n\n // PGM array with pointers to PGM wiring information structs\n const void* const g_wiring[NODE_COUNT] PROGMEM = {\n {{#each nodes}}\n &wiring_{{ id }}{{#unless @last }},{{/unless }}\n {{/each}}\n };\n\n // PGM array with pointers to RAM-located storages\n void* const g_storages[NODE_COUNT] PROGMEM = {\n {{#each nodes}}\n &storage_{{ id }}{{#unless @last }},{{/unless }}\n {{/each}}\n };\n}\n'; | ||
/* babel-plugin-inline-import '../platform/configuration.tpl.cpp' */var configTpl = '{{!-- Template for program configuration block --}}\n{{!-- Accepts the Config context --}}\n\n/*=============================================================================\n *\n *\n * Configuration\n *\n *\n =============================================================================*/\n\n// Uncomment to turn on debug of the program\n{{#unless XOD_DEBUG}}//{{/unless}}#define XOD_DEBUG\n\n// Uncomment to trace the program runtime in the Serial Monitor\n//#define XOD_DEBUG_ENABLE_TRACE\n'; | ||
/* babel-plugin-inline-import '../platform/patchContext.tpl.cpp' */var patchContextTpl = '{{!-- Template for GENERATED_CODE token inside each patch implementation --}}\n{{!-- Accepts TPatch context --}}\n\nstruct Node {\n State state;\n {{#if usesTimeouts}}\n TimeMs timeoutAt;\n {{/if}}\n {{#each outputs}}\n {{ cppType type }} output_{{ pinKey }};\n {{/each}}\n\n union {\n struct {\n {{#eachDirtyablePin outputs}}\n bool isOutputDirty_{{ pinKey }} : 1;\n {{/eachDirtyablePin}}\n bool isNodeDirty : 1;\n };\n\n DirtyFlags dirtyFlags;\n };\n};\n\n{{#each inputs}}\nstruct input_{{ pinKey }} { };\n{{/each}}\n{{#each outputs}}\nstruct output_{{ pinKey }} { };\n{{/each}}\n\ntemplate<typename PinT> struct ValueType { using T = void; };\n{{#each inputs}}\ntemplate<> struct ValueType<input_{{ pinKey }}> { using T = {{ cppType type }}; };\n{{/each}}\n{{#each outputs}}\ntemplate<> struct ValueType<output_{{ pinKey }}> { using T = {{ cppType type }}; };\n{{/each}}\n\n\nstruct ContextObject {\n Node* _node;\n {{#if usesNodeId}}\n uint16_t _nodeId;\n {{/if}}\n\n {{#each inputs}}\n {{ cppType type }} _input_{{ pinKey }};\n {{/each}}\n\n {{#eachDirtyablePin inputs}}\n bool _isInputDirty_{{ pinKey }};\n {{/eachDirtyablePin}}\n};\n\nusing Context = ContextObject*;\n\ntemplate<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {\n static_assert(always_false<PinT>::value,\n "Invalid pin descriptor. Expected one of:" \\\n "{{#each inputs}} input_{{pinKey}}{{/each}}" \\\n "{{#each outputs}} output_{{pinKey}}{{/each}}");\n}\n\n{{#each inputs}}\ntemplate<> {{ cppType type }} getValue<input_{{ pinKey }}>(Context ctx) {\n return ctx->_input_{{ pinKey }};\n}\n{{/each}}\n{{#each outputs}}\ntemplate<> {{ cppType type }} getValue<output_{{ pinKey }}>(Context ctx) {\n return ctx->_node->output_{{ pinKey }};\n}\n{{/each}}\n\ntemplate<typename InputT> bool isInputDirty(Context ctx) {\n static_assert(always_false<InputT>::value,\n "Invalid input descriptor. Expected one of:" \\\n "{{#eachDirtyablePin inputs}} input_{{pinKey}}{{/eachDirtyablePin}}");\n return false;\n}\n\n{{#eachDirtyablePin inputs}}\ntemplate<> bool isInputDirty<input_{{ pinKey }}>(Context ctx) {\n return ctx->_isInputDirty_{{ pinKey }};\n}\n{{/eachDirtyablePin}}\n\ntemplate<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {\n static_assert(always_false<OutputT>::value,\n "Invalid output descriptor. Expected one of:" \\\n "{{#each outputs}} output_{{pinKey}}{{/each}}");\n}\n\n{{#each outputs}}\ntemplate<> void emitValue<output_{{ pinKey }}>(Context ctx, {{ cppType type }} val) {\n ctx->_node->output_{{ pinKey }} = val;\n {{#if isDirtyable}}\n ctx->_node->isOutputDirty_{{ pinKey }} = true;\n {{/if}}\n}\n{{/each}}\n\nState* getState(Context ctx) {\n return &ctx->_node->state;\n}\n\n{{#if usesNodeId}}\nuint16_t getNodeId(Context ctx) {\n return ctx->_nodeId;\n}\n{{/if}}\n'; | ||
/* babel-plugin-inline-import '../platform/implList.tpl.cpp' */var implListTpl = '{{!-- Template for program graph --}}\n{{!-- Accepts the context with list of TPatch --}}\n/*=============================================================================\n *\n *\n * Native node implementations\n *\n *\n =============================================================================*/\n\nnamespace xod {\n\n{{#each this}}\n{{#unless isConstant}}\n//-----------------------------------------------------------------------------\n// {{ owner }}/{{ libName }}/{{ patchName }} implementation\n//-----------------------------------------------------------------------------\nnamespace {{ owner }}__{{ libName }}__{{ patchName }} {\n\n{{ implementation }}\n\n} // namespace {{ owner }}__{{ libName }}__{{ patchName }}\n\n{{/unless}}\n{{/each}}\n} // namespace xod\n'; | ||
/* babel-plugin-inline-import '../platform/program.tpl.cpp' */var programTpl = '\n/*=============================================================================\n *\n *\n * Main loop components\n *\n *\n =============================================================================*/\n\nnamespace xod {\n\n// Define/allocate persistent storages (state, timeout, output data) for all nodes\n{{#each nodes}}\n{{~ mergePins }}\n{{#each outputs }}\n{{ decltype type value }} node_{{ ../id }}_output_{{ pinKey }} = {{ cppValue type value }};\n{{/each}}\n{{#unless patch.isConstant}}\n{{ ns patch }}::Node node_{{ id }} = {\n {{ ns patch }}::State(), // state default\n {{#if patch.usesTimeouts}}\n 0, // timeoutAt\n {{/if}}\n {{#each outputs}}\n node_{{ ../id }}_output_{{ pinKey }}, // output {{ pinKey }} default\n {{/each}}\n {{#eachDirtyablePin outputs}}\n {{ isDirtyOnBoot }}, // {{ pinKey }} dirty\n {{/eachDirtyablePin}}\n true // node itself dirty\n};\n{{/unless}}\n{{/each}}\n\nvoid runTransaction(bool firstRun) {\n g_transactionTime = millis();\n\n XOD_TRACE_F("Transaction started, t=");\n XOD_TRACE_LN(g_transactionTime);\n\n // Check for timeouts\n {{#eachNodeUsingTimeouts nodes}}\n detail::checkTriggerTimeout(&node_{{ id }});\n {{/eachNodeUsingTimeouts}}\n\n // defer-* nodes are always at the very bottom of the graph, so no one will\n // recieve values emitted by them. We must evaluate them before everybody\n // else to give them a chance to emit values.\n //\n // If trigerred, keep only output dirty, not the node itself, so it will\n // evaluate on the regular pass only if it pushed a new value again.\n {{#eachDeferNode nodes}}\n {\n if (node_{{ id }}.isNodeDirty) {\n XOD_TRACE_F("Trigger defer node #");\n XOD_TRACE_LN({{ id }});\n\n {{ns patch }}::ContextObject ctxObj;\n ctxObj._node = &node_{{ id }};\n ctxObj._isInputDirty_IN = false;\n\n {{ ns patch }}::evaluate(&ctxObj);\n\n // mark downstream nodes dirty\n {{#each outputs }}\n {{#if isDirtyable ~}}\n {{#each to}}\n node_{{ this }}.isNodeDirty |= node_{{ ../../id }}.isOutputDirty_{{ ../pinKey }};\n {{/each}}\n {{else}}\n {{#each to}}\n node_{{ this }}.isNodeDirty = true;\n {{/each}}\n {{/if}}\n {{/each}}\n\n node_{{ id }}.isNodeDirty = false;\n detail::clearTimeout(&node_{{ id }});\n }\n }\n {{/eachDeferNode}}\n\n // Evaluate all dirty nodes\n {{#eachNonConstantNode nodes}}\n { // {{ ns patch }} #{{ id }}\n if (node_{{ id }}.isNodeDirty) {\n XOD_TRACE_F("Eval node #");\n XOD_TRACE_LN({{ id }});\n\n {{ns patch }}::ContextObject ctxObj;\n ctxObj._node = &node_{{ id }};\n {{#if patch.usesNodeId}}\n ctxObj._nodeId = {{ id }};\n {{/if}}\n\n // copy data from upstream nodes into context\n {{#eachLinkedInput inputs}}\n {{!--\n // We refer to node_42.output_FOO as data source in case\n // of a regular node and directly use node_42_output_VAL\n // initial value constexpr in case of a constant. It\u2019s\n // because store no Node structures at the global level\n --}}\n ctxObj._input_{{ pinKey }} = node_{{ fromNodeId }}\n {{~#if fromPatch.isConstant }}_{{else}}.{{/if~}}\n output_{{ fromPinKey }};\n {{/eachLinkedInput}}\n\n {{#eachNonlinkedInput inputs}}\n {{!--\n // Nonlinked pulse inputs are never dirty, all value types\n // are linked (to extracted constant nodes) and so will be\n // processed in another loop.\n --}}\n {{#if isDirtyable}}\n ctxObj._isInputDirty_{{ pinKey }} = false;\n {{/if}}\n {{/eachNonlinkedInput}}\n {{#eachLinkedInput inputs}}\n {{!--\n // Constants do not store dirtieness. They are never dirty\n // except the very first run\n --}}\n {{#if isDirtyable}}\n {{#if fromPatch.isConstant}}\n ctxObj._isInputDirty_{{ pinKey }} = firstRun;\n {{else if fromOutput.isDirtyable}}\n ctxObj._isInputDirty_{{ pinKey }} = node_{{ fromNodeId }}.isOutputDirty_{{ fromPinKey }};\n {{else}}\n ctxObj._isInputDirty_{{ pinKey }} = true;\n {{/if}}\n {{/if}}\n {{/eachLinkedInput}}\n\n {{ ns patch }}::evaluate(&ctxObj);\n\n // mark downstream nodes dirty\n {{#each outputs }}\n {{#if isDirtyable ~}}\n {{#each to}}\n node_{{ this }}.isNodeDirty |= node_{{ ../../id }}.isOutputDirty_{{ ../pinKey }};\n {{/each}}\n {{else}}\n {{#each to}}\n node_{{ this }}.isNodeDirty = true;\n {{/each}}\n {{/if}}\n {{/each}}\n }\n }\n {{/eachNonConstantNode}}\n\n // Clear dirtieness and timeouts for all nodes and pins\n {{#eachNonConstantNode nodes}}\n node_{{ id }}.dirtyFlags = 0;\n {{/eachNonConstantNode}}\n {{#eachNodeUsingTimeouts nodes}}\n detail::clearStaleTimeout(&node_{{ id }});\n {{/eachNodeUsingTimeouts}}\n\n XOD_TRACE_F("Transaction completed, t=");\n XOD_TRACE_LN(millis());\n}\n\n} // namespace xod\n'; | ||
/* babel-plugin-inline-import '../platform/preamble.h' */var preambleH = '// The sketch is auto-generated with XOD (https://xod.io).\n//\n// You can compile and upload it to an Arduino-compatible board with\n// Arduino IDE.\n//\n// Rough code overview:\n//\n// - Configuration section\n// - STL shim\n// - Immutable list classes and functions\n// - XOD runtime environment\n// - Native node implementation\n// - Program graph definition\n//\n// Search for comments fenced with \'====\' and \'----\' to navigate through\n// the major code blocks.\n\n#include <Arduino.h>\n#include <inttypes.h>\n#include <avr/pgmspace.h>\n'; | ||
/* babel-plugin-inline-import '../platform/listViews.h' */var listViewsH = '/*=============================================================================\n *\n *\n * XOD-specific list/array implementations\n *\n *\n =============================================================================*/\n\n#ifndef XOD_LIST_H\n#define XOD_LIST_H\n\nnamespace xod {\nnamespace detail {\n\n/*\n * Cursors are used internaly by iterators and list views. They are not exposed\n * directly to a list consumer.\n *\n * The base `Cursor` is an interface which provides the bare minimum of methods\n * to facilitate a single iteration pass.\n */\ntemplate<typename T> class Cursor {\n public:\n virtual ~Cursor() { }\n virtual bool isValid() const = 0;\n virtual bool value(T* out) const = 0;\n virtual void next() = 0;\n};\n\ntemplate<typename T> class NilCursor : public Cursor<T> {\n public:\n virtual bool isValid() const { return false; }\n virtual bool value(T* out) const { return false; }\n virtual void next() { }\n};\n\n} // namespace detail\n\n/*\n * Iterator is an object used to iterate a list once.\n *\n * Users create new iterators by calling `someList.iterate()`.\n * Iterators are created on stack and are supposed to have a\n * short live, e.g. for a duration of `for` loop or node\u2019s\n * `evaluate` function. Iterators can\u2019t be copied.\n *\n * Implemented as a pimpl pattern wrapper over the cursor.\n * Once created for a cursor, an iterator owns that cursor\n * and will delete the cursor object once destroyed itself.\n */\ntemplate<typename T>\nclass Iterator {\n public:\n static Iterator<T> nil() {\n return Iterator<T>(new detail::NilCursor<T>());\n }\n\n Iterator(detail::Cursor<T>* cursor)\n : _cursor(cursor)\n { }\n\n ~Iterator() {\n if (_cursor)\n delete _cursor;\n }\n\n Iterator(const Iterator& that) = delete;\n Iterator& operator=(const Iterator& that) = delete;\n\n Iterator(Iterator&& it)\n : _cursor(it._cursor)\n {\n it._cursor = nullptr;\n }\n\n Iterator& operator=(Iterator&& it) {\n auto tmp = it._cursor;\n it._cursor = _cursor;\n _cursor = tmp;\n return *this;\n }\n\n operator bool() const { return _cursor->isValid(); }\n\n bool value(T* out) const {\n return _cursor->value(out);\n }\n\n T operator*() const {\n T out;\n _cursor->value(&out);\n return out;\n }\n\n Iterator& operator++() {\n _cursor->next();\n return *this;\n }\n\n private:\n detail::Cursor<T>* _cursor;\n};\n\n/*\n * An interface for a list view. A particular list view provides a new\n * kind of iteration over existing data. This way we can use list slices,\n * list concatenations, list rotations, etc without introducing new data\n * buffers. We just change the way already existing data is iterated.\n *\n * ListView is not exposed to a list user directly, it is used internally\n * by the List class. However, deriving a new ListView is necessary if you\n * make a new list/string processing node.\n */\ntemplate<typename T> class ListView {\n public:\n virtual Iterator<T> iterate() const = 0;\n};\n\n/*\n * The list as it seen by data consumers. Have a single method `iterate`\n * to create a new iterator.\n *\n * Implemented as pimpl pattern wrapper over a list view. Takes pointer\n * to a list view in constructor and expects the view will be alive for\n * the whole life time of the list.\n */\ntemplate<typename T> class List {\n public:\n constexpr List()\n : _view(nullptr)\n { }\n\n List(const ListView<T>* view)\n : _view(view)\n { }\n\n Iterator<T> iterate() const {\n return _view ? _view->iterate() : Iterator<T>::nil();\n }\n\n // pre 0.15.0 backward compatibility\n List* operator->() __attribute__ ((deprecated)) { return this; }\n const List* operator->() const __attribute__ ((deprecated)) { return this; }\n\n private:\n const ListView<T>* _view;\n};\n\n/*\n * A list view over an old good plain C array.\n *\n * Expects the array will be alive for the whole life time of the\n * view.\n */\ntemplate<typename T> class PlainListView : public ListView<T> {\n public:\n class Cursor : public detail::Cursor<T> {\n public:\n Cursor(const PlainListView* owner)\n : _owner(owner)\n , _idx(0)\n { }\n\n bool isValid() const override {\n return _idx < _owner->_len;\n }\n\n bool value(T* out) const override {\n if (!isValid())\n return false;\n *out = _owner->_data[_idx];\n return true;\n }\n\n void next() override { ++_idx; }\n\n private:\n const PlainListView* _owner;\n size_t _idx;\n };\n\n public:\n PlainListView(const T* data, size_t len)\n : _data(data)\n , _len(len)\n { }\n\n virtual Iterator<T> iterate() const override {\n return Iterator<T>(new Cursor(this));\n }\n\n private:\n friend class Cursor;\n const T* _data;\n size_t _len;\n};\n\n/*\n * A list view over a null-terminated C-String.\n *\n * Expects the char buffer will be alive for the whole life time of the view.\n * You can use string literals as a buffer, since they are persistent for\n * the program execution time.\n */\nclass CStringView : public ListView<char> {\n public:\n class Cursor : public detail::Cursor<char> {\n public:\n Cursor(const char* str)\n : _ptr(str)\n { }\n\n bool isValid() const override {\n return (bool)*_ptr;\n }\n\n bool value(char* out) const override {\n *out = *_ptr;\n return (bool)*_ptr;\n }\n\n void next() override { ++_ptr; }\n\n private:\n const char* _ptr;\n };\n\n public:\n CStringView(const char* str = nullptr)\n : _str(str)\n { }\n\n CStringView& operator=(const CStringView& rhs) {\n _str = rhs._str;\n return *this;\n }\n\n virtual Iterator<char> iterate() const override {\n return _str ? Iterator<char>(new Cursor(_str)) : Iterator<char>::nil();\n }\n\n private:\n friend class Cursor;\n const char* _str;\n};\n\n/*\n * A list view over two other lists (Left and Right) which first iterates the\n * left one, and when exhausted, iterates the right one.\n *\n * Expects both Left and Right to be alive for the whole view life time.\n */\ntemplate<typename T> class ConcatListView : public ListView<T> {\n public:\n class Cursor : public detail::Cursor<T> {\n public:\n Cursor(Iterator<T>&& left, Iterator<T>&& right)\n : _left(std::move(left))\n , _right(std::move(right))\n { }\n\n bool isValid() const override {\n return _left || _right;\n }\n\n bool value(T* out) const override {\n return _left.value(out) || _right.value(out);\n }\n\n void next() override {\n _left ? ++_left : ++_right;\n }\n\n private:\n Iterator<T> _left;\n Iterator<T> _right;\n };\n\n public:\n ConcatListView() { }\n\n ConcatListView(List<T> left, List<T> right)\n : _left(left)\n , _right(right)\n { }\n\n ConcatListView& operator=(const ConcatListView& rhs) {\n _left = rhs._left;\n _right = rhs._right;\n return *this;\n }\n\n virtual Iterator<T> iterate() const override {\n return Iterator<T>(new Cursor(_left.iterate(), _right.iterate()));\n }\n\n private:\n friend class Cursor;\n const List<T> _left;\n const List<T> _right;\n};\n\n//----------------------------------------------------------------------------\n// Text string helpers\n//----------------------------------------------------------------------------\n\nusing XString = List<char>;\n\n/*\n * List and list view in a single pack. An utility used to define constant\n * string literals in XOD.\n */\nclass XStringCString : public XString {\n public:\n XStringCString(const char* str)\n : XString(&_view)\n , _view(str)\n { }\n\n private:\n CStringView _view;\n};\n\n} // namespace xod\n\n#endif\n'; | ||
/* babel-plugin-inline-import '../platform/listViews.h' */var listViewsH = '/*=============================================================================\n *\n *\n * XOD-specific list/array implementations\n *\n *\n =============================================================================*/\n\n#ifndef XOD_LIST_H\n#define XOD_LIST_H\n\nnamespace xod {\nnamespace detail {\n\n/*\n * Cursors are used internaly by iterators and list views. They are not exposed\n * directly to a list consumer.\n *\n * The base `Cursor` is an interface which provides the bare minimum of methods\n * to facilitate a single iteration pass.\n */\ntemplate<typename T> class Cursor {\n public:\n virtual ~Cursor() { }\n virtual bool isValid() const = 0;\n virtual bool value(T* out) const = 0;\n virtual void next() = 0;\n};\n\ntemplate<typename T> class NilCursor : public Cursor<T> {\n public:\n virtual bool isValid() const { return false; }\n virtual bool value(T*) const { return false; }\n virtual void next() { }\n};\n\n} // namespace detail\n\n/*\n * Iterator is an object used to iterate a list once.\n *\n * Users create new iterators by calling `someList.iterate()`.\n * Iterators are created on stack and are supposed to have a\n * short live, e.g. for a duration of `for` loop or node\u2019s\n * `evaluate` function. Iterators can\u2019t be copied.\n *\n * Implemented as a pimpl pattern wrapper over the cursor.\n * Once created for a cursor, an iterator owns that cursor\n * and will delete the cursor object once destroyed itself.\n */\ntemplate<typename T>\nclass Iterator {\n public:\n static Iterator<T> nil() {\n return Iterator<T>(new detail::NilCursor<T>());\n }\n\n Iterator(detail::Cursor<T>* cursor)\n : _cursor(cursor)\n { }\n\n ~Iterator() {\n if (_cursor)\n delete _cursor;\n }\n\n Iterator(const Iterator& that) = delete;\n Iterator& operator=(const Iterator& that) = delete;\n\n Iterator(Iterator&& it)\n : _cursor(it._cursor)\n {\n it._cursor = nullptr;\n }\n\n Iterator& operator=(Iterator&& it) {\n auto tmp = it._cursor;\n it._cursor = _cursor;\n _cursor = tmp;\n return *this;\n }\n\n operator bool() const { return _cursor->isValid(); }\n\n bool value(T* out) const {\n return _cursor->value(out);\n }\n\n T operator*() const {\n T out;\n _cursor->value(&out);\n return out;\n }\n\n Iterator& operator++() {\n _cursor->next();\n return *this;\n }\n\n private:\n detail::Cursor<T>* _cursor;\n};\n\n/*\n * An interface for a list view. A particular list view provides a new\n * kind of iteration over existing data. This way we can use list slices,\n * list concatenations, list rotations, etc without introducing new data\n * buffers. We just change the way already existing data is iterated.\n *\n * ListView is not exposed to a list user directly, it is used internally\n * by the List class. However, deriving a new ListView is necessary if you\n * make a new list/string processing node.\n */\ntemplate<typename T> class ListView {\n public:\n virtual Iterator<T> iterate() const = 0;\n};\n\n/*\n * The list as it seen by data consumers. Have a single method `iterate`\n * to create a new iterator.\n *\n * Implemented as pimpl pattern wrapper over a list view. Takes pointer\n * to a list view in constructor and expects the view will be alive for\n * the whole life time of the list.\n */\ntemplate<typename T> class List {\n public:\n constexpr List()\n : _view(nullptr)\n { }\n\n List(const ListView<T>* view)\n : _view(view)\n { }\n\n Iterator<T> iterate() const {\n return _view ? _view->iterate() : Iterator<T>::nil();\n }\n\n // pre 0.15.0 backward compatibility\n List* operator->() __attribute__ ((deprecated)) { return this; }\n const List* operator->() const __attribute__ ((deprecated)) { return this; }\n\n private:\n const ListView<T>* _view;\n};\n\n/*\n * A list view over an old good plain C array.\n *\n * Expects the array will be alive for the whole life time of the\n * view.\n */\ntemplate<typename T> class PlainListView : public ListView<T> {\n public:\n class Cursor : public detail::Cursor<T> {\n public:\n Cursor(const PlainListView* owner)\n : _owner(owner)\n , _idx(0)\n { }\n\n bool isValid() const override {\n return _idx < _owner->_len;\n }\n\n bool value(T* out) const override {\n if (!isValid())\n return false;\n *out = _owner->_data[_idx];\n return true;\n }\n\n void next() override { ++_idx; }\n\n private:\n const PlainListView* _owner;\n size_t _idx;\n };\n\n public:\n PlainListView(const T* data, size_t len)\n : _data(data)\n , _len(len)\n { }\n\n virtual Iterator<T> iterate() const override {\n return Iterator<T>(new Cursor(this));\n }\n\n private:\n friend class Cursor;\n const T* _data;\n size_t _len;\n};\n\n/*\n * A list view over a null-terminated C-String.\n *\n * Expects the char buffer will be alive for the whole life time of the view.\n * You can use string literals as a buffer, since they are persistent for\n * the program execution time.\n */\nclass CStringView : public ListView<char> {\n public:\n class Cursor : public detail::Cursor<char> {\n public:\n Cursor(const char* str)\n : _ptr(str)\n { }\n\n bool isValid() const override {\n return (bool)*_ptr;\n }\n\n bool value(char* out) const override {\n *out = *_ptr;\n return (bool)*_ptr;\n }\n\n void next() override { ++_ptr; }\n\n private:\n const char* _ptr;\n };\n\n public:\n CStringView(const char* str = nullptr)\n : _str(str)\n { }\n\n CStringView& operator=(const CStringView& rhs) {\n _str = rhs._str;\n return *this;\n }\n\n virtual Iterator<char> iterate() const override {\n return _str ? Iterator<char>(new Cursor(_str)) : Iterator<char>::nil();\n }\n\n private:\n friend class Cursor;\n const char* _str;\n};\n\n/*\n * A list view over two other lists (Left and Right) which first iterates the\n * left one, and when exhausted, iterates the right one.\n *\n * Expects both Left and Right to be alive for the whole view life time.\n */\ntemplate<typename T> class ConcatListView : public ListView<T> {\n public:\n class Cursor : public detail::Cursor<T> {\n public:\n Cursor(Iterator<T>&& left, Iterator<T>&& right)\n : _left(std::move(left))\n , _right(std::move(right))\n { }\n\n bool isValid() const override {\n return _left || _right;\n }\n\n bool value(T* out) const override {\n return _left.value(out) || _right.value(out);\n }\n\n void next() override {\n _left ? ++_left : ++_right;\n }\n\n private:\n Iterator<T> _left;\n Iterator<T> _right;\n };\n\n public:\n ConcatListView() { }\n\n ConcatListView(List<T> left, List<T> right)\n : _left(left)\n , _right(right)\n { }\n\n ConcatListView& operator=(const ConcatListView& rhs) {\n _left = rhs._left;\n _right = rhs._right;\n return *this;\n }\n\n virtual Iterator<T> iterate() const override {\n return Iterator<T>(new Cursor(_left.iterate(), _right.iterate()));\n }\n\n private:\n friend class Cursor;\n const List<T> _left;\n const List<T> _right;\n};\n\n//----------------------------------------------------------------------------\n// Text string helpers\n//----------------------------------------------------------------------------\n\nusing XString = List<char>;\n\n/*\n * List and list view in a single pack. An utility used to define constant\n * string literals in XOD.\n */\nclass XStringCString : public XString {\n public:\n XStringCString(const char* str)\n : XString(&_view)\n , _view(str)\n { }\n\n private:\n CStringView _view;\n};\n\n} // namespace xod\n\n#endif\n'; | ||
/* babel-plugin-inline-import '../platform/listFuncs.h' */var listFuncsH = '/*=============================================================================\n *\n *\n * Basic algorithms for XOD lists\n *\n *\n =============================================================================*/\n\n#ifndef XOD_LIST_FUNCS_H\n#define XOD_LIST_FUNCS_H\n\n#include "listViews.h"\n\nnamespace xod {\n\n/*\n * Folds a list from left. Also known as "reduce".\n */\ntemplate<typename T, typename TR>\nTR foldl(List<T> xs, TR (*func)(TR, T), TR acc) {\n for (auto it = xs.iterate(); it; ++it)\n acc = func(acc, *it);\n return acc;\n}\n\ntemplate<typename T> size_t lengthReducer(size_t len, T) {\n return len + 1;\n}\n\n/*\n * Computes length of a list.\n */\ntemplate<typename T> size_t length(List<T> xs) {\n return foldl(xs, lengthReducer<T>, (size_t)0);\n}\n\ntemplate<typename T> T* dumpReducer(T* buff, T x) {\n *buff = x;\n return buff + 1;\n}\n\n/*\n * Copies a list content into a memory buffer.\n *\n * It is expected that `outBuff` has enough size to fit all the data.\n */\ntemplate<typename T> size_t dump(List<T> xs, T* outBuff) {\n T* buffEnd = foldl(xs, dumpReducer, outBuff);\n return buffEnd - outBuff;\n}\n\n} // namespace xod\n\n#endif\n'; | ||
/* babel-plugin-inline-import '../platform/stl.h' */var stlH = '/*=============================================================================\n *\n *\n * STL shim. Provides implementation for vital std::* constructs\n *\n *\n =============================================================================*/\n\nnamespace std {\n\ntemplate< class T > struct remove_reference {typedef T type;};\ntemplate< class T > struct remove_reference<T&> {typedef T type;};\ntemplate< class T > struct remove_reference<T&&> {typedef T type;};\n\ntemplate <class T>\ntypename remove_reference<T>::type&& move(T&& a) {\n return static_cast<typename remove_reference<T>::type&&>(a);\n}\n\n} // namespace std\n'; | ||
/* babel-plugin-inline-import '../platform/runtime.cpp' */var runtimeCpp = '\n/*=============================================================================\n *\n *\n * Runtime\n *\n *\n =============================================================================*/\n\n//----------------------------------------------------------------------------\n// Debug routines\n//----------------------------------------------------------------------------\n#ifndef DEBUG_SERIAL\n# define DEBUG_SERIAL Serial\n#endif\n\n#if defined(XOD_DEBUG) && defined(XOD_DEBUG_ENABLE_TRACE)\n# define XOD_TRACE(x) { DEBUG_SERIAL.print(x); DEBUG_SERIAL.flush(); }\n# define XOD_TRACE_LN(x) { DEBUG_SERIAL.println(x); DEBUG_SERIAL.flush(); }\n# define XOD_TRACE_F(x) XOD_TRACE(F(x))\n# define XOD_TRACE_FLN(x) XOD_TRACE_LN(F(x))\n#else\n# define XOD_TRACE(x)\n# define XOD_TRACE_LN(x)\n# define XOD_TRACE_F(x)\n# define XOD_TRACE_FLN(x)\n#endif\n\n//----------------------------------------------------------------------------\n// PGM space utilities\n//----------------------------------------------------------------------------\n#define pgm_read_nodeid(address) (pgm_read_word(address))\n\n/*\n * Workaround for bugs:\n * https://github.com/arduino/ArduinoCore-sam/pull/43\n * https://github.com/arduino/ArduinoCore-samd/pull/253\n * Remove after the PRs merge\n */\n#if !defined(ARDUINO_ARCH_AVR) && defined(pgm_read_ptr)\n# undef pgm_read_ptr\n# define pgm_read_ptr(addr) (*(const void **)(addr))\n#endif\n\n//----------------------------------------------------------------------------\n// Compatibilities\n//----------------------------------------------------------------------------\n\n#if !defined(ARDUINO_ARCH_AVR)\n/*\n * Provide dtostrf function for non-AVR platforms. Although many platforms\n * provide a stub many others do not. And the stub is based on `sprintf`\n * which doesn\u2019t work with floating point formatters on some platforms\n * (e.g. Arduino M0).\n *\n * This is an implementation based on `fcvt` standard function. Taken here:\n * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614\n */\nchar *dtostrf(double val, int width, unsigned int prec, char *sout) {\n int decpt, sign, reqd, pad;\n const char *s, *e;\n char *p;\n s = fcvt(val, prec, &decpt, &sign);\n if (prec == 0 && decpt == 0) {\n s = (*s < \'5\') ? "0" : "1";\n reqd = 1;\n } else {\n reqd = strlen(s);\n if (reqd > decpt) reqd++;\n if (decpt == 0) reqd++;\n }\n if (sign) reqd++;\n p = sout;\n e = p + reqd;\n pad = width - reqd;\n if (pad > 0) {\n e += pad;\n while (pad-- > 0) *p++ = \' \';\n }\n if (sign) *p++ = \'-\';\n if (decpt <= 0 && prec > 0) {\n *p++ = \'0\';\n *p++ = \'.\';\n e++;\n while ( decpt < 0 ) {\n decpt++;\n *p++ = \'0\';\n }\n }\n while (p < e) {\n *p++ = *s++;\n if (p == e) break;\n if (--decpt == 0) *p++ = \'.\';\n }\n if (width < 0) {\n pad = (reqd + width) * -1;\n while (pad-- > 0) *p++ = \' \';\n }\n *p = 0;\n return sout;\n}\n#endif\n\n\nnamespace xod {\n//----------------------------------------------------------------------------\n// Type definitions\n//----------------------------------------------------------------------------\n#define NO_NODE ((NodeId)-1)\n\ntypedef double Number;\ntypedef bool Logic;\n\n#if NODE_COUNT < 256\ntypedef uint8_t NodeId;\n#elif NODE_COUNT < 65536\ntypedef uint16_t NodeId;\n#else\ntypedef uint32_t NodeId;\n#endif\n\n/*\n * Context is a handle passed to each node `evaluate` function. Currently, it\u2019s\n * alias for NodeId but likely will be changed in future to support list\n * lifting and other features\n */\ntypedef NodeId Context;\n\n/*\n * LSB of a dirty flag shows whether a particular node is dirty or not\n * Other bits shows dirtieness of its particular outputs:\n * - 1-st bit for 0-th output\n * - 2-nd bit for 1-st output\n * - etc\n *\n * An outcome limitation is that a native node must not have more than 7 output\n * pins.\n */\ntypedef uint8_t DirtyFlags;\n\ntypedef unsigned long TimeMs;\ntypedef void (*EvalFuncPtr)(Context ctx);\n\n/*\n * Each input stores a reference to its upstream node so that we can get values\n * on input pins. Having a direct pointer to the value is not enough because we\n * want to know dirty\u2019ness as well. So we have to use this structure instead of\n * a pointer.\n */\nstruct UpstreamPinRef {\n // Upstream node ID\n NodeId nodeId;\n // Index of the upstream node\u2019s output.\n // Use 3 bits as it just enough to store values 0..7\n uint16_t pinIndex : 3;\n // Byte offset in a storage of the upstream node where the actual pin value\n // is stored\n uint16_t storageOffset : 13;\n};\n\n/*\n * Input descriptor is a metaprogramming structure used to enforce an\n * input\u2019s type and store its wiring data location as a zero-RAM constant.\n *\n * A specialized descriptor is required by `getValue` function. Every\n * input of every type node gets its own descriptor in generated code that\n * can be accessed as input_FOO. Where FOO is a pin identifier.\n */\ntemplate<typename ValueT_, size_t wiringOffset>\nstruct InputDescriptor {\n typedef ValueT_ ValueT;\n enum {\n WIRING_OFFSET = wiringOffset\n };\n};\n\n/*\n * Output descriptor serve the same purpose as InputDescriptor but for\n * outputs.\n *\n * In addition to wiring data location it keeps storage data location (where\n * actual value is stored) and own zero-based index among outputs of a particular\n * node\n */\ntemplate<typename ValueT_, size_t wiringOffset, size_t storageOffset, uint8_t index>\nstruct OutputDescriptor {\n typedef ValueT_ ValueT;\n enum {\n WIRING_OFFSET = wiringOffset,\n STORAGE_OFFSET = storageOffset,\n INDEX = index\n };\n};\n\n//----------------------------------------------------------------------------\n// Forward declarations\n//----------------------------------------------------------------------------\nextern void* const g_storages[NODE_COUNT];\nextern const void* const g_wiring[NODE_COUNT];\nextern DirtyFlags g_dirtyFlags[NODE_COUNT];\n\n// TODO: replace with a compact list\nextern TimeMs g_schedule[NODE_COUNT];\n\nvoid clearTimeout(NodeId nid);\nbool isTimedOut(NodeId nid);\n\n//----------------------------------------------------------------------------\n// Engine (private API)\n//----------------------------------------------------------------------------\n\nTimeMs g_transactionTime;\n\nvoid* getStoragePtr(NodeId nid, size_t offset) {\n return (uint8_t*)pgm_read_ptr(&g_storages[nid]) + offset;\n}\n\ntemplate<typename T>\nT getStorageValue(NodeId nid, size_t offset) {\n return *reinterpret_cast<T*>(getStoragePtr(nid, offset));\n}\n\nvoid* getWiringPgmPtr(NodeId nid, size_t offset) {\n return (uint8_t*)pgm_read_ptr(&g_wiring[nid]) + offset;\n}\n\ntemplate<typename T>\nT getWiringValue(NodeId nid, size_t offset) {\n T result;\n memcpy_P(&result, getWiringPgmPtr(nid, offset), sizeof(T));\n return result;\n}\n\nbool isOutputDirty(NodeId nid, uint8_t index) {\n return g_dirtyFlags[nid] & (1 << (index + 1));\n}\n\nbool isInputDirtyImpl(NodeId nid, size_t wiringOffset) {\n UpstreamPinRef ref = getWiringValue<UpstreamPinRef>(nid, wiringOffset);\n if (ref.nodeId == NO_NODE)\n return false;\n\n return isOutputDirty(ref.nodeId, ref.pinIndex);\n}\n\ntemplate<typename InputT>\nbool isInputDirty(NodeId nid) {\n return isInputDirtyImpl(nid, InputT::WIRING_OFFSET);\n}\n\nvoid markPinDirty(NodeId nid, uint8_t index) {\n g_dirtyFlags[nid] |= 1 << (index + 1);\n}\n\nvoid markNodeDirty(NodeId nid) {\n g_dirtyFlags[nid] |= 0x1;\n}\n\nbool isNodeDirty(NodeId nid) {\n return g_dirtyFlags[nid] & 0x1;\n}\n\ntemplate<typename T>\nT getOutputValueImpl(NodeId nid, size_t storageOffset) {\n return getStorageValue<T>(nid, storageOffset);\n}\n\ntemplate<typename T>\nT getInputValueImpl(NodeId nid, size_t wiringOffset) {\n UpstreamPinRef ref = getWiringValue<UpstreamPinRef>(nid, wiringOffset);\n if (ref.nodeId == NO_NODE)\n return (T)0;\n\n return getOutputValueImpl<T>(ref.nodeId, ref.storageOffset);\n}\n\ntemplate<typename T>\nstruct always_false {\n enum { value = 0 };\n};\n\n// GetValue -- classical trick for partial function (API `xod::getValue`)\n// template specialization\ntemplate<typename InputOutputT>\nstruct GetValue {\n static typename InputOutputT::ValueT getValue(Context ctx) {\n static_assert(\n always_false<InputOutputT>::value,\n "You should provide an input_XXX or output_YYY argument " \\\n "in angle brackets of getValue"\n );\n\n }\n};\n\ntemplate<typename ValueT, size_t wiringOffset>\nstruct GetValue<InputDescriptor<ValueT, wiringOffset>> {\n static ValueT getValue(Context ctx) {\n return getInputValueImpl<ValueT>(ctx, wiringOffset);\n }\n};\n\ntemplate<typename ValueT, size_t wiringOffset, size_t storageOffset, uint8_t index>\nstruct GetValue<OutputDescriptor<ValueT, wiringOffset, storageOffset, index>> {\n static ValueT getValue(Context ctx) {\n return getOutputValueImpl<ValueT>(ctx, storageOffset);\n }\n};\n\ntemplate<typename T>\nvoid emitValueImpl(\n NodeId nid,\n size_t storageOffset,\n size_t wiringOffset,\n uint8_t index,\n T value) {\n\n // Store new value and make the node itself dirty\n T* storedValue = reinterpret_cast<T*>(getStoragePtr(nid, storageOffset));\n *storedValue = value;\n markPinDirty(nid, index);\n\n // Notify downstream nodes about changes\n // NB: linked nodes array is in PGM space\n const NodeId* pDownstreamNid = getWiringValue<const NodeId*>(nid, wiringOffset);\n NodeId downstreamNid = pgm_read_nodeid(pDownstreamNid);\n\n while (downstreamNid != NO_NODE) {\n markNodeDirty(downstreamNid);\n downstreamNid = pgm_read_nodeid(pDownstreamNid++);\n }\n}\n\nvoid evaluateNode(NodeId nid) {\n XOD_TRACE_F("eval #");\n XOD_TRACE_LN(nid);\n EvalFuncPtr eval = getWiringValue<EvalFuncPtr>(nid, 0);\n eval(nid);\n}\n\nvoid runTransaction() {\n g_transactionTime = millis();\n\n XOD_TRACE_F("Transaction started, t=");\n XOD_TRACE_LN(g_transactionTime);\n\n // defer-* nodes are always at the very bottom of the graph,\n // so no one will recieve values emitted by them.\n // We must evaluate them before everybody else\n // to give them a chance to emit values.\n for (NodeId nid = NODE_COUNT - DEFER_NODE_COUNT; nid < NODE_COUNT; ++nid) {\n if (isTimedOut(nid)) {\n evaluateNode(nid);\n // Clear node dirty flag, so it will evaluate\n // on "regular" pass only if it has a dirty input.\n // We must save dirty output flags,\n // or \'isInputDirty\' will not work correctly in "downstream" nodes.\n g_dirtyFlags[nid] &= ~0x1;\n clearTimeout(nid);\n }\n }\n\n for (NodeId nid = 0; nid < NODE_COUNT; ++nid) {\n if (isNodeDirty(nid)) {\n evaluateNode(nid);\n\n // If the schedule is stale, clear timeout so that\n // the node would not be marked dirty again in idle\n if (isTimedOut(nid))\n clearTimeout(nid);\n }\n }\n\n // Clear dirtieness for all nodes and pins\n memset(g_dirtyFlags, 0, sizeof(g_dirtyFlags));\n\n XOD_TRACE_F("Transaction completed, t=");\n XOD_TRACE_LN(millis());\n}\n\nvoid idle() {\n // Mark timed out nodes dirty. Do not reset schedule here to give\n // a chance for a node to get a reasonable result from `isTimedOut`\n TimeMs now = millis();\n for (NodeId nid = 0; nid < NODE_COUNT; ++nid) {\n TimeMs t = g_schedule[nid];\n if (t && t < now)\n markNodeDirty(nid);\n }\n}\n\n//----------------------------------------------------------------------------\n// Public API (can be used by native nodes\u2019 `evaluate` functions)\n//----------------------------------------------------------------------------\n\ntemplate<typename InputOutputT>\ntypename InputOutputT::ValueT getValue(Context ctx) {\n return GetValue<InputOutputT>::getValue(ctx);\n}\n\ntemplate<typename OutputT>\nvoid emitValue(NodeId nid, typename OutputT::ValueT value) {\n emitValueImpl(\n nid,\n OutputT::STORAGE_OFFSET,\n OutputT::WIRING_OFFSET,\n OutputT::INDEX,\n value);\n}\n\nTimeMs transactionTime() {\n return g_transactionTime;\n}\n\nvoid setTimeout(NodeId nid, TimeMs timeout) {\n g_schedule[nid] = transactionTime() + timeout;\n}\n\nvoid clearTimeout(NodeId nid) {\n g_schedule[nid] = 0;\n}\n\nbool isTimedOut(NodeId nid) {\n return g_schedule[nid] && g_schedule[nid] < transactionTime();\n}\n\n} // namespace xod\n\n//----------------------------------------------------------------------------\n// Entry point\n//----------------------------------------------------------------------------\nvoid setup() {\n // FIXME: looks like there is a rounding bug. Waiting for 100ms fights it\n delay(100);\n#ifdef XOD_DEBUG\n DEBUG_SERIAL.begin(115200);\n#endif\n XOD_TRACE_FLN("\\n\\nProgram started");\n}\n\nvoid loop() {\n xod::idle();\n xod::runTransaction();\n}\n'; | ||
/* babel-plugin-inline-import '../platform/runtime.cpp' */var runtimeCpp = '\n/*=============================================================================\n *\n *\n * Runtime\n *\n *\n =============================================================================*/\n\n//----------------------------------------------------------------------------\n// Debug routines\n//----------------------------------------------------------------------------\n#ifndef DEBUG_SERIAL\n# define DEBUG_SERIAL Serial\n#endif\n\n#if defined(XOD_DEBUG) && defined(XOD_DEBUG_ENABLE_TRACE)\n# define XOD_TRACE(x) { DEBUG_SERIAL.print(x); DEBUG_SERIAL.flush(); }\n# define XOD_TRACE_LN(x) { DEBUG_SERIAL.println(x); DEBUG_SERIAL.flush(); }\n# define XOD_TRACE_F(x) XOD_TRACE(F(x))\n# define XOD_TRACE_FLN(x) XOD_TRACE_LN(F(x))\n#else\n# define XOD_TRACE(x)\n# define XOD_TRACE_LN(x)\n# define XOD_TRACE_F(x)\n# define XOD_TRACE_FLN(x)\n#endif\n\n//----------------------------------------------------------------------------\n// PGM space utilities\n//----------------------------------------------------------------------------\n#define pgm_read_nodeid(address) (pgm_read_word(address))\n\n/*\n * Workaround for bugs:\n * https://github.com/arduino/ArduinoCore-sam/pull/43\n * https://github.com/arduino/ArduinoCore-samd/pull/253\n * Remove after the PRs merge\n */\n#if !defined(ARDUINO_ARCH_AVR) && defined(pgm_read_ptr)\n# undef pgm_read_ptr\n# define pgm_read_ptr(addr) (*(const void **)(addr))\n#endif\n\n//----------------------------------------------------------------------------\n// Compatibilities\n//----------------------------------------------------------------------------\n\n#if !defined(ARDUINO_ARCH_AVR)\n/*\n * Provide dtostrf function for non-AVR platforms. Although many platforms\n * provide a stub many others do not. And the stub is based on `sprintf`\n * which doesn\u2019t work with floating point formatters on some platforms\n * (e.g. Arduino M0).\n *\n * This is an implementation based on `fcvt` standard function. Taken here:\n * https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614\n */\nchar *dtostrf(double val, int width, unsigned int prec, char *sout) {\n int decpt, sign, reqd, pad;\n const char *s, *e;\n char *p;\n s = fcvt(val, prec, &decpt, &sign);\n if (prec == 0 && decpt == 0) {\n s = (*s < \'5\') ? "0" : "1";\n reqd = 1;\n } else {\n reqd = strlen(s);\n if (reqd > decpt) reqd++;\n if (decpt == 0) reqd++;\n }\n if (sign) reqd++;\n p = sout;\n e = p + reqd;\n pad = width - reqd;\n if (pad > 0) {\n e += pad;\n while (pad-- > 0) *p++ = \' \';\n }\n if (sign) *p++ = \'-\';\n if (decpt <= 0 && prec > 0) {\n *p++ = \'0\';\n *p++ = \'.\';\n e++;\n while ( decpt < 0 ) {\n decpt++;\n *p++ = \'0\';\n }\n }\n while (p < e) {\n *p++ = *s++;\n if (p == e) break;\n if (--decpt == 0) *p++ = \'.\';\n }\n if (width < 0) {\n pad = (reqd + width) * -1;\n while (pad-- > 0) *p++ = \' \';\n }\n *p = 0;\n return sout;\n}\n#endif\n\n\nnamespace xod {\n//----------------------------------------------------------------------------\n// Type definitions\n//----------------------------------------------------------------------------\ntypedef double Number;\ntypedef bool Logic;\ntypedef unsigned long TimeMs;\ntypedef uint8_t DirtyFlags;\n\n//----------------------------------------------------------------------------\n// Global variables\n//----------------------------------------------------------------------------\n\nTimeMs g_transactionTime;\n\n//----------------------------------------------------------------------------\n// Metaprogramming utilities\n//----------------------------------------------------------------------------\n\ntemplate<typename T> struct always_false {\n enum { value = 0 };\n};\n\n//----------------------------------------------------------------------------\n// Forward declarations\n//----------------------------------------------------------------------------\n\nTimeMs transactionTime();\nvoid runTransaction(bool firstRun);\n\n//----------------------------------------------------------------------------\n// Engine (private API)\n//----------------------------------------------------------------------------\n\nnamespace detail {\n\ntemplate<typename NodeT>\nbool isTimedOut(const NodeT* node) {\n TimeMs t = node->timeoutAt;\n // TODO: deal with uint32 overflow\n return t && t < transactionTime();\n}\n\n// Marks timed out node dirty. Do not reset timeoutAt here to give\n// a chance for a node to get a reasonable result from `isTimedOut`\n// later during its `evaluate`\ntemplate<typename NodeT>\nvoid checkTriggerTimeout(NodeT* node) {\n node->isNodeDirty |= isTimedOut(node);\n}\n\ntemplate<typename NodeT>\nvoid clearTimeout(NodeT* node) {\n node->timeoutAt = 0;\n}\n\ntemplate<typename NodeT>\nvoid clearStaleTimeout(NodeT* node) {\n if (isTimedOut(node))\n clearTimeout(node);\n}\n\n} // namespace detail\n\n//----------------------------------------------------------------------------\n// Public API (can be used by native nodes\u2019 `evaluate` functions)\n//----------------------------------------------------------------------------\n\nTimeMs transactionTime() {\n return g_transactionTime;\n}\n\ntemplate<typename ContextT>\nvoid setTimeout(ContextT* ctx, TimeMs timeout) {\n ctx->_node->timeoutAt = transactionTime() + timeout;\n}\n\ntemplate<typename ContextT>\nvoid clearTimeout(ContextT* ctx) {\n detail::clearTimeout(ctx->_node);\n}\n\ntemplate<typename ContextT>\nbool isTimedOut(const ContextT* ctx) {\n return detail::isTimedOut(ctx->_node);\n}\n\n} // namespace xod\n\n//----------------------------------------------------------------------------\n// Entry point\n//----------------------------------------------------------------------------\nvoid setup() {\n // FIXME: looks like there is a rounding bug. Waiting for 100ms fights it\n delay(100);\n#ifdef XOD_DEBUG\n DEBUG_SERIAL.begin(115200);\n#endif\n XOD_TRACE_FLN("\\n\\nProgram started");\n\n xod::runTransaction(true);\n}\n\nvoid loop() {\n xod::runTransaction(false);\n}\n'; | ||
@@ -44,27 +46,27 @@ // ============================================================================= | ||
var trimTrailingWhitespace = _ramda2.default.replace(/\s+$/gm, '\n'); | ||
var trimTrailingWhitespace = R.replace(/\s+$/gm, '\n'); | ||
var omitLocalIncludes = _ramda2.default.replace(/#include ".*$/gm, ''); | ||
var omitLocalIncludes = R.replace(/#include ".*$/gm, ''); | ||
var indexByPinKey = _ramda2.default.indexBy(_ramda2.default.prop('pinKey')); | ||
var indexByPinKey = R.indexBy(R.prop('pinKey')); | ||
var getPatchPins = function getPatchPins(direction) { | ||
return _ramda2.default.compose(indexByPinKey, _ramda2.default.path(['patch', direction])); | ||
return R.compose(indexByPinKey, R.path(['patch', direction])); | ||
}; | ||
var omitNullValues = _ramda2.default.map(_ramda2.default.when(_ramda2.default.propSatisfies(_ramda2.default.isNil, 'value'), _ramda2.default.omit(['value']))); | ||
var omitNullValues = R.map(R.when(R.propSatisfies(R.isNil, 'value'), R.omit(['value']))); | ||
var getNodePins = function getNodePins(direction) { | ||
return _ramda2.default.compose(indexByPinKey, omitNullValues, _ramda2.default.prop(direction)); | ||
return R.compose(indexByPinKey, omitNullValues, R.prop(direction)); | ||
}; | ||
var mergeAndListPins = function mergeAndListPins(direction, node) { | ||
return _ramda2.default.compose(_ramda2.default.values, _ramda2.default.converge(_ramda2.default.mergeWith(_ramda2.default.merge), [getPatchPins(direction), getNodePins(direction)]))(node); | ||
return R.compose(R.values, R.converge(R.mergeWith(R.merge), [getPatchPins(direction), getNodePins(direction)]))(node); | ||
}; | ||
// Converts DataType value to a corresponding C++ storage type | ||
var cppType = (0, _types.def)('cppType :: DataType -> String', _ramda2.default.propOr('unknown_type<void>', _ramda2.default.__, (_R$propOr = {}, _defineProperty(_R$propOr, _xodProject.PIN_TYPE.PULSE, 'Logic'), _defineProperty(_R$propOr, _xodProject.PIN_TYPE.BOOLEAN, 'Logic'), _defineProperty(_R$propOr, _xodProject.PIN_TYPE.NUMBER, 'Number'), _defineProperty(_R$propOr, _xodProject.PIN_TYPE.STRING, 'XString'), _R$propOr))); | ||
var cppType = (0, _types.def)('cppType :: DataType -> String', R.propOr('unknown_type<void>', R.__, (_R$propOr = {}, _defineProperty(_R$propOr, _xodProject.PIN_TYPE.PULSE, 'Logic'), _defineProperty(_R$propOr, _xodProject.PIN_TYPE.BOOLEAN, 'Logic'), _defineProperty(_R$propOr, _xodProject.PIN_TYPE.NUMBER, 'Number'), _defineProperty(_R$propOr, _xodProject.PIN_TYPE.STRING, 'XString'), _R$propOr))); | ||
// Formats a plain JS string into C++ string object | ||
var cppStringLiteral = (0, _types.def)('cppStringLiteral :: String -> String', _ramda2.default.ifElse(_ramda2.default.isEmpty, _ramda2.default.always('XString()'), function (str) { | ||
var cppStringLiteral = (0, _types.def)('cppStringLiteral :: String -> String', R.ifElse(R.isEmpty, R.always('XString()'), function (str) { | ||
return 'XStringCString("' + str + '")'; | ||
@@ -86,4 +88,7 @@ })); | ||
// Generate patch-level namespace name | ||
_handlebars2.default.registerHelper('ns', _ramda2.default.compose(_ramda2.default.join('__'), _ramda2.default.props(['owner', 'libName', 'patchName']))); | ||
_handlebars2.default.registerHelper('ns', R.compose(R.join('__'), R.props(['owner', 'libName', 'patchName']))); | ||
// Debug helper | ||
_handlebars2.default.registerHelper('json', JSON.stringify); | ||
// Returns declaration type specifier for an initial value of an output | ||
@@ -113,5 +118,21 @@ _handlebars2.default.registerHelper('decltype', function (type, value) { | ||
return _ramda2.default.propOr(_ramda2.default.always('unknown_type<void>'), type, (_R$propOr2 = {}, _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.PULSE, _ramda2.default.always('false')), _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.BOOLEAN, _ramda2.default.toString), _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.NUMBER, _ramda2.default.toString), _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.STRING, cppStringLiteral), _R$propOr2))(value); | ||
return R.propOr(R.always('unknown_type<void>'), type, (_R$propOr2 = {}, _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.PULSE, R.always('false')), _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.BOOLEAN, R.toString), _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.NUMBER, R.toString), _defineProperty(_R$propOr2, _xodProject.PIN_TYPE.STRING, cppStringLiteral), _R$propOr2))(value); | ||
}); | ||
// A helper to quickly introduce a new filtered {{each ...}} loop | ||
function registerHandlebarsFilterLoopHelper(name, predicate) { | ||
_handlebars2.default.registerHelper(name, function (list, block) { | ||
return R.compose(R.join(''), R.map(function (node) { | ||
return block.fn(node); | ||
}), R.filter(predicate))(list); | ||
}); | ||
} | ||
registerHandlebarsFilterLoopHelper('eachDeferNode', R.path(['patch', 'isDefer'])); | ||
registerHandlebarsFilterLoopHelper('eachNonConstantNode', R.pathEq(['patch', 'isConstant'], false)); | ||
registerHandlebarsFilterLoopHelper('eachNodeUsingTimeouts', R.path(['patch', 'usesTimeouts'])); | ||
registerHandlebarsFilterLoopHelper('eachLinkedInput', R.has('fromNodeId')); | ||
registerHandlebarsFilterLoopHelper('eachNonlinkedInput', R.complement(R.has('fromNodeId'))); | ||
registerHandlebarsFilterLoopHelper('eachDirtyablePin', R.prop('isDirtyable')); | ||
// ============================================================================= | ||
@@ -143,18 +164,15 @@ // | ||
var renderImpl = exports.renderImpl = (0, _types.def)('renderImpl :: TPatch -> String', function (data) { | ||
var ctx = _ramda2.default.applySpec({ | ||
owner: _ramda2.default.prop('owner'), | ||
libName: _ramda2.default.prop('libName'), | ||
patchName: _ramda2.default.prop('patchName'), | ||
var ctx = R.applySpec({ | ||
owner: R.prop('owner'), | ||
libName: R.prop('libName'), | ||
patchName: R.prop('patchName'), | ||
GENERATED_CODE: renderPatchContext | ||
})(data); | ||
var patchImpl = _ramda2.default.prop('impl', data); | ||
var patchImpl = R.prop('impl', data); | ||
return _handlebars2.default.compile(patchImpl, renderingOptions)(ctx); | ||
}); | ||
var renderImplList = exports.renderImplList = (0, _types.def)('renderImplList :: [TPatch] -> String', _ramda2.default.compose(trimTrailingWhitespace, templates.implList, _ramda2.default.map(_ramda2.default.applySpec({ | ||
owner: _ramda2.default.prop('owner'), | ||
libName: _ramda2.default.prop('libName'), | ||
patchName: _ramda2.default.prop('patchName'), | ||
implementation: renderImpl | ||
})))); | ||
var renderImplList = exports.renderImplList = (0, _types.def)('renderImplList :: [TPatch] -> String', R.compose(trimTrailingWhitespace, templates.implList, R.map(function (patch) { | ||
return R.assoc('implementation', renderImpl(patch), patch); | ||
}))); | ||
@@ -164,3 +182,7 @@ var renderProgram = exports.renderProgram = (0, _types.def)('renderProgram :: [TNode] -> String', function (nodes) { | ||
}); | ||
var renderProject = exports.renderProject = (0, _types.def)('renderProject :: TProject -> String', function (project) { | ||
var renderProject = exports.renderProject = (0, _types.def)('renderProject :: TProject -> String', function (originalProject) { | ||
// HACK: We have to clone TProject to prevent mutating | ||
// of original TProject by Handlebars templates. | ||
var project = R.clone(originalProject); | ||
var config = renderConfig(project.config); | ||
@@ -170,4 +192,4 @@ var impls = renderImplList(project.patches); | ||
return _ramda2.default.join('\n')([preambleH, config, stlH, listViewsH, omitLocalIncludes(listFuncsH), runtimeCpp, impls, program]); | ||
return R.join('\n')([preambleH, config, stlH, listViewsH, omitLocalIncludes(listFuncsH), runtimeCpp, impls, program]); | ||
}); | ||
//# sourceMappingURL=templates.js.map |
@@ -6,7 +6,7 @@ 'use strict'; | ||
}); | ||
exports.transpile = exports.transformProjectWithDebug = exports.transformProject = exports.getNodeIdsMap = exports.getInitialDirtyFlags = undefined; | ||
exports.forUnitTests = exports.transpile = exports.transformProjectWithDebug = exports.transformProject = exports.getNodeIdsMap = undefined; | ||
var _ramda = require('ramda'); | ||
var _ramda2 = _interopRequireDefault(_ramda); | ||
var R = _interopRequireWildcard(_ramda); | ||
@@ -27,8 +27,6 @@ var _ramdaFantasy = require('ramda-fantasy'); | ||
var _directives = require('./directives'); | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } | ||
//----------------------------------------------------------------------------- | ||
@@ -41,44 +39,39 @@ // | ||
// :: x -> Number | ||
var toInt = _ramda2.default.flip(parseInt)(10); | ||
var toInt = R.flip(parseInt)(10); | ||
// TODO: rename to XP | ||
var getNodeCount = (0, _types.def)('getNodeCount :: Patch -> Number', _ramda2.default.compose(_ramda2.default.length, Project.listNodes)); | ||
var getOutputCount = (0, _types.def)('getOutputCount :: Project -> Number', _ramda2.default.compose(_ramda2.default.reduce(_ramda2.default.max, 0), _ramda2.default.map(_ramda2.default.compose(_ramda2.default.length, Project.listOutputPins)), Project.listPatches)); | ||
var kebabToSnake = R.replace(/-/g, '_'); | ||
var kebabToSnake = _ramda2.default.replace(/-/g, '_'); | ||
var createPatchNames = (0, _types.def)('createPatchNames :: PatchPath -> { owner :: String, libName :: String, patchName :: String }', R.compose(R.map(kebabToSnake), R.ifElse(R.startsWith(['@']), function (parts) { | ||
return { | ||
owner: '', | ||
libName: '', | ||
patchName: parts.slice(1).join('/') | ||
}; | ||
}, function (parts) { | ||
return { | ||
owner: parts[0], | ||
libName: parts[1], | ||
patchName: parts.slice(2).join('/') | ||
}; | ||
}), R.split('/'))); | ||
var createPatchNames = (0, _types.def)('createPatchNames :: PatchPath -> { owner :: String, libName :: String, patchName :: String }', function (path) { | ||
// TODO: this handles @/local-patches incorrectly | ||
var _R$split = _ramda2.default.split('/', path), | ||
_R$split2 = _toArray(_R$split), | ||
owner = _R$split2[0], | ||
libName = _R$split2[1], | ||
patchNameParts = _R$split2.slice(2); | ||
var patchName = patchNameParts.join('/'); | ||
return _ramda2.default.map(kebabToSnake, { | ||
owner: owner, | ||
libName: libName, | ||
patchName: patchName | ||
}); | ||
}); | ||
var findPatchByPath = (0, _types.def)('findPatchByPath :: PatchPath -> [TPatch] -> TPatch', function (path, patches) { | ||
return _ramda2.default.compose(_ramda2.default.find(_ramda2.default.__, patches), _ramda2.default.allPass, _ramda2.default.map(_ramda2.default.apply(_ramda2.default.propEq)), _ramda2.default.toPairs, createPatchNames)(path); | ||
return R.compose(R.find(R.__, patches), R.allPass, R.map(R.apply(R.propEq)), R.toPairs, createPatchNames)(path); | ||
}); | ||
var getLinksInputNodeIds = (0, _types.def)('getLinksInputNodeIds :: [Link] -> [TNodeId]', _ramda2.default.compose(_ramda2.default.uniq, _ramda2.default.map(_ramda2.default.compose(toInt, Project.getLinkInputNodeId)))); | ||
var getLinksInputNodeIds = (0, _types.def)('getLinksInputNodeIds :: [Link] -> [TNodeId]', R.compose(R.uniq, R.map(R.compose(toInt, Project.getLinkInputNodeId)))); | ||
var getPatchByNodeId = (0, _types.def)('getPatchByNodeId :: Project -> PatchPath -> [TPatch] -> NodeId -> TPatch', function (project, entryPath, patches, nodeId) { | ||
return _ramda2.default.compose(findPatchByPath(_ramda2.default.__, patches), Project.getNodeType, Project.getNodeByIdUnsafe(nodeId), Project.getPatchByPathUnsafe)(entryPath, project); | ||
return R.compose(findPatchByPath(R.__, patches), Project.getNodeType, Project.getNodeByIdUnsafe(nodeId), Project.getPatchByPathUnsafe)(entryPath, project); | ||
}); | ||
var toposortProject = (0, _types.def)('toposortProject :: PatchPath -> Project -> Either Error Object', function (path, project) { | ||
return _ramda2.default.compose(_ramda2.default.chain(function (nodeIdsMap) { | ||
return _ramda2.default.compose(_ramda2.default.map(_ramda2.default.applySpec({ | ||
project: _ramda2.default.identity, | ||
nodeIdsMap: _ramda2.default.always(nodeIdsMap) | ||
return R.compose(R.chain(function (nodeIdsMap) { | ||
return R.compose(R.map(R.applySpec({ | ||
project: R.identity, | ||
nodeIdsMap: R.always(nodeIdsMap) | ||
})), function () { | ||
return Project.updatePatch(path, Project.applyNodeIdMap(_ramda2.default.__, nodeIdsMap), project); | ||
return Project.updatePatch(path, Project.applyNodeIdMap(R.__, nodeIdsMap), project); | ||
})(nodeIdsMap); | ||
@@ -94,45 +87,47 @@ }), Project.getTopologyMap, Project.getPatchByPathUnsafe)(path, project); | ||
// Creates a TConfig object from entry-point path and project | ||
var createTConfig = (0, _types.def)('createTConfig :: TranspilationOptions -> PatchPath -> Number -> Project -> TConfig', function (opts, path, deferNodeCount, project) { | ||
return _ramda2.default.applySpec({ | ||
NODE_COUNT: _ramda2.default.compose(getNodeCount, Project.getPatchByPathUnsafe(path)), | ||
MAX_OUTPUT_COUNT: getOutputCount, | ||
XOD_DEBUG: function XOD_DEBUG() { | ||
return opts.debug; | ||
}, | ||
DEFER_NODE_COUNT: _ramda2.default.always(deferNodeCount) | ||
})(project); | ||
}); | ||
var createTPatches = (0, _types.def)('createTPatches :: PatchPath -> Project -> [TPatch]', function (entryPath, project) { | ||
return _ramda2.default.compose(_ramda2.default.values, _ramda2.default.mapObjIndexed(function (patch, path) { | ||
return R.compose(R.values, R.mapObjIndexed(function (patch, path) { | ||
var names = createPatchNames(path); | ||
var impl = (0, _xodFuncTools.explodeMaybe)('Implementation for ' + path + ' not found', Project.getImpl(patch)); | ||
var outputs = _ramda2.default.compose(_ramda2.default.map(_ramda2.default.applySpec({ | ||
var isDirtyable = function isDirtyable(pin) { | ||
return Project.getPinType(pin) === Project.PIN_TYPE.PULSE || (0, _directives.isDirtienessEnabled)(impl, pin.direction + '_' + pin.label); | ||
}; | ||
var outputs = R.compose(R.map(R.applySpec({ | ||
type: Project.getPinType, | ||
pinKey: Project.getPinLabel, | ||
value: _ramda2.default.compose(Project.defaultValueOfType, Project.getPinType) | ||
value: R.compose(Project.defaultValueOfType, Project.getPinType), | ||
isDirtyable: isDirtyable, | ||
isDirtyOnBoot: R.compose(R.not, R.equals(Project.PIN_TYPE.PULSE), Project.getPinType) | ||
})), Project.normalizePinLabels, Project.listOutputPins)(patch); | ||
var inputs = _ramda2.default.compose(_ramda2.default.map(_ramda2.default.applySpec({ | ||
var inputs = R.compose(R.map(R.applySpec({ | ||
type: Project.getPinType, | ||
pinKey: Project.getPinLabel | ||
pinKey: Project.getPinLabel, | ||
isDirtyable: isDirtyable | ||
})), Project.normalizePinLabels, Project.listInputPins)(patch); | ||
return _ramda2.default.merge(names, { | ||
var isThisIsThat = { | ||
isDefer: Project.isDeferNodeType(path), | ||
isConstant: Project.isConstantNodeType(path), | ||
usesTimeouts: (0, _directives.areTimeoutsEnabled)(impl), | ||
usesNodeId: (0, _directives.isNodeIdEnabled)(impl) | ||
}; | ||
return R.mergeAll([names, isThisIsThat, { | ||
outputs: outputs, | ||
inputs: inputs, | ||
impl: impl | ||
}); | ||
}), _ramda2.default.omit([entryPath]), _ramda2.default.indexBy(Project.getPatchPath), Project.listPatchesWithoutBuiltIns)(project); | ||
}]); | ||
}), R.omit([entryPath]), R.indexBy(Project.getPatchPath), Project.listPatchesWithoutBuiltIns)(project); | ||
}); | ||
var getPinLabelsMap = (0, _types.def)('getPinLabelsMap :: [Pin] -> Map PinKey PinLabel', _ramda2.default.compose(_ramda2.default.map(Project.getPinLabel), _ramda2.default.indexBy(Project.getPinKey))); | ||
var getPinLabelsMap = (0, _types.def)('getPinLabelsMap :: [Pin] -> Map PinKey PinLabel', R.compose(R.map(Project.getPinLabel), R.indexBy(Project.getPinKey))); | ||
var getNodePinsUnsafe = (0, _types.def)('getNodePinsUnsafe :: Node -> Project -> [Pin]', function (node, project) { | ||
return _ramda2.default.compose((0, _xodFuncTools.explodeMaybe)('Can\u2019t get node pins of node ' + node + '. Referred type missing?'), Project.getNodePins)(node, project); | ||
return R.compose((0, _xodFuncTools.explodeMaybe)('Can\u2019t get node pins of node ' + node + '. Referred type missing?'), Project.getNodePins)(node, project); | ||
}); | ||
var getNodePinLabels = (0, _types.def)('getNodePinLabels :: Node -> Project -> Map PinKey PinLabel', _ramda2.default.compose(getPinLabelsMap, Project.normalizePinLabels, getNodePinsUnsafe)); | ||
var getNodePinLabels = (0, _types.def)('getNodePinLabels :: Node -> Project -> Map PinKey PinLabel', R.compose(getPinLabelsMap, Project.normalizePinLabels, getNodePinsUnsafe)); | ||
@@ -145,3 +140,3 @@ // TODO: Remove it when `Project.getBoundValue` will return default values | ||
var getDefaultPinValue = (0, _types.def)('getDefaultPinValue :: PinKey -> Node -> Project -> DataValue', function (pinKey, node, project) { | ||
return _ramda2.default.compose((0, _xodFuncTools.explodeMaybe)('Can\u2019t find pin with key ' + pinKey + ' for node ' + node + '"'), _ramda2.default.map(_ramda2.default.compose(Project.defaultValueOfType, Project.getPinType)), _ramda2.default.chain(Project.getPinByKey(pinKey)), Project.getPatchByNode(_ramda2.default.__, project))(node); | ||
return R.compose((0, _xodFuncTools.explodeMaybe)('Can\u2019t find pin with key ' + pinKey + ' for node ' + node + '"'), R.map(R.compose(Project.defaultValueOfType, Project.getPinType)), R.chain(Project.getPinByKey(pinKey)), Project.getPatchByNode(R.__, project))(node); | ||
}); | ||
@@ -153,3 +148,3 @@ | ||
return _ramda2.default.compose(_ramda2.default.values, _ramda2.default.mapObjIndexed(function (links, pinKey) { | ||
return R.compose(R.values, R.mapObjIndexed(function (links, pinKey) { | ||
return { | ||
@@ -160,3 +155,3 @@ to: getLinksInputNodeIds(links), | ||
}; | ||
}), _ramda2.default.groupBy(Project.getLinkOutputPinKey), _ramda2.default.filter(Project.isLinkOutputNodeIdEquals(nodeId)), Project.listLinksByNode(node), Project.getPatchByPathUnsafe)(entryPath, project); | ||
}), R.groupBy(Project.getLinkOutputPinKey), R.filter(Project.isLinkOutputNodeIdEquals(nodeId)), Project.listLinksByNode(node), Project.getPatchByPathUnsafe)(entryPath, project); | ||
}); | ||
@@ -166,5 +161,9 @@ | ||
var pinKey = Project.getLinkOutputPinKey(link); | ||
return _ramda2.default.compose((0, _xodFuncTools.explodeMaybe)('Can\u2019t find pin with key ' + pinKey + ' for link ' + link + ' on patch ' + patch), _ramda2.default.map(_ramda2.default.compose(Project.getPinLabel, _ramda2.default.head, Project.normalizePinLabels, _ramda2.default.of)), _ramda2.default.chain(Project.getPinByKey(pinKey)), _ramda2.default.chain(Project.getPatchByNode(_ramda2.default.__, project)), Project.getNodeById(_ramda2.default.__, patch), Project.getLinkOutputNodeId)(link); | ||
return R.compose((0, _xodFuncTools.explodeMaybe)('Can\u2019t find pin with key ' + pinKey + ' for link ' + link + ' on patch ' + patch), R.map(R.compose(Project.getPinLabel, R.head, Project.normalizePinLabels, R.of)), R.chain(Project.getPinByKey(pinKey)), R.chain(Project.getPatchByNode(R.__, project)), Project.getNodeById(R.__, patch), Project.getLinkOutputNodeId)(link); | ||
}); | ||
var getTPatchOutputByLabel = (0, _types.def)('getTPatchOutputByLabel :: PinLabel -> TPatch -> TPatchOutput', function (pinLabel, tpatch) { | ||
return R.find(R.propEq('pinKey', pinLabel), tpatch.outputs); | ||
}); | ||
var getTNodeInputs = (0, _types.def)('getTNodeInputs :: Project -> PatchPath -> [TPatch] -> Node -> [TNodeInput]', function (project, entryPath, patches, node) { | ||
@@ -175,43 +174,30 @@ var patch = Project.getPatchByPathUnsafe(entryPath, project); | ||
return _ramda2.default.compose(_ramda2.default.map(_ramda2.default.applySpec({ | ||
nodeId: _ramda2.default.compose(toInt, Project.getLinkOutputNodeId), | ||
patch: _ramda2.default.compose(getPatchByNodeId(project, entryPath, patches), Project.getLinkOutputNodeId), | ||
pinKey: _ramda2.default.compose(_ramda2.default.prop(_ramda2.default.__, nodePins), Project.getLinkInputPinKey), | ||
fromPinKey: getOutputPinLabelByLink(project, patch) | ||
})), _ramda2.default.filter(Project.isLinkInputNodeIdEquals(nodeId)), Project.listLinksByNode(node))(patch); | ||
}); | ||
// :: Link -> TPatch | ||
var getUpstreamNodePatch = R.compose(getPatchByNodeId(project, entryPath, patches), Project.getLinkOutputNodeId); | ||
// returns an 8-bit number where the first bit is always set to 1, | ||
// and the rest are set to 0 if the corresponding pin has a pulse type: | ||
// | ||
// +---+---+---+---+---+---+---+---+ | ||
// | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | ||
// +---+---+---+---+---+---+---+---+ | ||
// <-*---*---*---* | ||
// etc Pin1 Pin0 | ||
var getInitialDirtyFlags = exports.getInitialDirtyFlags = (0, _types.def)('getInitialDirtyFlags :: [Pin] -> Number', _ramda2.default.reduce(function (flags, pin) { | ||
if (Project.getPinType(pin) !== Project.PIN_TYPE.PULSE) { | ||
return flags; | ||
} | ||
var mask = 2 << pin.order; // eslint-disable-line no-bitwise | ||
return flags ^ mask; // eslint-disable-line no-bitwise | ||
}, 255)); | ||
// :: Link -> PinLabel | ||
var getUpstreamPinLabel = getOutputPinLabelByLink(project, patch); | ||
var getNodeDirtyFlags = (0, _types.def)('getNodeDirtyFlags :: Project -> Node -> Number', _ramda2.default.compose(getInitialDirtyFlags, _ramda2.default.filter(Project.isOutputPin), _ramda2.default.flip(getNodePinsUnsafe))); | ||
// :: Link -> TNodeInput | ||
var constructTNodeInput = R.applySpec({ | ||
pinKey: R.compose(R.prop(R.__, nodePins), Project.getLinkInputPinKey), | ||
fromNodeId: R.compose(toInt, Project.getLinkOutputNodeId), | ||
fromPatch: getUpstreamNodePatch, | ||
fromPinKey: getUpstreamPinLabel, | ||
fromOutput: R.converge(getTPatchOutputByLabel, [getUpstreamPinLabel, getUpstreamNodePatch]) | ||
}); | ||
return R.compose(R.map(constructTNodeInput), R.filter(Project.isLinkInputNodeIdEquals(nodeId)), Project.listLinksByNode(node))(patch); | ||
}); | ||
var createTNodes = (0, _types.def)('createTNodes :: PatchPath -> [TPatch] -> Map NodeId String -> Project -> [TNode]', function (entryPath, patches, nodeIdsMap, project) { | ||
return _ramda2.default.compose(_ramda2.default.sortBy(_ramda2.default.compose(toInt, _ramda2.default.prop('id'))), _ramda2.default.map(_ramda2.default.applySpec({ | ||
id: _ramda2.default.compose(toInt, Project.getNodeId), | ||
originalId: _ramda2.default.compose((0, _xodFuncTools.reverseLookup)(_ramda2.default.__, nodeIdsMap), Project.getNodeId), | ||
patch: _ramda2.default.compose(findPatchByPath(_ramda2.default.__, patches), Project.getNodeType), | ||
return R.compose(R.sortBy(R.prop('id')), R.map(R.applySpec({ | ||
id: R.compose(toInt, Project.getNodeId), | ||
originalId: R.compose((0, _xodFuncTools.reverseLookup)(R.__, nodeIdsMap), Project.getNodeId), | ||
patch: R.compose(findPatchByPath(R.__, patches), Project.getNodeType), | ||
outputs: getTNodeOutputs(project, entryPath), | ||
inputs: getTNodeInputs(project, entryPath, patches), | ||
dirtyFlags: getNodeDirtyFlags(project) | ||
inputs: getTNodeInputs(project, entryPath, patches) | ||
})), Project.listNodes, Project.getPatchByPathUnsafe)(entryPath, project); | ||
}); | ||
var getDeferNodeCount = (0, _types.def)('getDeferNodeCount :: PatchPath -> Project -> Number', function (entryPath, project) { | ||
return _ramda2.default.compose(_ramda2.default.length, _ramda2.default.filter(_ramda2.default.compose(Project.isDeferNodeType, Project.getNodeType)), Project.listNodes, Project.getPatchByPathUnsafe)(entryPath, project); | ||
}); | ||
/** | ||
@@ -223,4 +209,4 @@ * Transforms Project into TProject. | ||
var transformProjectWithImpls = (0, _types.def)('transformProjectWithImpls :: Project -> PatchPath -> TranspilationOptions -> Either Error TProject', function (project, path, opts) { | ||
return _ramda2.default.compose(Project.wrapDeadRefErrorMessage(path), _ramda2.default.chain(function (tProject) { | ||
var nodeWithTooManyOutputs = _ramda2.default.find(_ramda2.default.pipe(_ramda2.default.prop('outputs'), _ramda2.default.length, _ramda2.default.lt(7)), tProject.patches); | ||
return R.compose(Project.wrapDeadRefErrorMessage(path), R.chain(function (tProject) { | ||
var nodeWithTooManyOutputs = R.find(R.pipe(R.prop('outputs'), R.length, R.lt(7)), tProject.patches); | ||
@@ -236,3 +222,3 @@ if (nodeWithTooManyOutputs) { | ||
return _ramdaFantasy.Either.of(tProject); | ||
}), _ramda2.default.map(function (_ref) { | ||
}), R.map(function (_ref) { | ||
var proj = _ref.project, | ||
@@ -242,17 +228,17 @@ nodeIdsMap = _ref.nodeIdsMap; | ||
var patches = createTPatches(path, proj); | ||
var deferNodeCount = getDeferNodeCount(path, proj); | ||
return _ramda2.default.applySpec({ | ||
config: createTConfig(opts, path, deferNodeCount), | ||
patches: _ramda2.default.always(patches), | ||
return R.merge({ | ||
config: { XOD_DEBUG: opts.debug } | ||
}, R.applySpec({ | ||
patches: R.always(patches), | ||
nodes: createTNodes(path, patches, nodeIdsMap) | ||
})(proj); | ||
}), _ramda2.default.chain(_ramda2.default.compose(toposortProject(path), Project.extractBoundInputsToConstNodes(_ramda2.default.__, path, project))), _ramda2.default.chain(Project.flatten(_ramda2.default.__, path)), _ramda2.default.unless(function () { | ||
})(proj)); | ||
}), R.chain(R.compose(toposortProject(path), Project.extractBoundInputsToConstNodes(R.__, path, project))), R.chain(Project.flatten(R.__, path)), R.unless(function () { | ||
return opts.debug; | ||
}, _ramda2.default.chain(Project.updatePatch(path, Project.removeDebugNodes))), Project.validateProject)(project); | ||
}, R.chain(Project.updatePatch(path, Project.removeDebugNodes))), Project.validateProject)(project); | ||
}); | ||
var getNodeIdsMap = exports.getNodeIdsMap = (0, _types.def)('getNodeIdsMap :: TProject -> Map NodeId String', _ramda2.default.compose(_ramda2.default.fromPairs, _ramda2.default.map(function (node) { | ||
var getNodeIdsMap = exports.getNodeIdsMap = (0, _types.def)('getNodeIdsMap :: TProject -> Map NodeId String', R.compose(R.fromPairs, R.map(function (node) { | ||
return [node.originalId, String(node.id)]; | ||
}), _ramda2.default.prop('nodes'))); | ||
}), R.prop('nodes'))); | ||
@@ -264,3 +250,3 @@ var transformProject = exports.transformProject = (0, _types.def)('transformProject :: Project -> PatchPath -> Either Error TProject', function (project, patchPath) { | ||
var transformProjectWithDebug = exports.transformProjectWithDebug = (0, _types.def)('transformProjectWithDebug :: Project -> PatchPath -> Either Error TProject', function (project, patchPath) { | ||
var options = _ramda2.default.merge(_constants.DEFAULT_TRANSPILATION_OPTIONS, { | ||
var options = R.merge(_constants.DEFAULT_TRANSPILATION_OPTIONS, { | ||
debug: true | ||
@@ -272,2 +258,6 @@ }); | ||
var transpile = exports.transpile = _templates.renderProject; | ||
var forUnitTests = exports.forUnitTests = { | ||
createPatchNames: createPatchNames | ||
}; | ||
//# sourceMappingURL=transpiler.js.map |
@@ -10,3 +10,3 @@ 'use strict'; | ||
var _ramda2 = _interopRequireDefault(_ramda); | ||
var R = _interopRequireWildcard(_ramda); | ||
@@ -27,6 +27,6 @@ var _sanctuaryDef = require('sanctuary-def'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/* Types are by convention starts with a capital leter, so: */ | ||
@@ -56,3 +56,3 @@ /* eslint-disable new-cap */ | ||
var TPinKey = OneOfType('TPinKey', [_xodProject.PinKey, _xodProject.PinLabel]); | ||
var DataValue = NullaryType('DataValue', _ramda2.default.complement(_ramda2.default.isNil)); | ||
var DataValue = NullaryType('DataValue', R.complement(R.isNil)); | ||
@@ -64,4 +64,2 @@ var TranspilationOptions = exports.TranspilationOptions = Model('TranspilationOptions', { | ||
var TConfig = exports.TConfig = Model('TConfig', { | ||
NODE_COUNT: _sanctuaryDef2.default.Number, | ||
MAX_OUTPUT_COUNT: _sanctuaryDef2.default.Number, | ||
XOD_DEBUG: _sanctuaryDef2.default.Boolean | ||
@@ -73,7 +71,11 @@ }); | ||
pinKey: _sanctuaryDef2.default.String, | ||
value: DataValue | ||
value: DataValue, | ||
isDirtyable: _sanctuaryDef2.default.Boolean, | ||
isDirtyOnBoot: _sanctuaryDef2.default.Boolean | ||
}); | ||
var TPatchInput = Model('TPatchInput', { | ||
pinKey: _sanctuaryDef2.default.String | ||
type: _sanctuaryDef2.default.String, | ||
pinKey: _sanctuaryDef2.default.String, | ||
isDirtyable: _sanctuaryDef2.default.Boolean | ||
}); | ||
@@ -85,2 +87,6 @@ | ||
patchName: _sanctuaryDef2.default.String, | ||
isDefer: _sanctuaryDef2.default.Boolean, | ||
isConstant: _sanctuaryDef2.default.Boolean, | ||
usesTimeouts: _sanctuaryDef2.default.Boolean, | ||
usesNodeId: _sanctuaryDef2.default.Boolean, | ||
outputs: _sanctuaryDef2.default.Array(TPatchOutput), | ||
@@ -98,5 +104,6 @@ inputs: _sanctuaryDef2.default.Array(TPatchInput), | ||
var TNodeInput = Model('TNodeInput', { | ||
nodeId: TNodeId, | ||
patch: TPatch, | ||
pinKey: TPinKey, | ||
fromNodeId: TNodeId, | ||
fromPatch: TPatch, | ||
fromOutput: TPatchOutput, | ||
fromPinKey: TPinKey | ||
@@ -110,4 +117,3 @@ }); | ||
outputs: _sanctuaryDef2.default.Array(TNodeOutput), | ||
inputs: _sanctuaryDef2.default.Array(TNodeInput), | ||
dirtyFlags: _sanctuaryDef2.default.Number | ||
inputs: _sanctuaryDef2.default.Array(TNodeInput) | ||
}); | ||
@@ -114,0 +120,0 @@ |
{ | ||
"name": "xod-arduino", | ||
"version": "0.15.2", | ||
"version": "0.19.0", | ||
"description": "XOD project: Arduino transpiler", | ||
@@ -19,15 +19,15 @@ "scripts": { | ||
"handlebars": "^4.0.6", | ||
"hm-def": "^0.2.0", | ||
"hm-def": "^0.3.2", | ||
"ramda": "^0.24.1", | ||
"ramda-fantasy": "^0.8.0", | ||
"sanctuary-def": "^0.9.0", | ||
"xod-func-tools": "^0.15.2", | ||
"xod-project": "^0.15.2" | ||
"sanctuary-def": "^0.14.0", | ||
"xod-func-tools": "^0.19.0", | ||
"xod-project": "^0.19.0" | ||
}, | ||
"devDependencies": { | ||
"babel-plugin-inline-import": "^2.0.4", | ||
"chai": "^3.5.0", | ||
"chai": "^4.1.2", | ||
"dirty-chai": "^1.2.2", | ||
"xod-fs": "^0.15.2" | ||
"xod-fs": "^0.19.0" | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import R from 'ramda'; | ||
import * as R from 'ramda'; | ||
import Handlebars from 'handlebars'; | ||
@@ -96,2 +96,5 @@ | ||
// Debug helper | ||
Handlebars.registerHelper('json', JSON.stringify); | ||
// Returns declaration type specifier for an initial value of an output | ||
@@ -136,2 +139,20 @@ Handlebars.registerHelper('decltype', (type, value) => ( | ||
// A helper to quickly introduce a new filtered {{each ...}} loop | ||
function registerHandlebarsFilterLoopHelper(name, predicate) { | ||
Handlebars.registerHelper(name, (list, block) => | ||
R.compose( | ||
R.join(''), | ||
R.map(node => block.fn(node)), | ||
R.filter(predicate) | ||
)(list) | ||
); | ||
} | ||
registerHandlebarsFilterLoopHelper('eachDeferNode', R.path(['patch', 'isDefer'])); | ||
registerHandlebarsFilterLoopHelper('eachNonConstantNode', R.pathEq(['patch', 'isConstant'], false)); | ||
registerHandlebarsFilterLoopHelper('eachNodeUsingTimeouts', R.path(['patch', 'usesTimeouts'])); | ||
registerHandlebarsFilterLoopHelper('eachLinkedInput', R.has('fromNodeId')); | ||
registerHandlebarsFilterLoopHelper('eachNonlinkedInput', R.complement(R.has('fromNodeId'))); | ||
registerHandlebarsFilterLoopHelper('eachDirtyablePin', R.prop('isDirtyable')); | ||
// ============================================================================= | ||
@@ -187,10 +208,3 @@ // | ||
templates.implList, | ||
R.map( | ||
R.applySpec({ | ||
owner: R.prop('owner'), | ||
libName: R.prop('libName'), | ||
patchName: R.prop('patchName'), | ||
implementation: renderImpl, | ||
}) | ||
) | ||
R.map(patch => R.assoc('implementation', renderImpl(patch), patch)), | ||
) | ||
@@ -207,3 +221,7 @@ ); | ||
'renderProject :: TProject -> String', | ||
(project) => { | ||
(originalProject) => { | ||
// HACK: We have to clone TProject to prevent mutating | ||
// of original TProject by Handlebars templates. | ||
const project = R.clone(originalProject); | ||
const config = renderConfig(project.config); | ||
@@ -210,0 +228,0 @@ const impls = renderImplList(project.patches); |
@@ -1,5 +0,6 @@ | ||
import R from 'ramda'; | ||
import * as R from 'ramda'; | ||
import { Either } from 'ramda-fantasy'; | ||
import { explodeMaybe, reverseLookup } from 'xod-func-tools'; | ||
// TODO: rename to XP | ||
import * as Project from 'xod-project'; | ||
@@ -11,2 +12,8 @@ import { def } from './types'; | ||
import { | ||
areTimeoutsEnabled, | ||
isNodeIdEnabled, | ||
isDirtienessEnabled, | ||
} from './directives'; | ||
//----------------------------------------------------------------------------- | ||
@@ -21,19 +28,2 @@ // | ||
const getNodeCount = def( | ||
'getNodeCount :: Patch -> Number', | ||
R.compose( | ||
R.length, | ||
Project.listNodes | ||
) | ||
); | ||
const getOutputCount = def( | ||
'getOutputCount :: Project -> Number', | ||
R.compose( | ||
R.reduce(R.max, 0), | ||
R.map(R.compose(R.length, Project.listOutputPins)), | ||
Project.listPatches | ||
) | ||
); | ||
const kebabToSnake = R.replace(/-/g, '_'); | ||
@@ -43,13 +33,19 @@ | ||
'createPatchNames :: PatchPath -> { owner :: String, libName :: String, patchName :: String }', | ||
(path) => { | ||
// TODO: this handles @/local-patches incorrectly | ||
const [owner, libName, ...patchNameParts] = R.split('/', path); | ||
const patchName = patchNameParts.join('/'); | ||
return R.map(kebabToSnake, { | ||
owner, | ||
libName, | ||
patchName, | ||
}); | ||
} | ||
R.compose( | ||
R.map(kebabToSnake), | ||
R.ifElse( | ||
R.startsWith(['@']), | ||
parts => ({ | ||
owner: '', | ||
libName: '', | ||
patchName: parts.slice(1).join('/'), | ||
}), | ||
parts => ({ | ||
owner: parts[0], | ||
libName: parts[1], | ||
patchName: parts.slice(2).join('/'), | ||
}) | ||
), | ||
R.split('/') | ||
) | ||
); | ||
@@ -110,14 +106,2 @@ | ||
// Creates a TConfig object from entry-point path and project | ||
const createTConfig = def( | ||
'createTConfig :: TranspilationOptions -> PatchPath -> Number -> Project -> TConfig', | ||
(opts, path, deferNodeCount, project) => R.applySpec({ | ||
NODE_COUNT: R.compose(getNodeCount, Project.getPatchByPathUnsafe(path)), | ||
MAX_OUTPUT_COUNT: getOutputCount, | ||
XOD_DEBUG: () => (opts.debug), | ||
DEFER_NODE_COUNT: R.always(deferNodeCount), | ||
})(project) | ||
); | ||
const createTPatches = def( | ||
@@ -134,2 +118,6 @@ 'createTPatches :: PatchPath -> Project -> [TPatch]', | ||
const isDirtyable = pin => | ||
Project.getPinType(pin) === Project.PIN_TYPE.PULSE | ||
|| isDirtienessEnabled(impl, `${pin.direction}_${pin.label}`); | ||
const outputs = R.compose( | ||
@@ -143,2 +131,8 @@ R.map(R.applySpec({ | ||
), | ||
isDirtyable, | ||
isDirtyOnBoot: R.compose( | ||
R.not, | ||
R.equals(Project.PIN_TYPE.PULSE), | ||
Project.getPinType | ||
), | ||
})), | ||
@@ -148,2 +142,3 @@ Project.normalizePinLabels, | ||
)(patch); | ||
const inputs = R.compose( | ||
@@ -153,2 +148,3 @@ R.map(R.applySpec({ | ||
pinKey: Project.getPinLabel, | ||
isDirtyable, | ||
})), | ||
@@ -159,3 +155,12 @@ Project.normalizePinLabels, | ||
return R.merge(names, | ||
const isThisIsThat = { | ||
isDefer: Project.isDeferNodeType(path), | ||
isConstant: Project.isConstantNodeType(path), | ||
usesTimeouts: areTimeoutsEnabled(impl), | ||
usesNodeId: isNodeIdEnabled(impl), | ||
}; | ||
return R.mergeAll([ | ||
names, | ||
isThisIsThat, | ||
{ | ||
@@ -165,4 +170,4 @@ outputs, | ||
impl, | ||
} | ||
); | ||
}, | ||
]); | ||
}), | ||
@@ -260,2 +265,7 @@ R.omit([entryPath]), | ||
const getTPatchOutputByLabel = def( | ||
'getTPatchOutputByLabel :: PinLabel -> TPatch -> TPatchOutput', | ||
(pinLabel, tpatch) => R.find(R.propEq('pinKey', pinLabel), tpatch.outputs) | ||
); | ||
const getTNodeInputs = def( | ||
@@ -268,12 +278,22 @@ 'getTNodeInputs :: Project -> PatchPath -> [TPatch] -> Node -> [TNodeInput]', | ||
// :: Link -> TPatch | ||
const getUpstreamNodePatch = R.compose( | ||
getPatchByNodeId(project, entryPath, patches), | ||
Project.getLinkOutputNodeId | ||
); | ||
// :: Link -> PinLabel | ||
const getUpstreamPinLabel = getOutputPinLabelByLink(project, patch); | ||
// :: Link -> TNodeInput | ||
const constructTNodeInput = R.applySpec({ | ||
pinKey: R.compose(R.prop(R.__, nodePins), Project.getLinkInputPinKey), | ||
fromNodeId: R.compose(toInt, Project.getLinkOutputNodeId), | ||
fromPatch: getUpstreamNodePatch, | ||
fromPinKey: getUpstreamPinLabel, | ||
fromOutput: R.converge(getTPatchOutputByLabel, [getUpstreamPinLabel, getUpstreamNodePatch]), | ||
}); | ||
return R.compose( | ||
R.map(R.applySpec({ | ||
nodeId: R.compose(toInt, Project.getLinkOutputNodeId), | ||
patch: R.compose( | ||
getPatchByNodeId(project, entryPath, patches), | ||
Project.getLinkOutputNodeId | ||
), | ||
pinKey: R.compose(R.prop(R.__, nodePins), Project.getLinkInputPinKey), | ||
fromPinKey: getOutputPinLabelByLink(project, patch), | ||
})), | ||
R.map(constructTNodeInput), | ||
R.filter(Project.isLinkInputNodeIdEquals(nodeId)), | ||
@@ -285,39 +305,6 @@ Project.listLinksByNode(node) | ||
// returns an 8-bit number where the first bit is always set to 1, | ||
// and the rest are set to 0 if the corresponding pin has a pulse type: | ||
// | ||
// +---+---+---+---+---+---+---+---+ | ||
// | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | ||
// +---+---+---+---+---+---+---+---+ | ||
// <-*---*---*---* | ||
// etc Pin1 Pin0 | ||
export const getInitialDirtyFlags = def( | ||
'getInitialDirtyFlags :: [Pin] -> Number', | ||
R.reduce( | ||
(flags, pin) => { | ||
if (Project.getPinType(pin) !== Project.PIN_TYPE.PULSE) { | ||
return flags; | ||
} | ||
const mask = 0b10 << pin.order; // eslint-disable-line no-bitwise | ||
return flags ^ mask; // eslint-disable-line no-bitwise | ||
}, | ||
0b11111111 | ||
) | ||
); | ||
const getNodeDirtyFlags = def( | ||
'getNodeDirtyFlags :: Project -> Node -> Number', | ||
R.compose( | ||
getInitialDirtyFlags, | ||
R.filter(Project.isOutputPin), | ||
R.flip(getNodePinsUnsafe) | ||
) | ||
); | ||
const createTNodes = def( | ||
'createTNodes :: PatchPath -> [TPatch] -> Map NodeId String -> Project -> [TNode]', | ||
(entryPath, patches, nodeIdsMap, project) => R.compose( | ||
R.sortBy( | ||
R.compose(toInt, R.prop('id')) | ||
), | ||
R.sortBy(R.prop('id')), | ||
R.map(R.applySpec({ | ||
@@ -329,3 +316,2 @@ id: R.compose(toInt, Project.getNodeId), | ||
inputs: getTNodeInputs(project, entryPath, patches), | ||
dirtyFlags: getNodeDirtyFlags(project), | ||
})), | ||
@@ -337,12 +323,2 @@ Project.listNodes, | ||
const getDeferNodeCount = def( | ||
'getDeferNodeCount :: PatchPath -> Project -> Number', | ||
(entryPath, project) => R.compose( | ||
R.length, | ||
R.filter(R.compose(Project.isDeferNodeType, Project.getNodeType)), | ||
Project.listNodes, | ||
Project.getPatchByPathUnsafe | ||
)(entryPath, project) | ||
); | ||
/** | ||
@@ -372,9 +348,12 @@ * Transforms Project into TProject. | ||
const patches = createTPatches(path, proj); | ||
const deferNodeCount = getDeferNodeCount(path, proj); | ||
return R.applySpec({ | ||
config: createTConfig(opts, path, deferNodeCount), | ||
patches: R.always(patches), | ||
nodes: createTNodes(path, patches, nodeIdsMap), | ||
})(proj); | ||
return R.merge( | ||
{ | ||
config: { XOD_DEBUG: opts.debug }, | ||
}, | ||
R.applySpec({ | ||
patches: R.always(patches), | ||
nodes: createTNodes(path, patches, nodeIdsMap), | ||
})(proj) | ||
); | ||
}), | ||
@@ -420,1 +399,5 @@ R.chain(R.compose( | ||
export const transpile = renderProject; | ||
export const forUnitTests = { | ||
createPatchNames, | ||
}; |
@@ -1,2 +0,2 @@ | ||
import R from 'ramda'; | ||
import * as R from 'ramda'; | ||
import $ from 'sanctuary-def'; | ||
@@ -38,4 +38,2 @@ import HMDef from 'hm-def'; | ||
export const TConfig = Model('TConfig', { | ||
NODE_COUNT: $.Number, | ||
MAX_OUTPUT_COUNT: $.Number, | ||
XOD_DEBUG: $.Boolean, | ||
@@ -48,6 +46,10 @@ }); | ||
value: DataValue, | ||
isDirtyable: $.Boolean, | ||
isDirtyOnBoot: $.Boolean, | ||
}); | ||
const TPatchInput = Model('TPatchInput', { | ||
type: $.String, | ||
pinKey: $.String, | ||
isDirtyable: $.Boolean, | ||
}); | ||
@@ -59,2 +61,6 @@ | ||
patchName: $.String, | ||
isDefer: $.Boolean, | ||
isConstant: $.Boolean, | ||
usesTimeouts: $.Boolean, | ||
usesNodeId: $.Boolean, | ||
outputs: $.Array(TPatchOutput), | ||
@@ -72,5 +78,6 @@ inputs: $.Array(TPatchInput), | ||
const TNodeInput = Model('TNodeInput', { | ||
nodeId: TNodeId, | ||
patch: TPatch, | ||
pinKey: TPinKey, | ||
fromNodeId: TNodeId, | ||
fromPatch: TPatch, | ||
fromOutput: TPatchOutput, | ||
fromPinKey: TPinKey, | ||
@@ -85,3 +92,2 @@ }); | ||
inputs: $.Array(TNodeInput), | ||
dirtyFlags: $.Number, | ||
}); | ||
@@ -88,0 +94,0 @@ |
@@ -8,6 +8,6 @@ import fs from 'fs'; | ||
import { loadProject } from 'xod-fs'; | ||
import { PIN_TYPE } from 'xod-project'; | ||
import { defaultizePin } from 'xod-project/test/helpers'; | ||
import { transpile, getInitialDirtyFlags, transformProject, getNodeIdsMap } from '../src/transpiler'; | ||
import { transpile, transformProject, getNodeIdsMap, forUnitTests } from '../src/transpiler'; | ||
const { createPatchNames } = forUnitTests; | ||
// Returns patch relative to repo’s `workspace` subdir | ||
@@ -24,3 +24,3 @@ const wsPath = (...subpath) => path.resolve(__dirname, '../../../workspace', ...subpath); | ||
return loadProject(wsPath(projName)) | ||
return loadProject([wsPath()], wsPath(projName)) | ||
.then(transformProject(R.__, '@/main')) | ||
@@ -42,6 +42,5 @@ .then(R.map(transpile)) | ||
() => | ||
loadProject(wsPath('blink')) | ||
loadProject([wsPath()], wsPath('blink')) | ||
.then(transformProject(R.__, '@/non-existing-patch')) | ||
.then(R.map(transpile)) | ||
.then(result => assert.ok(result.isLeft)) | ||
.then(result => assert.equal(result.isLeft, true)) | ||
); | ||
@@ -51,3 +50,3 @@ | ||
() => | ||
loadProject(wsPath('faulty')) | ||
loadProject([wsPath()], wsPath('faulty')) | ||
.then(transformProject(R.__, '@/too-many-outputs-main')) | ||
@@ -57,3 +56,3 @@ .then(R.map(transpile)) | ||
(err) => { | ||
assert.include(err.message, '@/too_many_outputs'); | ||
assert.include(err.message, 'too_many_outputs'); | ||
assert.include(err.message, 'has more than 7 outputs'); | ||
@@ -66,3 +65,3 @@ }, | ||
it('sorts nodes topologically', () => | ||
loadProject(wsPath('blink')) | ||
loadProject([wsPath()], wsPath('blink')) | ||
.then(R.pipe( | ||
@@ -103,3 +102,3 @@ transformProject(R.__, '@/main'), | ||
return loadProject(wsPath('blink')) | ||
return loadProject([wsPath()], wsPath('blink')) | ||
.then(transformProject(R.__, '@/main')) | ||
@@ -115,61 +114,18 @@ .then(R.map(getNodeIdsMap)) | ||
describe('getInitialDirtyFlags', () => { | ||
it('should return 0b11111111 if there are no pulse outputs', () => { | ||
assert.equal( | ||
getInitialDirtyFlags([]), | ||
0b11111111 | ||
); | ||
assert.equal( | ||
getInitialDirtyFlags([ | ||
defaultizePin({ order: 1, type: PIN_TYPE.BOOLEAN }), | ||
defaultizePin({ order: 0, type: PIN_TYPE.STRING }), | ||
defaultizePin({ order: 2, type: PIN_TYPE.NUMBER }), | ||
]), | ||
0b11111111 | ||
); | ||
describe('createPatchNames()', () => { | ||
it('correctly splits library patch paths', () => { | ||
assert.deepEqual(createPatchNames('bob-alice/bar-baz/qux-digun'), { | ||
owner: 'bob_alice', | ||
libName: 'bar_baz', | ||
patchName: 'qux_digun', | ||
}); | ||
}); | ||
it('should set bits corresponding to pulse outputs to 0', () => { | ||
assert.equal( | ||
getInitialDirtyFlags([ | ||
defaultizePin({ order: 0, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 1, type: PIN_TYPE.BOOLEAN }), | ||
defaultizePin({ order: 2, type: PIN_TYPE.NUMBER }), | ||
]), | ||
0b11111101 | ||
); | ||
assert.equal( | ||
getInitialDirtyFlags([ | ||
defaultizePin({ order: 0, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 1, type: PIN_TYPE.BOOLEAN }), | ||
defaultizePin({ order: 2, type: PIN_TYPE.NUMBER }), | ||
defaultizePin({ order: 3, type: PIN_TYPE.PULSE }), | ||
]), | ||
0b11101101 | ||
); | ||
assert.equal( | ||
getInitialDirtyFlags([ | ||
defaultizePin({ order: 0, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 1, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 2, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 3, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 4, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 5, type: PIN_TYPE.PULSE }), | ||
defaultizePin({ order: 6, type: PIN_TYPE.PULSE }), | ||
]), | ||
0b00000001 | ||
); | ||
it('correctly splits local patch paths', () => { | ||
assert.deepEqual(createPatchNames('@/qux-bar'), { | ||
owner: '', | ||
libName: '', | ||
patchName: 'qux_bar', | ||
}); | ||
}); | ||
it('should determine pin number by Pin`s `order` property', () => { | ||
assert.equal( | ||
getInitialDirtyFlags([ | ||
defaultizePin({ order: 2, type: PIN_TYPE.NUMBER }), | ||
defaultizePin({ order: 1, type: PIN_TYPE.BOOLEAN }), | ||
defaultizePin({ order: 0, type: PIN_TYPE.PULSE }), | ||
]), | ||
0b11111101 | ||
); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
620981
1790
39
9
+ Addedhm-def@0.3.2(transitive)
+ Addedsanctuary-def@0.14.0(transitive)
+ Addedsanctuary-type-classes@7.1.1(transitive)
+ Addedsanctuary-type-identifiers@2.0.1(transitive)
+ Addedxod-func-tools@0.19.2(transitive)
+ Addedxod-project@0.19.4(transitive)
- Removedhm-def@0.2.1(transitive)
- Removedramda@0.22.1(transitive)
- Removedsanctuary-def@0.9.0(transitive)
- Removedsanctuary-type-classes@2.0.1(transitive)
- Removedxod-func-tools@0.15.2(transitive)
- Removedxod-project@0.15.2(transitive)
Updatedhm-def@^0.3.2
Updatedsanctuary-def@^0.14.0
Updatedxod-func-tools@^0.19.0
Updatedxod-project@^0.19.0