xodio / xod

XOD IDE
https://xod.io
GNU Affero General Public License v3.0
888 stars 119 forks source link

Compiler Error When Using uart-usb Node #1775

Open jmwright opened 5 years ago

jmwright commented 5 years ago

This started as a discussion on the forum here.

Environment

What I'm Trying to Do

I'm trying to create a patch that passes string data over the USB port from an Arduino Uno to a PC via the USB-serial bridge.

Steps to reproduce

  1. Hook an Arduino Uno to any USB port on the PC.
  2. Create the following program: Screenshot_2019-05-08_17-20-35
  3. Click either the "Upload to Arduino" or "Upload and Debug" button.

Expected behavior

I expect compilation to succeed.

Actual behavior

I get a compilation error.

Console output

A compilation error is shown in the Compiler pane. Compiler Pane Output:

Begin compiling code for the board Arduino/Genuino Uno 📦

Using board 'uno' from platform in folder: /home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21
Using core 'arduino' from platform in folder: /home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21

Detecting libraries used...

/home/jwright/xod/__packages__/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.4-arduino2/bin/avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -I/home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21/cores/arduino -I/home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21/variants/standard /tmp/arduino-sketch-0EBF573157785F19E8DD4087B4EB4780/sketch/xod_1557350307225_sketch.ino.cpp -o /dev/null

Generating function prototypes...

/home/jwright/xod/__packages__/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.4-arduino2/bin/avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -I/home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21/cores/arduino -I/home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21/variants/standard /tmp/arduino-sketch-0EBF573157785F19E8DD4087B4EB4780/sketch/xod_1557350307225_sketch.ino.cpp -o /tmp/arduino-sketch-0EBF573157785F19E8DD4087B4EB4780/preproc/ctags_target_for_gcc_minus_e.cpp

/home/jwright/xod/__packages__/packages/builtin/tools/ctags/5.8-arduino11/ctags -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives /tmp/arduino-sketch-0EBF573157785F19E8DD4087B4EB4780/preproc/ctags_target_for_gcc_minus_e.cpp

Compiling sketch...

/home/jwright/xod/__packages__/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.4-arduino2/bin/avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10607 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -I/home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21/cores/arduino -I/home/jwright/xod/__packages__/packages/arduino/hardware/avr/1.6.21/variants/standard /tmp/arduino-sketch-0EBF573157785F19E8DD4087B4EB4780/sketch/xod_1557350307225_sketch.ino.cpp -o /tmp/arduino-sketch-0EBF573157785F19E8DD4087B4EB4780/sketch/xod_1557350307225_sketch.ino.cpp.o

/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1151:5: error: 'Serial_' does not name a type
     Serial_ _serial;
     ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1156:21: error: expected ')' before 'serial'
     UsbUart(Serial_ serial, long baud = 9600)
                     ^

/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1189:5: error: 'Serial_' does not name a type
     Serial_* toUsbSerial() {
     ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In member function 'virtual bool xod::xod__uart__uart_usb::UsbUart::available()':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1166:22: error: '_serial' was not declared in this scope
         return (bool)_serial.available();
                      ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In member function 'virtual bool xod::xod__uart__uart_usb::UsbUart::writeByte(uint8_t)':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1170:22: error: '_serial' was not declared in this scope
         return (bool)_serial.write(byte);
                      ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In member function 'virtual bool xod::xod__uart__uart_usb::UsbUart::readByte(uint8_t*)':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1174:20: error: '_serial' was not declared in this scope
         int data = _serial.read();
                    ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In member function 'virtual void xod::xod__uart__uart_usb::UsbUart::begin()':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1196:5: error: '_serial' was not declared in this scope
     _serial.begin(getBaudRate());
     ^

/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In member function 'virtual void xod::xod__uart__uart_usb::UsbUart::end()':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1200:5: error: '_serial' was not declared in this scope
     _serial.end();
     ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In member function 'virtual void xod::xod__uart__uart_usb::UsbUart::flush()':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1203:5: error: '_serial' was not declared in this scope
     _serial.flush();
     ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: At global scope:
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1211:26: error: 'Serial_' was not declared in this scope
 struct ChooseUartWrapper<Serial_> {
                          ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1211:33: error: template argument 1 is invalid
 struct ChooseUartWrapper<Serial_> {
                                 ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1216:49: error: 'SerialUSB' was not declared in this scope
     uint8_t mem[sizeof(ChooseUartWrapper<typeof SerialUSB>::UartT)];
                                                 ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1216:58: error: template argument 1 is invalid
     uint8_t mem[sizeof(ChooseUartWrapper<typeof SerialUSB>::UartT)];
                                                          ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1217:30: error: 'SerialUSB' was not declared in this scope
     ChooseUartWrapper<typeof SerialUSB>::UartT* uart;
                              ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1217:39: error: template argument 1 is invalid
     ChooseUartWrapper<typeof SerialUSB>::UartT* uart;
                                       ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1217:42: error: expected ';' at end of member declaration
     ChooseUartWrapper<typeof SerialUSB>::UartT* uart;
                                          ^

/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino: In function 'void xod::xod__uart__uart_usb::evaluate(xod::xod__uart__uart_usb::Context)':
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1316:35: error: 'struct xod::xod__uart__uart_usb::State' has no member named 'mem'
         state->uart = new (state->mem) ChooseUartWrapper<typeof SerialUSB>::UartT(SerialUSB, (uint32_t)getValue<input_BAUD>(ctx));
                                   ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1316:65: error: 'SerialUSB' was not declared in this scope
         state->uart = new (state->mem) ChooseUartWrapper<typeof SerialUSB>::UartT(SerialUSB, (uint32_t)getValue<input_BAUD>(ctx));
                                                                 ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1316:74: error: template argument 1 is invalid
         state->uart = new (state->mem) ChooseUartWrapper<typeof SerialUSB>::UartT(SerialUSB, (uint32_t)getValue<input_BAUD>(ctx));
                                                                          ^

/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1317:48: error: no matching function for call to 'emitValue(xod::xod__uart__uart_usb::ContextObject*&, int*&)'
         emitValue<output_UART>(ctx, state->uart);
                                                ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1317:48: note: candidate is:
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1291:33: note: template<class OutputT> void xod::xod__uart__uart_usb::emitValue(xod::xod__uart__uart_usb::Context, typename xod::xod__uart__uart_usb::ValueType<OutputT>::T)
 template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
                                 ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1291:33: note:   template argument deduction/substitution failed:
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1317:48: note:   cannot convert 'state->xod::xod__uart__uart_usb::State::uart' (type 'int*') to type 'xod::xod__uart__uart_usb::ValueType<xod::xod__uart__uart_usb::output_UART>::T {aka xod::Uart*}'
         emitValue<output_UART>(ctx, state->uart);
                                                ^
/tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch/xod_1557350307225_sketch.ino:1321:22: error: request for member 'begin' in '* state->xod::xod__uart__uart_usb::State::uart', which is of non-class type 'int'
         state->uart->begin();
                      ^

Error: exit status 1
Compilation failed.
Compilation failed
Command `/opt/XOD\ IDE/resources/arduino-cli --config-file=/tmp/arduino-cliBwDGgU/.cli-config.yml compile --fqbn arduino:avr:uno --verbose /tmp/xod_temp_sketchbook2poidD/xod_1557350307225_sketch` failed with code 1
The generated C++ code contains errors. It can be due to a bad node implementation or if your board is not compatible with XOD runtime code. The original compiler error message is above. Fix C++ errors to continue. If you believe it is a bug, report the problem to XOD developers.

Generated Arduino Code From Patch Above:

// The sketch is auto-generated with XOD (https://xod.io).
//
// You can compile and upload it to an Arduino-compatible board with
// Arduino IDE.
//
// Rough code overview:
//
// - Configuration section
// - STL shim
// - Immutable list classes and functions
// - XOD runtime environment
// - Native node implementation
// - Program graph definition
//
// Search for comments fenced with '====' and '----' to navigate through
// the major code blocks.

#include <Arduino.h>
#include <inttypes.h>

/*=============================================================================
 *
 *
 * Configuration
 *
 *
 =============================================================================*/

// Uncomment to turn on debug of the program
//#define XOD_DEBUG

// Uncomment to trace the program runtime in the Serial Monitor
//#define XOD_DEBUG_ENABLE_TRACE

// Uncomment to make possible simulation of the program
//#define XOD_SIMULATION

#ifdef XOD_SIMULATION
#include <WasmSerial.h>
#define XOD_DEBUG_SERIAL WasmSerial
#else
#define XOD_DEBUG_SERIAL DEBUG_SERIAL
#endif

/*=============================================================================
 *
 *
 * STL shim. Provides implementation for vital std::* constructs
 *
 *
 =============================================================================*/

namespace xod {
namespace std {

template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

template <class T>
typename remove_reference<T>::type&& move(T&& a) {
    return static_cast<typename remove_reference<T>::type&&>(a);
}

} // namespace std
} // namespace xod

/*=============================================================================
 *
 *
 * Basic XOD types
 *
 *
 =============================================================================*/
namespace xod {
#if __SIZEOF_FLOAT__ == 4
typedef float Number;
#else
typedef double Number;
#endif
typedef bool Logic;
typedef unsigned long TimeMs;
typedef uint8_t DirtyFlags;
} // namespace xod

/*=============================================================================
 *
 *
 * XOD-specific list/array implementations
 *
 *
 =============================================================================*/

#ifndef XOD_LIST_H
#define XOD_LIST_H

namespace xod {
namespace detail {

/*
 * Cursors are used internaly by iterators and list views. They are not exposed
 * directly to a list consumer.
 *
 * The base `Cursor` is an interface which provides the bare minimum of methods
 * to facilitate a single iteration pass.
 */
template<typename T> class Cursor {
  public:
    virtual ~Cursor() { }
    virtual bool isValid() const = 0;
    virtual bool value(T* out) const = 0;
    virtual void next() = 0;
};

template<typename T> class NilCursor : public Cursor<T> {
  public:
    virtual bool isValid() const { return false; }
    virtual bool value(T*) const { return false; }
    virtual void next() { }
};

} // namespace detail

/*
 * Iterator is an object used to iterate a list once.
 *
 * Users create new iterators by calling `someList.iterate()`.
 * Iterators are created on stack and are supposed to have a
 * short live, e.g. for a duration of `for` loop or node’s
 * `evaluate` function. Iterators can’t be copied.
 *
 * Implemented as a pimpl pattern wrapper over the cursor.
 * Once created for a cursor, an iterator owns that cursor
 * and will delete the cursor object once destroyed itself.
 */
template<typename T>
class Iterator {
  public:
    static Iterator<T> nil() {
        return Iterator<T>(new detail::NilCursor<T>());
    }

    Iterator(detail::Cursor<T>* cursor)
        : _cursor(cursor)
    { }

    ~Iterator() {
        if (_cursor)
            delete _cursor;
    }

    Iterator(const Iterator& that) = delete;
    Iterator& operator=(const Iterator& that) = delete;

    Iterator(Iterator&& it)
        : _cursor(it._cursor)
    {
        it._cursor = nullptr;
    }

    Iterator& operator=(Iterator&& it) {
        auto tmp = it._cursor;
        it._cursor = _cursor;
        _cursor = tmp;
        return *this;
    }

    operator bool() const { return _cursor->isValid(); }

    bool value(T* out) const {
        return _cursor->value(out);
    }

    T operator*() const {
        T out;
        _cursor->value(&out);
        return out;
    }

    Iterator& operator++() {
        _cursor->next();
        return *this;
    }

  private:
    detail::Cursor<T>* _cursor;
};

/*
 * An interface for a list view. A particular list view provides a new
 * kind of iteration over existing data. This way we can use list slices,
 * list concatenations, list rotations, etc without introducing new data
 * buffers. We just change the way already existing data is iterated.
 *
 * ListView is not exposed to a list user directly, it is used internally
 * by the List class. However, deriving a new ListView is necessary if you
 * make a new list/string processing node.
 */
template<typename T> class ListView {
  public:
    virtual Iterator<T> iterate() const = 0;
};

/*
 * The list as it seen by data consumers. Have a single method `iterate`
 * to create a new iterator.
 *
 * Implemented as pimpl pattern wrapper over a list view. Takes pointer
 * to a list view in constructor and expects the view will be alive for
 * the whole life time of the list.
 */
template<typename T> class List {
  public:
    constexpr List()
        : _view(nullptr)
    { }

    List(const ListView<T>* view)
        : _view(view)
    { }

    Iterator<T> iterate() const {
        return _view ? _view->iterate() : Iterator<T>::nil();
    }

    // pre 0.15.0 backward compatibility
    List* operator->() __attribute__ ((deprecated)) { return this; }
    const List* operator->() const __attribute__ ((deprecated)) { return this; }

  private:
    const ListView<T>* _view;
};

/*
 * A list view over an old good plain C array.
 *
 * Expects the array will be alive for the whole life time of the
 * view.
 */
template<typename T> class PlainListView : public ListView<T> {
  public:
    class Cursor : public detail::Cursor<T> {
      public:
        Cursor(const PlainListView* owner)
            : _owner(owner)
            , _idx(0)
        { }

        bool isValid() const override {
            return _idx < _owner->_len;
        }

        bool value(T* out) const override {
            if (!isValid())
                return false;
            *out = _owner->_data[_idx];
            return true;
        }

        void next() override { ++_idx; }

      private:
        const PlainListView* _owner;
        size_t _idx;
    };

  public:
    PlainListView(const T* data, size_t len)
        : _data(data)
        , _len(len)
    { }

    virtual Iterator<T> iterate() const override {
        return Iterator<T>(new Cursor(this));
    }

  private:
    friend class Cursor;
    const T* _data;
    size_t _len;
};

/*
 * A list view over a null-terminated C-String.
 *
 * Expects the char buffer will be alive for the whole life time of the view.
 * You can use string literals as a buffer, since they are persistent for
 * the program execution time.
 */
class CStringView : public ListView<char> {
  public:
    class Cursor : public detail::Cursor<char> {
      public:
        Cursor(const char* str)
            : _ptr(str)
        { }

        bool isValid() const override {
            return (bool)*_ptr;
        }

        bool value(char* out) const override {
            *out = *_ptr;
            return (bool)*_ptr;
        }

        void next() override { ++_ptr; }

      private:
        const char* _ptr;
    };

  public:
    CStringView(const char* str = nullptr)
        : _str(str)
    { }

    CStringView& operator=(const CStringView& rhs) {
        _str = rhs._str;
        return *this;
    }

    virtual Iterator<char> iterate() const override {
        return _str ? Iterator<char>(new Cursor(_str)) : Iterator<char>::nil();
    }

  private:
    friend class Cursor;
    const char* _str;
};

/*
 * A list view over two other lists (Left and Right) which first iterates the
 * left one, and when exhausted, iterates the right one.
 *
 * Expects both Left and Right to be alive for the whole view life time.
 */
template<typename T> class ConcatListView : public ListView<T> {
  public:
    class Cursor : public detail::Cursor<T> {
      public:
        Cursor(Iterator<T>&& left, Iterator<T>&& right)
            : _left(std::move(left))
            , _right(std::move(right))
        { }

        bool isValid() const override {
            return _left || _right;
        }

        bool value(T* out) const override {
            return _left.value(out) || _right.value(out);
        }

        void next() override {
            _left ? ++_left : ++_right;
        }

      private:
        Iterator<T> _left;
        Iterator<T> _right;
    };

  public:
    ConcatListView() { }

    ConcatListView(List<T> left, List<T> right)
        : _left(left)
        , _right(right)
    { }

    ConcatListView& operator=(const ConcatListView& rhs) {
        _left = rhs._left;
        _right = rhs._right;
        return *this;
    }

    virtual Iterator<T> iterate() const override {
        return Iterator<T>(new Cursor(_left.iterate(), _right.iterate()));
    }

  private:
    friend class Cursor;
    List<T> _left;
    List<T> _right;
};

//----------------------------------------------------------------------------
// Text string helpers
//----------------------------------------------------------------------------

using XString = List<char>;

/*
 * List and list view in a single pack. An utility used to define constant
 * string literals in XOD.
 */
class XStringCString : public XString {
  public:
    XStringCString(const char* str)
        : XString(&_view)
        , _view(str)
    { }

  private:
    CStringView _view;
};

} // namespace xod

#endif

/*=============================================================================
 *
 *
 * Functions to work with memory
 *
 *
 =============================================================================*/
#ifdef __AVR__
// Placement `new` for Arduino
void* operator new(size_t, void* ptr) {
    return ptr;
}
#endif

/*=============================================================================
 *
 *
 * UART Classes, that wraps Serials
 *
 *
 =============================================================================*/

class HardwareSerial;
class SoftwareSerial;

namespace xod {

class Uart {
  private:
    long _baud;

  protected:
    bool _started = false;

  public:
    Uart(long baud) {
        _baud = baud;
    }

    virtual void begin() = 0;

    virtual void end() = 0;

    virtual void flush() = 0;

    virtual bool available() = 0;

    virtual bool writeByte(uint8_t) = 0;

    virtual bool readByte(uint8_t*) = 0;

    virtual SoftwareSerial* toSoftwareSerial() {
      return nullptr;
    }

    virtual HardwareSerial* toHardwareSerial() {
      return nullptr;
    }

    void changeBaudRate(long baud) {
      _baud = baud;
      if (_started) {
        end();
        begin();
      }
    }

    long getBaudRate() const {
      return _baud;
    }

    Stream* toStream() {
      Stream* stream = (Stream*) toHardwareSerial();
      if (stream) return stream;
      return (Stream*) toSoftwareSerial();
    }
};

class HardwareUart : public Uart {
  private:
    HardwareSerial* _serial;

  public:
    HardwareUart(HardwareSerial& hserial, uint32_t baud = 115200) : Uart(baud) {
      _serial = &hserial;
    }

    void begin();
    void end();
    void flush();

    bool available() {
      return (bool) _serial->available();
    }

    bool writeByte(uint8_t byte) {
      return (bool) _serial->write(byte);
    }

    bool readByte(uint8_t* out) {
      int data = _serial->read();
      if (data == -1) return false;
      *out = data;
      return true;
    }

    HardwareSerial* toHardwareSerial() {
      return _serial;
    }
};

void HardwareUart::begin() {
  _started = true;
  _serial->begin(getBaudRate());
};
void HardwareUart::end() {
  _started = false;
  _serial->end();
};
void HardwareUart::flush() {
  _serial->flush();
};

} // namespace xod

/*=============================================================================
 *
 *
 * Basic algorithms for XOD lists
 *
 *
 =============================================================================*/

#ifndef XOD_LIST_FUNCS_H
#define XOD_LIST_FUNCS_H

namespace xod {

/*
 * Folds a list from left. Also known as "reduce".
 */
template<typename T, typename TR>
TR foldl(List<T> xs, TR (*func)(TR, T), TR acc) {
    for (auto it = xs.iterate(); it; ++it)
        acc = func(acc, *it);
    return acc;
}

template<typename T> size_t lengthReducer(size_t len, T) {
    return len + 1;
}

/*
 * Computes length of a list.
 */
template<typename T> size_t length(List<T> xs) {
    return foldl(xs, lengthReducer<T>, (size_t)0);
}

template<typename T> T* dumpReducer(T* buff, T x) {
    *buff = x;
    return buff + 1;
}

/*
 * Copies a list content into a memory buffer.
 *
 * It is expected that `outBuff` has enough size to fit all the data.
 */
template<typename T> size_t dump(List<T> xs, T* outBuff) {
    T* buffEnd = foldl(xs, dumpReducer, outBuff);
    return buffEnd - outBuff;
}

/*
 * Compares two lists.
 */
template<typename T> bool equal(List<T> lhs, List<T> rhs) {
    auto lhsIt = lhs.iterate();
    auto rhsIt = rhs.iterate();

    for (; lhsIt && rhsIt; ++lhsIt, ++rhsIt) {
        if (*lhsIt != *rhsIt) return false;
    }

    return !lhsIt && !rhsIt;
}

template<typename T> bool operator == (List<T> lhs, List<T> rhs) {
  return equal(lhs, rhs);
}

} // namespace xod

#endif

/*=============================================================================
 *
 *
 * Format Numbers
 *
 *
 =============================================================================*/

/**
 * Provide `formatNumber` cross-platform number to string converter function.
 *
 * Taken from here:
 * https://github.com/client9/stringencoders/blob/master/src/modp_numtoa.c
 * Original function name: `modp_dtoa2`.
 *
 * Modified:
 * - `isnan` instead of tricky comparing and return "NaN"
 * - handle Infinity values and return "Inf" or "-Inf"
 * - return `OVF` and `-OVF` for numbers bigger than max possible, instead of using `sprintf`
 * - use `Number` instead of double
 * - if negative number rounds to zero, return just "0" instead of "-0"
 *
 * This is a replacement of `dtostrf`.
 */

#ifndef XOD_FORMAT_NUMBER_H
#define XOD_FORMAT_NUMBER_H

namespace xod {

/**
 * Powers of 10
 * 10^0 to 10^9
 */
static const Number powers_of_10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
    10000000, 100000000, 1000000000 };

static void strreverse(char* begin, char* end) {
    char aux;
    while (end > begin)
        aux = *end, *end-- = *begin, *begin++ = aux;
};

size_t formatNumber(Number value, int prec, char* str) {
    if (isnan(value)) {
        strcpy(str, "NaN");
        return (size_t)3;
    }

    if (isinf(value)) {
        bool isNegative = value < 0;
        strcpy(str, isNegative ? "-Inf" : "Inf");
        return (size_t)isNegative ? 4 : 3;
    }

    /* if input is larger than thres_max return "OVF" */
    const Number thres_max = (Number)(0x7FFFFFFF);

    Number diff = 0.0;
    char* wstr = str;

    if (prec < 0) {
        prec = 0;
    } else if (prec > 9) {
        /* precision of >= 10 can lead to overflow errors */
        prec = 9;
    }

    /* we'll work in positive values and deal with the
       negative sign issue later */
    int neg = 0;
    if (value < 0) {
        neg = 1;
        value = -value;
    }

    uint32_t whole = (uint32_t)value;
    Number tmp = (value - whole) * powers_of_10[prec];
    uint32_t frac = (uint32_t)(tmp);
    diff = tmp - frac;

    if (diff > 0.5) {
        ++frac;
        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && prec > 0 && (frac & 1)) {
        /* if halfway, round up if odd, OR
           if last digit is 0.  That last part is strange */
        ++frac;
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && prec == 0 && (whole & 1)) {
        ++frac;
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    }

    if (value > thres_max) {
        if (neg) {
            strcpy(str, "-OVF");
            return (size_t)4;
        }
        strcpy(str, "OVF");
        return (size_t)3;
    }

    int has_decimal = 0;
    int count = prec;
    bool notzero = frac > 0;

    while (count > 0) {
        --count;
        *wstr++ = (char)(48 + (frac % 10));
        frac /= 10;
        has_decimal = 1;
    }

    if (frac > 0) {
        ++whole;
    }

    /* add decimal */
    if (has_decimal) {
        *wstr++ = '.';
    }

    notzero = notzero || whole > 0;

    /* do whole part
     * Take care of sign conversion
     * Number is reversed.
     */
    do
        *wstr++ = (char)(48 + (whole % 10));
    while (whole /= 10);

    if (neg && notzero) {
        *wstr++ = '-';
    }
    *wstr = '\0';
    strreverse(str, wstr - 1);
    return (size_t)(wstr - str);
}

} // namespace xod
#endif

/*=============================================================================
 *
 *
 * Runtime
 *
 *
 =============================================================================*/

//----------------------------------------------------------------------------
// Debug routines
//----------------------------------------------------------------------------
// #ifndef DEBUG_SERIAL
#if defined(XOD_DEBUG) && !defined(DEBUG_SERIAL)
#  define DEBUG_SERIAL Serial
#endif

#if defined(XOD_DEBUG) && defined(XOD_DEBUG_ENABLE_TRACE)
#  define XOD_TRACE(x)      { DEBUG_SERIAL.print(x); DEBUG_SERIAL.flush(); }
#  define XOD_TRACE_LN(x)   { DEBUG_SERIAL.println(x); DEBUG_SERIAL.flush(); }
#  define XOD_TRACE_F(x)    XOD_TRACE(F(x))
#  define XOD_TRACE_FLN(x)  XOD_TRACE_LN(F(x))
#else
#  define XOD_TRACE(x)
#  define XOD_TRACE_LN(x)
#  define XOD_TRACE_F(x)
#  define XOD_TRACE_FLN(x)
#endif

//----------------------------------------------------------------------------
// PGM space utilities
//----------------------------------------------------------------------------
#define pgm_read_nodeid(address) (pgm_read_word(address))

/*
 * Workaround for bugs:
 * https://github.com/arduino/ArduinoCore-sam/pull/43
 * https://github.com/arduino/ArduinoCore-samd/pull/253
 * Remove after the PRs merge
 */
#if !defined(ARDUINO_ARCH_AVR) && defined(pgm_read_ptr)
#  undef pgm_read_ptr
#  define pgm_read_ptr(addr) (*(const void **)(addr))
#endif

namespace xod {
//----------------------------------------------------------------------------
// Global variables
//----------------------------------------------------------------------------

TimeMs g_transactionTime;
bool g_isSettingUp;

//----------------------------------------------------------------------------
// Metaprogramming utilities
//----------------------------------------------------------------------------

template<typename T> struct always_false {
    enum { value = 0 };
};

//----------------------------------------------------------------------------
// Forward declarations
//----------------------------------------------------------------------------

TimeMs transactionTime();
void runTransaction();

//----------------------------------------------------------------------------
// Engine (private API)
//----------------------------------------------------------------------------

namespace detail {

template<typename NodeT>
bool isTimedOut(const NodeT* node) {
    TimeMs t = node->timeoutAt;
    // TODO: deal with uint32 overflow
    return t && t < transactionTime();
}

// Marks timed out node dirty. Do not reset timeoutAt here to give
// a chance for a node to get a reasonable result from `isTimedOut`
// later during its `evaluate`
template<typename NodeT>
void checkTriggerTimeout(NodeT* node) {
    node->isNodeDirty |= isTimedOut(node);
}

template<typename NodeT>
void clearTimeout(NodeT* node) {
    node->timeoutAt = 0;
}

template<typename NodeT>
void clearStaleTimeout(NodeT* node) {
    if (isTimedOut(node))
        clearTimeout(node);
}

} // namespace detail

//----------------------------------------------------------------------------
// Public API (can be used by native nodes’ `evaluate` functions)
//----------------------------------------------------------------------------

TimeMs transactionTime() {
    return g_transactionTime;
}

bool isSettingUp() {
    return g_isSettingUp;
}

template<typename ContextT>
void setTimeout(ContextT* ctx, TimeMs timeout) {
    ctx->_node->timeoutAt = transactionTime() + timeout;
}

template<typename ContextT>
void clearTimeout(ContextT* ctx) {
    detail::clearTimeout(ctx->_node);
}

template<typename ContextT>
bool isTimedOut(const ContextT* ctx) {
    return detail::isTimedOut(ctx->_node);
}

bool isValidDigitalPort(uint8_t port) {
#if defined(__AVR__) && defined(NUM_DIGITAL_PINS)
    return port < NUM_DIGITAL_PINS;
#else
    return true;
#endif
}

bool isValidAnalogPort(uint8_t port) {
#if defined(__AVR__) && defined(NUM_ANALOG_INPUTS)
    return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
    return true;
#endif
}

} // namespace xod

//----------------------------------------------------------------------------
// Entry point
//----------------------------------------------------------------------------
void setup() {
    // FIXME: looks like there is a rounding bug. Waiting for 100ms fights it
    delay(100);

#if defined(XOD_DEBUG) || defined(XOD_SIMULATION)
    XOD_DEBUG_SERIAL.begin(115200);
    XOD_DEBUG_SERIAL.setTimeout(10);
#endif
    XOD_TRACE_FLN("\n\nProgram started");

    xod::g_isSettingUp = true;
    xod::runTransaction();
    xod::g_isSettingUp = false;
}

void loop() {
    xod::runTransaction();
}

/*=============================================================================
 *
 *
 * Native node implementations
 *
 *
 =============================================================================*/

namespace xod {

//-----------------------------------------------------------------------------
// xod/uart/uart implementation
//-----------------------------------------------------------------------------
namespace xod__uart__uart {

using Type = Uart*;
struct State {
    uint8_t mem[sizeof(HardwareUart)];
    HardwareUart* uart;
};

// clang-format off

struct Node {
    State state;
    xod__uart__uart::Type output_UART;
    Logic output_DONE;

    union {
        struct {
            bool isOutputDirty_UART : 1;
            bool isOutputDirty_DONE : 1;
            bool isNodeDirty : 1;
        };

        DirtyFlags dirtyFlags;
    };
};

struct input_BAUD { };
struct input_INIT { };
struct output_UART { };
struct output_DONE { };

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_BAUD> { using T = Number; };
template<> struct ValueType<input_INIT> { using T = Logic; };
template<> struct ValueType<output_UART> { using T = xod__uart__uart::Type; };
template<> struct ValueType<output_DONE> { using T = Logic; };

struct ContextObject {
    Node* _node;

    Number _input_BAUD;
    Logic _input_INIT;

    bool _isInputDirty_INIT;
};

using Context = ContextObject*;

template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            " input_BAUD input_INIT" \
            " output_UART output_DONE");
}

template<> Number getValue<input_BAUD>(Context ctx) {
    return ctx->_input_BAUD;
}
template<> Logic getValue<input_INIT>(Context ctx) {
    return ctx->_input_INIT;
}
template<> xod__uart__uart::Type getValue<output_UART>(Context ctx) {
    return ctx->_node->output_UART;
}
template<> Logic getValue<output_DONE>(Context ctx) {
    return ctx->_node->output_DONE;
}

template<typename InputT> bool isInputDirty(Context ctx) {
    static_assert(always_false<InputT>::value,
            "Invalid input descriptor. Expected one of:" \
            " input_INIT");
    return false;
}

template<> bool isInputDirty<input_INIT>(Context ctx) {
    return ctx->_isInputDirty_INIT;
}

template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
    static_assert(always_false<OutputT>::value,
            "Invalid output descriptor. Expected one of:" \
            " output_UART output_DONE");
}

template<> void emitValue<output_UART>(Context ctx, xod__uart__uart::Type val) {
    ctx->_node->output_UART = val;
    ctx->_node->isOutputDirty_UART = true;
}
template<> void emitValue<output_DONE>(Context ctx, Logic val) {
    ctx->_node->output_DONE = val;
    ctx->_node->isOutputDirty_DONE = true;
}

State* getState(Context ctx) {
    return &ctx->_node->state;
}

// clang-format on

void evaluate(Context ctx) {
    auto state = getState(ctx);

    if (isSettingUp()) {
        auto baud = (uint32_t)getValue<input_BAUD>(ctx);
#ifdef SERIAL_PORT_HARDWARE_OPEN
        auto serial = SERIAL_PORT_HARDWARE_OPEN;
#else
        auto serial = SERIAL_PORT_HARDWARE;
#endif
        state->uart = new (state->mem) HardwareUart(serial, baud);
        emitValue<output_UART>(ctx, state->uart);
    }

    if (isInputDirty<input_INIT>(ctx)) {
        state->uart->begin();
        emitValue<output_DONE>(ctx, 1);
    }
}

} // namespace xod__uart__uart

//-----------------------------------------------------------------------------
// xod/core/boot implementation
//-----------------------------------------------------------------------------
namespace xod__core__boot {

struct State {
};

struct Node {
    State state;
    Logic output_BOOT;

    union {
        struct {
            bool isOutputDirty_BOOT : 1;
            bool isNodeDirty : 1;
        };

        DirtyFlags dirtyFlags;
    };
};

struct output_BOOT { };

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<output_BOOT> { using T = Logic; };

struct ContextObject {
    Node* _node;

};

using Context = ContextObject*;

template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            "" \
            " output_BOOT");
}

template<> Logic getValue<output_BOOT>(Context ctx) {
    return ctx->_node->output_BOOT;
}

template<typename InputT> bool isInputDirty(Context ctx) {
    static_assert(always_false<InputT>::value,
            "Invalid input descriptor. Expected one of:" \
            "");
    return false;
}

template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
    static_assert(always_false<OutputT>::value,
            "Invalid output descriptor. Expected one of:" \
            " output_BOOT");
}

template<> void emitValue<output_BOOT>(Context ctx, Logic val) {
    ctx->_node->output_BOOT = val;
    ctx->_node->isOutputDirty_BOOT = true;
}

State* getState(Context ctx) {
    return &ctx->_node->state;
}

