-
-
Save dymk/bbf6c87e92d5aca3c737 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import | |
| std.stdio, | |
| std.string, | |
| std.array, | |
| std.range, | |
| std.algorithm; | |
| uint leftIndexOfAny(Char1, Char2)(const(Char1)[] s, const(Char2)[][] subs) { | |
| auto indexes_of = map!((a) { return s.countUntil(a); })(subs); | |
| auto min_index = -1U; | |
| foreach(index_of; indexes_of) { | |
| if(index_of != -1) { | |
| if(min_index == -1) { | |
| min_index = index_of; | |
| } else { | |
| min_index = min(min_index, index_of); | |
| } | |
| } | |
| } | |
| return min_index; | |
| } | |
| unittest { | |
| auto a = "1, 2, 3, 4"; | |
| assert(a.leftIndexOfAny(["1", "2"]) == 0); | |
| assert(a.leftIndexOfAny(["4", "2"]) == 3); | |
| assert(a.leftIndexOfAny(["5", "1"]) == 0); | |
| assert(a.leftIndexOfAny(["5", "6"]) == -1); | |
| } | |
| string EmbeddedDFunc(Context = void)(string template_string) { | |
| enum OPEN_DELIM = "<%"; | |
| enum OPEN_DELIM_STR = "<%="; | |
| enum CLOSE_DELIM = "%>"; | |
| auto function_body = ""; | |
| auto indent_level = 0; | |
| void push_line(string[] stmts...) { | |
| foreach(i; 0..indent_level) { | |
| function_body ~= '\t'; | |
| } | |
| foreach(stmt; stmts) { | |
| function_body ~= stmt; | |
| } | |
| function_body ~= '\n'; | |
| } | |
| void indent() { indent_level++; } | |
| void outdent() { indent_level--; } | |
| //generates something like | |
| /+ | |
| (Ctx __context) { | |
| alias __context.a a; | |
| auto d_code = ""; | |
| //generated code | |
| return d_code; | |
| }+/ | |
| const bool context_given = !is(Context == void); | |
| static if(!context_given) { | |
| push_line("() {"); | |
| } else { | |
| enum ContextType = __traits(identifier, Context); | |
| push_line("(", ContextType, " __context) {"); | |
| } | |
| indent(); | |
| push_line("import std.conv;"); | |
| push_line("import std.array;"); | |
| push_line("auto __buff = appender!string();"); | |
| //generate local bindings to context fields | |
| if(context_given) { | |
| push_line("with(__context) {"); | |
| indent(); | |
| } | |
| while(!template_string.empty) { | |
| auto open = template_string.leftIndexOfAny([OPEN_DELIM, OPEN_DELIM_STR]); | |
| if(open != -1) { | |
| //pragma(msg, "Found open delimer @" ~ open ~ " in '" ~ template_string ~ "': " ~ template_string[open..$]); | |
| if(template_string[0..open].length) { | |
| //Append everything before the open delimer onto the string | |
| push_line(`__buff.put("` ~ template_string[0..open] ~ `");`); | |
| } | |
| //find the next close delimer | |
| auto close = template_string[open..$].countUntil(CLOSE_DELIM); | |
| assert(close != -1, "Missing close delimer '" ~ CLOSE_DELIM ~ "'. (" ~ template_string ~ ")"); | |
| close += open; //add index position lost by slicing from 0..open | |
| string delim_type; | |
| if(template_string[open..open+OPEN_DELIM_STR.length] == OPEN_DELIM_STR) { | |
| delim_type = OPEN_DELIM_STR; | |
| } else if(template_string[open..open+OPEN_DELIM.length] == OPEN_DELIM) { | |
| delim_type = OPEN_DELIM; | |
| } else { | |
| assert(false, "Unknown delimer at " ~ template_string[open..open+5]); | |
| } | |
| auto inbetween_delims = template_string[open+delim_type.length ..close]; | |
| if(delim_type == OPEN_DELIM_STR) { | |
| //Was an evaluate + output string delimer | |
| push_line(`__buff.put(to!string((` ~ inbetween_delims ~ `)));`); | |
| } else { | |
| //Was an evaluate delimer | |
| push_line(inbetween_delims); | |
| } | |
| template_string = template_string[close + CLOSE_DELIM.length .. $]; | |
| } | |
| else { | |
| //no more open delimers, append rest to buffer | |
| push_line(`__buff.put("` ~ template_string[0..$] ~ `");`); | |
| break; | |
| } | |
| } | |
| if(context_given) { | |
| outdent(); | |
| push_line("}"); | |
| } | |
| push_line("return __buff.data();"); | |
| outdent(); | |
| push_line("}"); | |
| return function_body; | |
| } | |
| unittest { | |
| //Test delimer parsing | |
| const render = mixin(EmbeddedDFunc("<% if(true) { %>foo<% } %>")); | |
| static assert(render() == "foo"); | |
| } | |
| unittest { | |
| //Test to!string of eval delimers | |
| const render = mixin(EmbeddedDFunc(`<%= "foo" %>`)); | |
| static assert(render() == "foo"); | |
| } | |
| unittest { | |
| //Test raw text with no delimers | |
| const render = mixin(EmbeddedDFunc(`foo`)); | |
| static assert(render() == "foo"); | |
| } | |
| unittest { | |
| //Assert that it's invalid if no context is used | |
| static assert(!__traits(compiles, mixin(EmbeddedDFunc(`<%= test %>`)))); | |
| } | |
| unittest { | |
| //Assert that it's invalid if invalid context fields are used | |
| struct Ctx { | |
| string foo; | |
| } | |
| static assert(!__traits(compiles, mixin(EmbeddedDFunc(`<%= bar %>`)))); | |
| } | |
| unittest { | |
| //Test static context fields | |
| struct Ctx { | |
| static string test = "static value"; | |
| } | |
| const render = mixin(EmbeddedDFunc!Ctx(`<%= test %>`)); | |
| assert(render(Ctx()) == "static value"); | |
| } | |
| unittest { | |
| //Test member context fields | |
| struct Ctx { | |
| string test; | |
| } | |
| const render = mixin(EmbeddedDFunc!Ctx(`<%= test %>`)); | |
| Ctx ctx = {test: "member value"}; | |
| assert(render(ctx) == "member value"); | |
| } | |
| unittest { | |
| //Test looping | |
| const templ = `<% foreach(i; 0..3) { %>foo<% } %>`; | |
| const render = mixin(EmbeddedDFunc(templ)); | |
| static assert(render() == "foofoofoo"); | |
| } | |
| unittest { | |
| //Test looping | |
| const templ = `<% foreach(i; 0..3) { %><%= i %><% } %>`; | |
| const render = mixin(EmbeddedDFunc(templ)); | |
| static assert(render() == "012"); | |
| } | |
| unittest { | |
| //Test method calling & context state on contexts | |
| struct Ctx { | |
| int foo() { | |
| return n_foo++; | |
| } | |
| int n_foo; | |
| } | |
| const templ = `<% foreach(i; 0..3) { %><%= foo() %><% } %>`; | |
| const render = mixin(EmbeddedDFunc!Ctx(templ)); | |
| assert(render(Ctx()) == "012"); | |
| } | |
| void main() { | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment