fc8ffc | 2017-05-23 | Pontus Östlund | | #pike __REAL_VERSION__
#require constant(Regexp.PCRE)
import Regexp.PCRE;
#ifdef MUSTACHE_DEBUG
# define TRACE(X...)werror("%s:%d: %s",basename(__FILE__),__LINE__,sprintf(X))
#else
# define TRACE(X...)0
#endif
protected class Re
{
inherit Widestring;
int search(string s)
{
array(int)|int r = ::exec(s);
if (intp(r) && r != -1) {
error("Regex error %d!\n", r);
}
return intp(r) ? r : r[0];
}
}
protected array(string) tags = ({ "{{", "}}" });
protected Re
_re_escape_re = Re("[\\-\\[\\]{}()*+?.,\\\\^$|#\\s]"),
_re_escape_html = Re("[&<>\"'`=/]"),
_re_nonspace = Re("\\S"),
_re_white = Re("\\s*"),
_re_space = Re("\\s+"),
_re_equals = Re("\\s*="),
_re_curly = Re("\\s*\\}"),
_re_tag = Re("#|\\^|\\/|>|\\{|&|=|!");
protected string escape_regexp(string s)
{
return _re_escape_re->replace(s, lambda (string a) {
return "\\" + a;
});
}
protected bool has_property(mixed obj, string prop)
{
if (objectp(obj) || mappingp(obj) || multisetp(obj) || arrayp(obj)) {
return has_index(obj, prop);
}
return false;
}
protected bool is_whitespace(string|int s)
{
if (stringp(s)) {
return !_re_nonspace->match(s);
}
return (< '\n', ' ', '\t', '\r' >)[s];
}
protected mapping entity_map = ([
"&" : "&",
"<" : "<",
">" : ">",
"\"" : """,
"'" : "'",
"/" : "/",
"`" : "`",
"=" : "="
]);
public string escape_html(string s)
{
return _re_escape_html->replace(s, lambda (string a) {
return entity_map[a] || a;
});
}
protected class Scanner
{
string str;
string tail;
int pos;
protected void create(string s)
{
str = s;
tail = s;
pos = 0;
}
public bool eos()
{
return tail == "";
}
public string scan(Re re)
{
array(int)|int r = re->exec(tail);
if (intp(r) && r != -1) {
error("Regexp error: %d", r);
}
if ((intp(r) && r == -1) || r[0] != 0) {
return 0;
}
[int start, int end] = r;
int len = end-start;
string s = tail[start..end-1];
tail = tail[len..];
pos += len;
return s;
}
public string scan_until(Re|string what)
{
int index;
if (stringp(what)) {
index = search(tail, what);
}
else {
index = what->search(tail);
}
string match;
switch (index)
{
case -1:
match = tail;
tail = "";
break;
case 0:
match = "";
break;
default:
match = tail[0..index-1];
tail = tail[index..];
break;
}
pos += sizeof(match);
return match;
}
#ifdef MUSTACHE_DEBUG
protected void destroy()
{
TRACE("Scanner destroyed!\n");
}
#endif
}
|
fc8ffc | 2017-05-23 | Pontus Östlund | |
protected class Token
{
protected string type;
protected mixed value;
protected int start, end;
protected mixed extra, extra2;
protected void create(string type, mixed value, int start, int end)
{
this::type = type;
this::value = value;
this::start = start;
this::end = end;
}
mixed `[](int idx)
{
switch (idx) {
case 0: return type;
case 1: return value;
case 2: return start;
case 3: return end;
case 4: return extra;
case 5: return extra2;
}
}
mixed `[]=(int idx, mixed val)
{
switch (idx)
{
case 0: return type = val;
case 1: return value = val;
case 2: return start = val;
case 3: return end = val;
case 4: return extra = val;
case 5: return extra2 = val;
}
}
mixed cast(string how)
{
switch (how)
{
case "array":
return ({ type, value, start, end, extra, extra2 });
default:
error("Unknown cast (%O) in object! ", how);
}
}
string _sprintf(int t)
{
return sprintf("({ %O, %O, %O, %O, %O, %O })",
type, value, start, end, extra, extra2);
}
#ifdef MUSTACHE_DEBUG
protected void destroy()
{
TRACE("Token destroyed!\n");
}
#endif
}
|
fc8ffc | 2017-05-23 | Pontus Östlund | |
array(Token) parse_template(string|function template, void|array(string) _tags)
{
if (functionp(template)) {
template = (string)template(0);
}
if (!template || !sizeof(template)) {
return ({});
}
array(Token) sections = ({});
array(Token) tokens = ({});
array(int) spaces = ({});
bool has_tag = false;
bool none_space = false;
void strip_space() {
if (has_tag && !none_space) {
while (sizeof(spaces)) {
int t = spaces[-1];
spaces = spaces[..<1];
tokens[t] = 0;
tokens -= ({ 0 });
}
}
else {
spaces = ({});
}
has_tag = false;
none_space = false;
};
Re opening_tag_re, closing_tag_re, closing_curly_re;
void compile_tags(string|array(string) t) {
if (stringp(t)) {
t = _re_space->split(t);
}
if (!arrayp(t) || sizeof(t) != 2) {
error("Invalid tags: %O\n", t);
}
opening_tag_re = Re(escape_regexp(t[0]) + "\\s*");
closing_tag_re = Re("\\s*" + escape_regexp(t[1]));
closing_curly_re = Re("\\s*" + escape_regexp("}" + t[1]));
};
compile_tags(_tags || tags);
Scanner scanner = Scanner(template);
int start, chr;
Token token, open_section;
string value;
while (!scanner->eos()) {
start = scanner->pos;
value = scanner->scan_until(opening_tag_re);
if (value) {
for (int i; i < sizeof(value); i++) {
chr = value[i];
if (is_whitespace(chr)) {
spaces += ({ sizeof(tokens) });
}
else {
none_space = true;
}
tokens += ({ Token("text", value[i..i], start, start + 1) });
start += 1;
if (chr == '\n') {
strip_space();
}
}
}
if (!scanner->scan(opening_tag_re)) {
break;
}
has_tag = true;
string type = scanner->scan(_re_tag) || "name";
scanner->scan(_re_white);
if (type == "=") {
value = scanner->scan_until(_re_equals);
scanner->scan(_re_equals);
scanner->scan_until(closing_tag_re);
}
else if (type == "{") {
value = scanner->scan_until(closing_curly_re);
scanner->scan(_re_curly);
scanner->scan_until(closing_tag_re);
type = "&";
}
else {
value = scanner->scan_until(closing_tag_re);
}
if (!scanner->scan(closing_tag_re)) {
error("Unclosed tag at byte %d!\n", scanner->pos);
}
token = Token(type, value, start, scanner->pos);
tokens += ({ token });
if ((< "#", "^" >)[type]) {
sections += ({ token });
}
else if (type == "/") {
open_section = sections[-1];
sections = sections[..<1];
if (!open_section) {
error("Unopened section \"%s\" at byte %d!\n", value, start);
}
if (open_section[1] != value) {
error("Unclosed section \"%s\" at byte %d!\n", open_section[1], start);
}
}
else if ((< "name", "{", "&" >)[type]) {
none_space = true;
}
else if (type == "=") {
compile_tags(value);
}
}
if (sizeof(sections)) {
open_section = sections[-1];
error("Unclosed section \"%s\" at byte %d!\n",
open_section[1], scanner->pos);
}
return nest_tokens(squash_tokens(tokens));
}
protected array(Token) squash_tokens(array(Token) tokens)
{
array(Token) st = ({});
Token token, last_token;
int len = sizeof(tokens);
for (int i; i < len; ++i) {
token = tokens[i];
if (token) {
if (token[0] == "text" && last_token && last_token[0] == "text") {
last_token[1] += token[1];
last_token[3] = token[3];
}
else {
st += ({ token });
last_token = token;
}
}
}
return st;
}
class TokRef
{
private array _data = ({});
array `data()
{
return _data;
}
TokRef `+(Token t)
{
_data += ({ t });
return this;
}
TokRef `[]=(int index, mixed v)
{
if (has_index(_data, index)) {
_data[index] = v;
}
return this;
}
Token `[](int t)
{
if (sizeof(_data) >= t) {
return _data[t];
}
}
Token pop()
{
if (sizeof(_data)) {
Token t = _data[-1];
_data = _data[..<1];
return t;
}
}
int _sizeof()
{
return sizeof(_data);
}
mixed cast(string how)
{
if (how == "array") {
array(Token) out = allocate(sizeof(_data));
for (int i; i < sizeof(_data); i++) {
out[i] = _data[i];
if (objectp(out[i][4])) {
out[i][4] = out[i][4]->cast("array");
}
}
return out;
}
}
string _sprintf(int t)
{
return sprintf("%O(%d)", object_program(this), sizeof(_data));
}
#ifdef MUSTACHE_DEBUG
protected void destroy()
{
TRACE("TokRef destroyed!\n");
}
#endif
}
protected array(Token) nest_tokens(array(Token) tokens)
{
TokRef
nested_tokens = TokRef(),
collector = nested_tokens,
sections = TokRef();
Token token, section;
int len = sizeof(tokens);
for (int i; i < len; ++i) {
token = tokens[i];
switch (token[0]) {
case "#":
case "^":
collector += token;
sections += token;
collector = token[4] = TokRef();
break;
case "/":
section = sections->pop();
section[5] = token[2];
collector = sizeof(sections)
? sections[-1][4]
: nested_tokens;
break;
default:
collector += token;
break;
}
}
array(Token) my_toks = (array(object(Token))) nested_tokens;
destruct(collector);
destruct(sections);
destruct(nested_tokens);
return my_toks;
}
protected class Context
{
mixed view;
mapping cache;
Context parent;
protected void create(mixed view, void|Context parent_context)
{
this::view = view;
this::parent = parent_context;
cache = ([ "." : view ]);
}
public Context push(mixed view)
{
return Context(view, this);
}
public mixed lookup(string name)
{
mixed value = cache[name];
if (undefinedp(value)) {
Context ctx = this;
array(string) names;
int index;
bool lookuphit = false;
while (ctx) {
if (search(name, ".") > -1) {
value = ctx->view;
names = name/".";
index = 0;
int namelen = sizeof(names);
while (value && index < namelen) {
if (index == namelen - 1) {
lookuphit = has_property(value, names[index]);
}
value = value[names[index++]];
}
}
else {
value = ctx->view[name];
lookuphit = has_property(ctx->view, name);
}
if (lookuphit) {
break;
}
ctx = ctx->parent;
}
cache[name] = value;
}
if (functionp(value)) {
value = value(name, view);
}
return safe_string(value);
}
#ifdef MUSTACHE_DEBUG
protected void destroy()
{
TRACE("Context destroyed!\n");
}
#endif
}
protected class Writer
{
private mapping __cache = ([]);
public void clear_cache()
{
__cache = ([]);
}
public array(Token) parse(string template, void|array(string) tags)
{
array(Token) tokens = __cache[template];
if (!tokens) {
tokens = __cache[template] = parse_template(template, tags);
}
return tokens;
}
public string render(string template, mixed view,
void|mixed partials)
{
array(Token) tokens = parse(template);
Context ctx = objectp(view) && object_program(view) == Context
? view
: Context(view);
string res = render_tokens(tokens, ctx, partials, template);
tokens = 0;
return res;
}
protected string render_tokens(array(Token) tokens, Context ctx,
mixed partials, string template)
{
String.Buffer buf = String.Buffer();
function add = buf->add;
Token token;
string symbol;
mixed value;
int len = sizeof(tokens);
for (int i; i < len; ++i) {
value = UNDEFINED;
token = tokens[i];
symbol = token[0];
switch (symbol) {
case "#":
value = render_section(token, ctx, partials, template);
break;
case "^":
value = render_inverted(token, ctx, partials, template);
break;
case ">":
value = render_partial(token, ctx, partials);
break;
case "&":
value = unescaped_value(token, ctx);
break;
case "name":
value = escaped_value(token, ctx);
break;
case "text":
value = raw_value(token);
break;
}
if (value != UNDEFINED) {
add(value);
}
}
return buf->get();
}
protected string render_section(Token token, Context ctx, mixed partials,
string template)
{
String.Buffer b = String.Buffer();
function add = b->add;
mixed value = ctx->lookup(token[1]);
if (!value) {
return "";
}
string subrender(string tmpl) {
return render(tmpl, ctx, partials);
};
if (multisetp(value)) {
value = (array)value;
}
if (arrayp(value)) {
int len = sizeof(value);
for (int j; j < len; ++j) {
add(render_tokens(token[4], ctx->push(value[j]), partials, template));
}
}
else if (objectp(value) || mappingp(value)) {
add(render_tokens(token[4], ctx->push(value), partials, template));
}
else if (functionp(value)) {
if (!stringp(template)) {
error("Cannot use higher-order sections without the original template");
}
value = value(ctx->view, template[token[3]..token[5]-1], subrender);
if (value && sizeof(value)) {
add(value);
}
}
else {
add(safe_string(render_tokens(token[4], ctx, partials, template)));
}
return b->get();
}
string render_inverted(Token token, Context ctx, mixed partials,
string template)
{
mixed value = ctx->lookup(token[1]);
if (falsy(value)) {
return render_tokens(token[4], ctx, partials, template);
}
}
string render_partial(Token token, Context ctx, mixed partials)
{
if (!partials) {
return UNDEFINED;
}
mixed value = callablep(partials) ? partials(token[1]) : partials[token[1]];
if (value) {
return render_tokens(parse(value), ctx, partials, value);
}
}
string unescaped_value(Token token, Context ctx)
{
mixed value = ctx->lookup(token[1]);
if (value) {
return (string) value;
}
}
string escaped_value(Token token, Context ctx)
{
mixed value = ctx->lookup(token[1]);
if (value != UNDEFINED) {
return escape_html((string)value);
}
}
string raw_value(Token token)
{
return (string) token[1];
}
#ifdef MUSTACHE_DEBUG
protected void destroy()
{
TRACE("Writer destroyed!\n");
}
#endif
}
protected mixed safe_string(mixed i)
{
if (!stringp(i)) {
return i;
}
catch {
i = utf8_to_string(i);
return i;
};
return i;
}
protected Writer default_writer = Writer();
public void clear_cache()
{
default_writer->clear_cache();
}
public array(Token) parse(string template, void|array(string) tags)
{
return default_writer->parse(template, tags);
}
public string render(string template, mixed view, void|mixed partials)
{
return default_writer->render(template, view, partials);
}
protected bool falsy(mixed v)
{
if (!v) return true;
if ((stringp(v) || arrayp(v)) && !sizeof(v)) {
return true;
}
return false;
}
#ifdef MUSTACHE_DEBUG
protected void destroy()
{
TRACE("Mustache destroyed!\n");
}
#endif
|