void evaluate(Context ctx) {
    emitValue<output_BOOT>(ctx, 1);
}

} // namespace xod__core__boot

//-----------------------------------------------------------------------------
// xod/uart/uart-usb implementation
//-----------------------------------------------------------------------------
namespace xod__uart__uart_usb {

class UsbUart : public Uart {
private:
    Serial_ _serial;
    uint8_t _rx;
    uint8_t _tx;

public:
    UsbUart(Serial_ serial, long baud = 9600)
        : Uart(baud) {
        _serial = serial;
    }

    void begin();
    void end();
    void flush();

    bool available() {
        return (bool)_serial.available();
    }

    bool writeByte(uint8_t byte) {
        return (bool)_serial.write(byte);
    }

    bool readByte(uint8_t* out) {
        int data = _serial.read();
        if (data == -1)
            return false;
        *out = data;
        return true;
    }

    uint8_t getRX() {
        return _rx;
    }

    uint8_t getTX() {
        return _tx;
    }

    Serial_* toUsbSerial() {
        return &_serial;
    }
};

void UsbUart::begin() {
    _started = true;
    _serial.begin(getBaudRate());
};
void UsbUart::end() {
    _started = false;
    _serial.end();
};
void UsbUart::flush() {
    _serial.flush();
}

template <typename T>
struct ChooseUartWrapper {
    using UartT = HardwareUart;
};
template <>
struct ChooseUartWrapper<Serial_> {
    using UartT = UsbUart;
};

struct State {
    uint8_t mem[sizeof(ChooseUartWrapper<typeof SerialUSB>::UartT)];
    ChooseUartWrapper<typeof SerialUSB>::UartT* uart;
};

// clang-format off

struct Node {
    State state;
    xod__uart__uart::Type output_UART;
    Logic output_DONE;

