Skip to content
This repository has been archived by the owner on Jun 4, 2022. It is now read-only.

Commit

Permalink
Get {% query %} to work with {% macro %} (as well as {% with %} & {% …
Browse files Browse the repository at this point in the history
…trans %}.

These all use the same runtime node with their real implementation
being done at compile time (except for {% with %} which lets you
instantiate the AST node directly).

The main challenge here was around inlining variables within others.
For that I defined a new method on variables which'll try and
detect whether it actually needs to build a new one (to save memory),
before it copies over the data from the variable context and extend it
to have the new filters (with recursive inlining) and path traversal
(implemented as a behind-the-scenes filter).

Simplified Odysseus.Templating.Variable.nilvar initialization.
  • Loading branch information
Adrian Cochrane committed Mar 18, 2018
1 parent 4fca870 commit dab7b04
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 11 deletions.
5 changes: 3 additions & 2 deletions src/Services/Prosody/lib.vala
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,9 @@ namespace Odysseus.Templating.Std {
}
}
private class WithTag : Template {
private Gee.Map<Bytes,Variable> vars;
private Template body;
// These fields are public to allow for external code to inline them.
public Gee.Map<Bytes,Variable> vars;
public Template body;
public WithTag(Gee.Map<Bytes,Variable> variables, Template bodyblock) {
this.vars = variables;
this.body = bodyblock;
Expand Down
76 changes: 67 additions & 9 deletions src/Services/Prosody/parser.vala
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,8 @@ namespace Odysseus.Templating {
}

public class Variable : Template {
private Bytes[] path;
private Data.Data? literal;
protected Bytes[] path;
protected Data.Data? literal;
public Map<uint8,string> escapes; // public so firstOf can apply it.

/* Useful global constants to be lazily compiled */
Expand All @@ -414,10 +414,7 @@ namespace Odysseus.Templating {
get {
if (_nilvar == null) {
try {
_nilvar = new Variable(
// undefined probably won't be defined...
b("undefined"),
new Gee.HashMap<char,string>());
_nilvar = new Variable.with(new Data.Empty());
} catch (SyntaxError e) {
error("Failed to initialize placeholder variable: %s", e.message);
}
Expand All @@ -436,20 +433,20 @@ namespace Odysseus.Templating {
}
/* end lazily compiled global constants */

private class FilterCall {
protected class FilterCall {
public Filter cb;
public Variable arg;
public FilterCall(Filter cb, Variable arg) {
this.cb = cb; this.arg = arg;
}
}
private FilterCall[] filters;
protected FilterCall[] filters;

public Variable.from_args(WordIter args, Map<uint8,string> escapes) throws SyntaxError {
this(args.next(), escapes);
}

public Variable.with(Data.Data d) {
public Variable.with(Data.Data? d) {
this.literal = d;
this.path = new Bytes[0];
this.escapes = new Gee.HashMap<uint8,string>();
Expand Down Expand Up @@ -529,6 +526,67 @@ namespace Odysseus.Templating {

return data;
}

// Called from external tags
// FIXME could really do with some rigid tests.
public Variable inlineCtx(Gee.Map<Bytes, Variable> ctx) {
// First see if we can get away with returning an existing variable.
if (literal != null || (path.length >= 1 && !ctx.has_key(path[0]))) {
// Filter arguments may still need inlining, but check first!
var needs_inlining = false;
foreach (var filter in filters) if (filter.arg.inlineCtx(ctx) != filter.arg) {
needs_inlining = true;
break;
}
if (!needs_inlining) return this;

// O.K., we actually need to build a new variable.
var ret = new Variable.with(literal);
ret.path = path;

var newFilters = new Gee.ArrayList<FilterCall>();
foreach (var filter in filters)
newFilters.add(new FilterCall(filter.cb, filter.arg.inlineCtx(ctx)));
ret.filters = newFilters.to_array();

return ret;
}
/* From here we know it's not a literal, and the base var is in ctx */
// Variables of the form {{ var }}, where var is in the context.
if (path.length == 1 && filters.length == 0)
return ctx[path[0]];
// Invalid vars
if (path.length == 0) return this;

/* Now things get trickier...
And may require a custom filter to be inserted
between the two filter chains. */
var baseVar = ctx[path[0]];
var ret = new Variable.with(null);

ret.literal = baseVar.literal;
ret.path = baseVar.path;

var newFilters = new Gee.ArrayList<FilterCall>();
newFilters.add_all(new Gee.ArrayList<FilterCall>.wrap(baseVar.filters));
newFilters.add(new FilterCall(new PathFilter(path[1:path.length]), nilvar));
foreach (var filter in filters)
newFilters.add(new FilterCall(filter.cb, filter.arg.inlineCtx(ctx)));
ret.filters = newFilters.to_array();

return ret;
}

private class PathFilter : Filter {
private Bytes[] path;
public PathFilter(Bytes[] path) {this.path = path;}

public override Data.Data filter0(Data.Data a) {
var data = a;
foreach (var property in path) data = data[property];
return data;
}
}
}

public class Block : Template {
Expand Down
10 changes: 10 additions & 0 deletions src/Services/database/prosody.vala
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ namespace Odysseus.Database.Prosody {
return "?";
} else if (ast is Echo) {
return ByteUtils.to_string((ast as Echo).text);
} else if (ast is Std.WithTag) {
var with = ast as Std.WithTag;

var innerParams = new Gee.ArrayList<Variable>();
var txt = compile_block(with.body, innerParams);

foreach (var param in innerParams)
queryParams.add(param.inlineCtx(with.vars));

return txt;
} else
throw new SyntaxError.OTHER("This tag is unsupported in combination with SQL!");
}
Expand Down

0 comments on commit dab7b04

Please sign in to comment.