xod icon indicating copy to clipboard operation
xod copied to clipboard

Do not demand handlebar tags in C++

Open nkrkv opened this issue 7 years ago • 0 comments

Rationale

Now xoders are forced to use {{ GENERATED_CODE }} and {{#global}} in their patch.cpp’s. It is ugly. We can get rid of them.

Acceptance criteria

  • [ ] Strict backward compatibility: any current code which uses {{ GENERATED_CODE }} and {{#global}} keeps working
  • [ ] The handlebar mentions removed from docs
  • [ ] The handlebar tags removed from the standard library C++ patches

How to implement

The reason we used to require {{ GENERATED_CODE }} is because some auto-generated code required State definition, so it should go below State but that code is also required to come before evaluate. However, with some clever forward declarations, we can limit ourselves to generating code before user code, then dump the user code as is, and then generate a footer code.

Here’s an example of xod/common-hardware/text-lcd-16x2 implementation that follows this principle and compiles successfully:

//-----------------------------------------------------------------------------
// xod/common-hardware/text-lcd-16x2 implementation
//-----------------------------------------------------------------------------
namespace xod__common_hardware__text_lcd_16x2 {

struct Node;

struct input_RS { };
struct input_EN { };
struct input_D4 { };
struct input_D5 { };
struct input_D6 { };
struct input_D7 { };
struct input_L1 { };
struct input_L2 { };
struct input_UPD { };
struct output_DONE { };

struct ContextObject {
    Node* _node;

    uint8_t _input_RS;
    uint8_t _input_EN;
    uint8_t _input_D4;
    uint8_t _input_D5;
    uint8_t _input_D6;
    uint8_t _input_D7;
    XString _input_L1;
    XString _input_L2;
    Logic _input_UPD;

    bool _isInputDirty_UPD;
};

using Context = ContextObject*;


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

template<> bool isInputDirty<input_UPD>(Context ctx) {
    return ctx->_isInputDirty_UPD;
}

template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_RS> { using T = uint8_t; };
template<> struct ValueType<input_EN> { using T = uint8_t; };
template<> struct ValueType<input_D4> { using T = uint8_t; };
template<> struct ValueType<input_D5> { using T = uint8_t; };
template<> struct ValueType<input_D6> { using T = uint8_t; };
template<> struct ValueType<input_D7> { using T = uint8_t; };
template<> struct ValueType<input_L1> { using T = XString; };
template<> struct ValueType<input_L2> { using T = XString; };
template<> struct ValueType<input_UPD> { using T = Logic; };
template<> struct ValueType<output_DONE> { using T = Logic; };



template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
    static_assert(always_false<PinT>::value,
            "Invalid pin descriptor. Expected one of:" \
            " input_RS input_EN input_D4 input_D5 input_D6 input_D7 input_L1 input_L2 input_UPD" \
            " output_DONE");
}

template<> uint8_t getValue<input_RS>(Context ctx) {
    return ctx->_input_RS;
}
template<> uint8_t getValue<input_EN>(Context ctx) {
    return ctx->_input_EN;
}
template<> uint8_t getValue<input_D4>(Context ctx) {
    return ctx->_input_D4;
}
template<> uint8_t getValue<input_D5>(Context ctx) {
    return ctx->_input_D5;
}
template<> uint8_t getValue<input_D6>(Context ctx) {
    return ctx->_input_D6;
}
template<> uint8_t getValue<input_D7>(Context ctx) {
    return ctx->_input_D7;
}
template<> XString getValue<input_L1>(Context ctx) {
    return ctx->_input_L1;
}
template<> XString getValue<input_L2>(Context ctx) {
    return ctx->_input_L2;
}
template<> Logic getValue<input_UPD>(Context ctx) {
    return ctx->_input_UPD;
}

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

template<> void emitValue<output_DONE>(Context ctx, Logic val);

struct State;
State* getState(Context);

// --- Enter global namespace ---
}}
#include <LiquidCrystal.h>

namespace xod {
namespace xod__common_hardware__text_lcd_16x2 {
// --- Back to local namespace ---
struct State {
    LiquidCrystal* lcd;
};



void printLine(LiquidCrystal* lcd, uint8_t lineIndex, XString str) {
    lcd->setCursor(0, lineIndex);
    uint8_t whitespace = 16;
    for (auto it = str.iterate(); it; ++it, --whitespace)
        lcd->write(*it);

    // Clear the rest of the line
    while (whitespace--)
        lcd->write(' ');
}

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

    State* state = getState(ctx);
    auto lcd = state->lcd;
    if (!state->lcd) {
        state->lcd = lcd = new LiquidCrystal(
            (int)getValue<input_RS>(ctx),
            (int)getValue<input_EN>(ctx),
            (int)getValue<input_D4>(ctx),
            (int)getValue<input_D5>(ctx),
            (int)getValue<input_D6>(ctx),
            (int)getValue<input_D7>(ctx));

        lcd->begin(16, 2);
    }

    printLine(lcd, 0, getValue<input_L1>(ctx));
    printLine(lcd, 1, getValue<input_L2>(ctx));

    emitValue<output_DONE>(ctx, 1);
}



struct Node {
    State state;
    Logic output_DONE;

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

        DirtyFlags dirtyFlags;
    };
};

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

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

} // namespace xod__common_hardware__text_lcd_16x2

What’s for #include’s, they could be automatically prefixed with escaping to the global scope and then returning back on the next line with a trivial regex.

How to preserve backward compatibility given that {{#global}} tags are used for includes and ISR’s with their global values? Rough idea:

#ifndef XOD_IN_GLOBAL_SCOPE
}}
#endif

#include <LiquidCrystal.h>

#ifndef XOD_IN_GLOBAL_SCOPE
namespace xod {
namespace xod__common_hardware__text_lcd_16x2 {
#endif

And if the current {{#global}} will #define XOD_IN_GLOBAL_SCOPE whereas {{/global}} will #undef XOD_IN_GLOBAL_SCOPE this bulky structure can work.

nkrkv avatar Jul 04 '18 16:07 nkrkv