    union {
        struct {
            bool isOutputDirty_UART : 1;
            bool isOutputDirty_DONE : 1;
            bool isNodeDirty : 1;
        };

        DirtyFlags dirtyFlags;
    };
};

struct input_BAUD { };
struct input_INIT { };
struct output_UART { };
struct output_DONE { };

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_BAUD> { using T = Number; };
template<> struct ValueType<input_INIT> { using T = Logic; };
template<> struct ValueType<output_UART> { using T = xod__uart__uart::Type; };
template<> struct ValueType<output_DONE> { using T = Logic; };

struct ContextObject {
    Node* _node;

    Number _input_BAUD;
    Logic _input_INIT;

    bool _isInputDirty_INIT;
};

using Context = ContextObject*;

template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            " input_BAUD input_INIT" \
            " output_UART output_DONE");
}

template<> Number getValue<input_BAUD>(Context ctx) {
    return ctx->_input_BAUD;
}
template<> Logic getValue<input_INIT>(Context ctx) {
    return ctx->_input_INIT;
}
template<> xod__uart__uart::Type getValue<output_UART>(Context ctx) {
    return ctx->_node->output_UART;
}
template<> Logic getValue<output_DONE>(Context ctx) {
    return ctx->_node->output_DONE;
}

template<typename InputT> bool isInputDirty(Context ctx) {
    static_assert(always_false<InputT>::value,
            "Invalid input descriptor. Expected one of:" \
            " input_INIT");
    return false;
}

