xod
xod copied to clipboard
Do not demand handlebar tags in C++
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.