template<> bool isInputDirty<input_INIT>(Context ctx) {
    return ctx->_isInputDirty_INIT;
}

template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
    static_assert(always_false<OutputT>::value,
            "Invalid output descriptor. Expected one of:" \
            " output_UART output_DONE");
}

template<> void emitValue<output_UART>(Context ctx, xod__uart__uart::Type val) {
    ctx->_node->output_UART = val;
    ctx->_node->isOutputDirty_UART = true;
}
template<> void emitValue<output_DONE>(Context ctx, Logic val) {
    ctx->_node->output_DONE = val;
    ctx->_node->isOutputDirty_DONE = true;
}

State* getState(Context ctx) {
    return &ctx->_node->state;
}

// clang-format on

void evaluate(Context ctx) {
    auto state = getState(ctx);

    if (isSettingUp()) {
        state->uart = new (state->mem) ChooseUartWrapper<typeof SerialUSB>::UartT(SerialUSB, (uint32_t)getValue<input_BAUD>(ctx));
        emitValue<output_UART>(ctx, state->uart);
    }

    if (isInputDirty<input_INIT>(ctx)) {
        state->uart->begin();
        emitValue<output_DONE>(ctx, 1);
    }
}

} // namespace xod__uart__uart_usb

//-----------------------------------------------------------------------------
// xod/core/flip-flop implementation
//-----------------------------------------------------------------------------
namespace xod__core__flip_flop {

struct State {
};

struct Node {
    State state;
    Logic output_MEM;

    union {
        struct {
            bool isOutputDirty_MEM : 1;
            bool isNodeDirty : 1;
        };

        DirtyFlags dirtyFlags;
    };
};

struct input_SET { };
struct input_TGL { };
struct input_RST { };
struct output_MEM { };

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_SET> { using T = Logic; };
template<> struct ValueType<input_TGL> { using T = Logic; };
template<> struct ValueType<input_RST> { using T = Logic; };
template<> struct ValueType<output_MEM> { using T = Logic; };

struct ContextObject {
    Node* _node;

    Logic _input_SET;
    Logic _input_TGL;
    Logic _input_RST;

    bool _isInputDirty_SET;
    bool _isInputDirty_TGL;
    bool _isInputDirty_RST;
};

using Context = ContextObject*;

template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            " input_SET input_TGL input_RST" \
            " output_MEM");
}

template<> Logic getValue<input_SET>(Context ctx) {
    return ctx->_input_SET;
}
template<> Logic getValue<input_TGL>(Context ctx) {
    return ctx->_input_TGL;
}
template<> Logic getValue<input_RST>(Context ctx) {
    return ctx->_input_RST;
}
template<> Logic getValue<output_MEM>(Context ctx) {
    return ctx->_node->output_MEM;
}

template<typename InputT> bool isInputDirty(Context ctx) {
    static_assert(always_false<InputT>::value,
            "Invalid input descriptor. Expected one of:" \
            " input_SET input_TGL input_RST");
    return false;
}

template<> bool isInputDirty<input_SET>(Context ctx) {
    return ctx->_isInputDirty_SET;
}
template<> bool isInputDirty<input_TGL>(Context ctx) {
    return ctx->_isInputDirty_TGL;
}
template<> bool isInputDirty<input_RST>(Context ctx) {
    return ctx->_isInputDirty_RST;
}

template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
    static_assert(always_false<OutputT>::value,
            "Invalid output descriptor. Expected one of:" \
            " output_MEM");
}

template<> void emitValue<output_MEM>(Context ctx, Logic val) {
    ctx->_node->output_MEM = val;
    ctx->_node->isOutputDirty_MEM = true;
}

State* getState(Context ctx) {
    return &ctx->_node->state;
}

void evaluate(Context ctx) {
    bool oldState = getValue<output_MEM>(ctx);
    bool newState = oldState;

    if (isInputDirty<input_RST>(ctx)) {
        newState = false;
    } else if (isInputDirty<input_SET>(ctx)) {
        newState = true;
    } else if (isInputDirty<input_TGL>(ctx)) {
        newState = !oldState;
    }

    if (newState == oldState)
        return;

    emitValue<output_MEM>(ctx, newState);
}

} // namespace xod__core__flip_flop

//-----------------------------------------------------------------------------
// xod/core/clock implementation
//-----------------------------------------------------------------------------
namespace xod__core__clock {

struct State {
  TimeMs nextTrig;
};

struct Node {
    State state;
    TimeMs timeoutAt;
    Logic output_TICK;

    union {
        struct {
            bool isOutputDirty_TICK : 1;
            bool isNodeDirty : 1;
        };

        DirtyFlags dirtyFlags;
    };
};

struct input_EN { };
struct input_IVAL { };
struct input_RST { };
struct output_TICK { };

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_EN> { using T = Logic; };
template<> struct ValueType<input_IVAL> { using T = Number; };
template<> struct ValueType<input_RST> { using T = Logic; };
template<> struct ValueType<output_TICK> { using T = Logic; };

struct ContextObject {
    Node* _node;

    Logic _input_EN;
    Number _input_IVAL;
    Logic _input_RST;

    bool _isInputDirty_EN;
    bool _isInputDirty_RST;
};

using Context = ContextObject*;

template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            " input_EN input_IVAL input_RST" \
            " output_TICK");
}

template<> Logic getValue<input_EN>(Context ctx) {
    return ctx->_input_EN;
}
template<> Number getValue<input_IVAL>(Context ctx) {
    return ctx->_input_IVAL;
}
template<> Logic getValue<input_RST>(Context ctx) {
    return ctx->_input_RST;
}
template<> Logic getValue<output_TICK>(Context ctx) {
    return ctx->_node->output_TICK;
}

template<typename InputT> bool isInputDirty(Context ctx) {
    static_assert(always_false<InputT>::value,
            "Invalid input descriptor. Expected one of:" \
            " input_EN input_RST");
    return false;
}

template<> bool isInputDirty<input_EN>(Context ctx) {
    return ctx->_isInputDirty_EN;
}
template<> bool isInputDirty<input_RST>(Context ctx) {
    return ctx->_isInputDirty_RST;
}

template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
    static_assert(always_false<OutputT>::value,
            "Invalid output descriptor. Expected one of:" \
            " output_TICK");
}

template<> void emitValue<output_TICK>(Context ctx, Logic val) {
    ctx->_node->output_TICK = val;
    ctx->_node->isOutputDirty_TICK = true;
}

State* getState(Context ctx) {
    return &ctx->_node->state;
}

void evaluate(Context ctx) {
    State* state = getState(ctx);
    TimeMs tNow = transactionTime();
    auto ival = getValue<input_IVAL>(ctx);
    if (ival < 0) ival = 0;
    TimeMs dt = ival * 1000;
    TimeMs tNext = tNow + dt;

    auto isEnabled = getValue<input_EN>(ctx);
    auto isRstDirty = isInputDirty<input_RST>(ctx);

    if (isTimedOut(ctx) && isEnabled && !isRstDirty) {
        emitValue<output_TICK>(ctx, 1);
        state->nextTrig = tNext;
        setTimeout(ctx, dt);
    }

    if (isRstDirty || isInputDirty<input_EN>(ctx)) {
        // Handle enable/disable/reset
        if (!isEnabled) {
            // Disable timeout loop on explicit false on EN
            state->nextTrig = 0;
            clearTimeout(ctx);
        } else if (state->nextTrig < tNow || state->nextTrig > tNext) {
            // Start timeout from scratch
            state->nextTrig = tNext;
            setTimeout(ctx, dt);
        }
    }
}

} // namespace xod__core__clock

//-----------------------------------------------------------------------------
// xod/uart/print implementation
//-----------------------------------------------------------------------------
namespace xod__uart__print {

struct State {
};

// clang-format off

struct Node {
    State state;
    Logic output_DONE;
    Logic output_ERR;

    union {
        struct {
            bool isOutputDirty_DONE : 1;
            bool isOutputDirty_ERR : 1;
            bool isNodeDirty : 1;
        };

        DirtyFlags dirtyFlags;
    };
};

struct input_UART { };
struct input_DATA { };
struct input_SEND { };
struct output_DONE { };
struct output_ERR { };

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_UART> { using T = xod__uart__uart::Type; };
template<> struct ValueType<input_DATA> { using T = XString; };
template<> struct ValueType<input_SEND> { using T = Logic; };
template<> struct ValueType<output_DONE> { using T = Logic; };
template<> struct ValueType<output_ERR> { using T = Logic; };

struct ContextObject {
    Node* _node;

    xod__uart__uart::Type _input_UART;
    XString _input_DATA;
    Logic _input_SEND;

    bool _isInputDirty_SEND;
};

using Context = ContextObject*;

template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            " input_UART input_DATA input_SEND" \
            " output_DONE output_ERR");
}

template<> xod__uart__uart::Type getValue<input_UART>(Context ctx) {
    return ctx->_input_UART;
}
template<> XString getValue<input_DATA>(Context ctx) {
    return ctx->_input_DATA;
}
template<> Logic getValue<input_SEND>(Context ctx) {
    return ctx->_input_SEND;
}
template<> Logic getValue<output_DONE>(Context ctx) {
    return ctx->_node->output_DONE;
}
template<> Logic getValue<output_ERR>(Context ctx) {
    return ctx->_node->output_ERR;
}

template<typename InputT> bool isInputDirty(Context ctx) {
    static_assert(always_false<InputT>::value,
            "Invalid input descriptor. Expected one of:" \
            " input_SEND");
    return false;
}

template<> bool isInputDirty<input_SEND>(Context ctx) {
    return ctx->_isInputDirty_SEND;
}

template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
    static_assert(always_false<OutputT>::value,
            "Invalid output descriptor. Expected one of:" \
            " output_DONE output_ERR");
}

template<> void emitValue<output_DONE>(Context ctx, Logic val) {
    ctx->_node->output_DONE = val;
    ctx->_node->isOutputDirty_DONE = true;
}
template<> void emitValue<output_ERR>(Context ctx, Logic val) {
    ctx->_node->output_ERR = val;
    ctx->_node->isOutputDirty_ERR = true;
}

State* getState(Context ctx) {
    return &ctx->_node->state;
}

// clang-format on

void emitErr(Context ctx) {
    emitValue<output_ERR>(ctx, 1);
}

void evaluate(Context ctx) {
    if (!isInputDirty<input_SEND>(ctx))
        return;

    auto uart = getValue<input_UART>(ctx);
    auto data = getValue<input_DATA>(ctx);

    for (auto it = data.iterate(); it; ++it) {
        bool err = !(uart->writeByte((char)*it));
        if (err)
            return emitErr(ctx);
    }
    if (!uart->writeByte('\r'))
        return emitErr(ctx);
    if (!uart->writeByte('\n'))
        return emitErr(ctx);
    uart->flush();
    emitValue<output_DONE>(ctx, 1);
}

} // namespace xod__uart__print

} // namespace xod

/*=============================================================================
 *
 *
 * Main loop components
 *
 *
 =============================================================================*/

namespace xod {

// Define/allocate persistent storages (state, timeout, output data) for all nodes
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

constexpr Logic node_0_output_BOOT = false;

static XStringCString node_1_output_VAL = XStringCString("Hello World");

constexpr Number node_2_output_VAL = 115200;

constexpr Number node_3_output_VAL = 5;

constexpr xod__uart__uart::Type node_4_output_UART = { /* xod/uart/uart */ };
constexpr Logic node_4_output_DONE = false;

constexpr Logic node_5_output_MEM = false;

constexpr Logic node_6_output_TICK = false;

constexpr Logic node_7_output_DONE = false;
constexpr Logic node_7_output_ERR = false;

#pragma GCC diagnostic pop

xod__core__boot::Node node_0 = {
    xod__core__boot::State(), // state default
    node_0_output_BOOT, // output BOOT default
    false, // BOOT dirty
    true // node itself dirty
};
xod__uart__uart_usb::Node node_4 = {
    xod__uart__uart_usb::State(), // state default
    node_4_output_UART, // output UART default
    node_4_output_DONE, // output DONE default
    true, // UART dirty
    false, // DONE dirty
    true // node itself dirty
};
xod__core__flip_flop::Node node_5 = {
    xod__core__flip_flop::State(), // state default
    node_5_output_MEM, // output MEM default
    true, // MEM dirty
    true // node itself dirty
};
xod__core__clock::Node node_6 = {
    xod__core__clock::State(), // state default
    0, // timeoutAt
    node_6_output_TICK, // output TICK default
    false, // TICK dirty
    true // node itself dirty
};
xod__uart__print::Node node_7 = {
    xod__uart__print::State(), // state default
    node_7_output_DONE, // output DONE default
    node_7_output_ERR, // output ERR default
    false, // DONE dirty
    false, // ERR dirty
    true // node itself dirty
};

#if defined(XOD_DEBUG) || defined(XOD_SIMULATION)
namespace detail {
void handleTweaks() {
    if (XOD_DEBUG_SERIAL.available() > 0 && XOD_DEBUG_SERIAL.find("+XOD:", 5)) {
        int tweakedNodeId = XOD_DEBUG_SERIAL.parseInt();

        switch (tweakedNodeId) {
        }

        XOD_DEBUG_SERIAL.find('\n');
    }
}
} // namespace detail
#endif

void runTransaction() {
    g_transactionTime = millis();

    XOD_TRACE_F("Transaction started, t=");
    XOD_TRACE_LN(g_transactionTime);

#if defined(XOD_DEBUG) || defined(XOD_SIMULATION)
    detail::handleTweaks();
#endif

    // Check for timeouts
    detail::checkTriggerTimeout(&node_6);

    // defer-* nodes are always at the very bottom of the graph, so no one will
    // recieve values emitted by them. We must evaluate them before everybody
    // else to give them a chance to emit values.
    //
    // If trigerred, keep only output dirty, not the node itself, so it will
    // evaluate on the regular pass only if it pushed a new value again.

    // Evaluate all dirty nodes
    { // xod__core__boot #0
        if (node_0.isNodeDirty) {
            XOD_TRACE_F("Eval node #");
            XOD_TRACE_LN(0);

            xod__core__boot::ContextObject ctxObj;
            ctxObj._node = &node_0;

            // copy data from upstream nodes into context

            xod__core__boot::evaluate(&ctxObj);

            // mark downstream nodes dirty
            node_4.isNodeDirty |= node_0.isOutputDirty_BOOT;
        }
    }
    { // xod__uart__uart_usb #4
        if (node_4.isNodeDirty) {
            XOD_TRACE_F("Eval node #");
            XOD_TRACE_LN(4);

            xod__uart__uart_usb::ContextObject ctxObj;
            ctxObj._node = &node_4;

            // copy data from upstream nodes into context
            ctxObj._input_BAUD = node_2_output_VAL;
            ctxObj._input_INIT = node_0.output_BOOT;

            ctxObj._isInputDirty_INIT = node_0.isOutputDirty_BOOT;

            xod__uart__uart_usb::evaluate(&ctxObj);

            // mark downstream nodes dirty
            node_7.isNodeDirty |= node_4.isOutputDirty_UART;
            node_5.isNodeDirty |= node_4.isOutputDirty_DONE;
        }
    }
    { // xod__core__flip_flop #5
        if (node_5.isNodeDirty) {
            XOD_TRACE_F("Eval node #");
            XOD_TRACE_LN(5);

            xod__core__flip_flop::ContextObject ctxObj;
            ctxObj._node = &node_5;

            // copy data from upstream nodes into context
            ctxObj._input_SET = node_4.output_DONE;

            ctxObj._isInputDirty_TGL = false;
            ctxObj._isInputDirty_RST = false;
            ctxObj._isInputDirty_SET = node_4.isOutputDirty_DONE;

            xod__core__flip_flop::evaluate(&ctxObj);

            // mark downstream nodes dirty
            node_6.isNodeDirty |= node_5.isOutputDirty_MEM;
        }
    }
    { // xod__core__clock #6
        if (node_6.isNodeDirty) {
            XOD_TRACE_F("Eval node #");
            XOD_TRACE_LN(6);

            xod__core__clock::ContextObject ctxObj;
            ctxObj._node = &node_6;

            // copy data from upstream nodes into context
            ctxObj._input_EN = node_5.output_MEM;
            ctxObj._input_IVAL = node_3_output_VAL;

            ctxObj._isInputDirty_RST = false;
            ctxObj._isInputDirty_EN = node_5.isOutputDirty_MEM;

            xod__core__clock::evaluate(&ctxObj);

            // mark downstream nodes dirty
            node_7.isNodeDirty |= node_6.isOutputDirty_TICK;
        }
    }
    { // xod__uart__print #7
        if (node_7.isNodeDirty) {
            XOD_TRACE_F("Eval node #");
            XOD_TRACE_LN(7);

            xod__uart__print::ContextObject ctxObj;
            ctxObj._node = &node_7;

            // copy data from upstream nodes into context
            ctxObj._input_UART = node_4.output_UART;
            ctxObj._input_DATA = node_1_output_VAL;
            ctxObj._input_SEND = node_6.output_TICK;

            ctxObj._isInputDirty_SEND = node_6.isOutputDirty_TICK;

            xod__uart__print::evaluate(&ctxObj);

            // mark downstream nodes dirty
        }
    }

    // Clear dirtieness and timeouts for all nodes and pins
    node_0.dirtyFlags = 0;
    node_4.dirtyFlags = 0;
    node_5.dirtyFlags = 0;
    node_6.dirtyFlags = 0;
    node_7.dirtyFlags = 0;
    detail::clearStaleTimeout(&node_6);

    XOD_TRACE_F("Transaction completed, t=");
    XOD_TRACE_LN(millis());
}

} // namespace xod

How to fix

I don't know.

jmwright commented 5 years ago

This ended up being a misunderstanding on my part on what uart-usb was meant for. This is a quote by @nkrkv from the forum:

The uart-usb node represents a USB serial on the boards having a dedicated CDC interface (like Leonardo or Micro). For Uno and Nano the interface is shared with UART #0. So, if you would use uart-0 instead of uart-usb, all should work fine for you.

I tried using uart-0 and everything worked as intended.

@nkrkv I see that you've already changed the tag from triage to s:stdlib, so I won't close this issue. Thanks for the help. I marked the forum thread as resolved.

nkrkv commented 5 years ago

Yes, thank you. I’m pretty sure others can be confused too. The uart-usb node should fall back to UART #0 if required, or at least provide a clear error message. Let’s keep the issue open to fix it eventually.