Load.java
/***************************************************************************
Copyright 2012 Emily Estes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***************************************************************************/
package net.metanotion.simpletemplate.interpreter;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.metanotion.simpletemplate.Resource;
import net.metanotion.simpletemplate.ResourceFactory;
import net.metanotion.simpletemplate.parser.ParseException;
import net.metanotion.simpletemplate.parser.STParser;
import net.metanotion.simpletemplate.parser.syntaxtree.*;
public final class Load {
private static final Logger logger = LoggerFactory.getLogger(Load.class);
private final ResourceFactory resources;
public Load(final ResourceFactory resources) {
this.resources = resources;
}
public Resource construct(final InputStream in,
final ArrayList<String> incPath,
final boolean doETag,
final Long maxExpire,
final String defaultMIMEType) {
try {
final List<Element> pieces = load(in, incPath);
return new Skeleton(pieces.toArray(new Element[pieces.size()]), doETag, maxExpire, defaultMIMEType);
} catch (final ParseException pe) {
throw new RuntimeException(pe);
}
}
List<Element> load(final InputStream in, final ArrayList<String> incPath) throws ParseException {
final STParser parse = new STParser(in);
final Template t = parse.Template();
final ArrayList pieces = new ArrayList();
for(final Node n: t.f0.nodes) { parse((HtmlBlock) n, pieces, incPath); }
return pieces;
}
void parse(HtmlBlock hb, List pieces, ArrayList<String> incPath) {
switch (hb.f0.which) {
case 0:
case 1:
pieces.add(new EChunk(hb.f0.choice.toString()));
break;
case 2:
final ES es = (ES) hb.f0.choice;
if(es.f1.present()) {
pieces.addAll(parseExprSeq((ExprSeq) es.f1.node));
}
break;
case 3:
logger.debug("Server Side Directive!");
parseSSD((SSD) ((NodeSequence) hb.f0.choice).nodes.get(1), pieces, incPath);
break;
}
}
List<Element> parseExprSeq(ExprSeq exsq) {
final List<Element> pieces = new ArrayList<Element>();
List<Element> le = parseCall(exsq.f0);
if(le.size() > 1) { throw new RuntimeException("Parse error, expression implied function value not found."); }
pieces.add(le.get(0));
for(final Node n: exsq.f1.nodes) {
final Expression e = (Expression) ((NodeSequence) n).nodes.get(1);
le = parseCall(e);
if(le.size() > 1) { throw new RuntimeException("Parse error, expression implied function value not found."); }
pieces.add(le.get(0));
}
return pieces;
}
void parseSSD(final SSD command, final List<Element> pieces, final ArrayList<String> incPath) {
logger.debug("'{}'", command.f0.f0.choice.toString());
if("include".equalsIgnoreCase(command.f0.f0.choice.toString())) {
String urn = command.f3.toString();
urn = urn.substring(1, urn.length() - 1);
incPath.add(urn);
// TO DO There is a bug in this code, include path needs to follow the recursion into ESubResource... not
// stay on this level.
//if(incPath.get(incPath.size() / 2).equals(urn)) { throw new RuntimeException("Include Cycle detected."); }
logger.debug("SSI - {}, {}", urn, resources.get(urn));
pieces.add(new ESubResource(resources.get(urn)));
} else {
// Unsupported so far :)
}
}
List<Element> parseCall(final Expression e) {
final ArrayList<Element> list = new ArrayList<Element>();
if(e.f0.choice instanceof Apply) {
logger.debug("APPLY");
list.add(parseApply((Apply) e.f0.choice));
} else if(e.f0.choice instanceof JSObj) {
logger.debug("JSOBJ");
list.add(parseJSObj((JSObj) e.f0.choice));
} else if(e.f0.choice instanceof JSArr) {
logger.debug("JSARR");
list.add(parseJSArr((JSArr) e.f0.choice));
} else if(e.f0.choice instanceof Tuple) {
logger.debug("TUPLE");
list.addAll(parseTuple((Tuple) e.f0.choice));
} else if(e.f0.choice instanceof Lambda) {
logger.debug("LAMBDA");
list.add(parseLambda((Lambda) e.f0.choice));
} else if(e.f0.choice instanceof Let) {
logger.debug("LET");
list.add(parseLet((Let) e.f0.choice));
} else if(e.f0.choice instanceof Lift) {
logger.debug("LIFT");
list.add(parseLift((Lift) e.f0.choice));
} else if(e.f0.choice instanceof Literal) {
logger.debug("LITERAL");
final Object o = parseLiteral((Literal) e.f0.choice);
logger.debug(o.toString());
list.add(new EChunk(o));
}
return list;
}
Element parseLet(final Let let) {
final EJSObj vars = parseJSObj(let.f1);
final List<Element> list = parseExprSeq(let.f3);
final Element r = new ELet(vars, list.toArray(new Element[list.size()]));
if(let.f5.present()) { return reduce(r, parseCall((Expression) let.f5.node)); }
return r;
}
Element parseLift(final Lift lift) {
List<Element> list = new ArrayList<Element>();
if(lift.f1.present()) { list = parseCall((Expression) lift.f1.node); }
return new ELift(list.toArray(new Element[list.size()]));
}
Element parseLambda(final Lambda l) {
final List<Element> list = parseExprSeq(l.f2);
final Element r = new ELambda(list.toArray(new Element[list.size()]));
if(l.f4.present()) { return reduce(r, parseCall((Expression) l.f4.node)); }
return r;
}
EList parseJSArr(final JSArr json) {
final ArrayList<Element> list = new ArrayList<Element>();
if(json.f1.present()) {
final Params p = (Params) json.f1.node;
List<Element> el = parseCall(p.f0);
if(el.size() > 1) { throw new RuntimeException("Parse Error"); }
list.add(el.get(0));
for(final Node n: p.f1.nodes) {
el = parseCall((Expression) (((NodeSequence) n).nodes.get(1)));
if(el.size() > 1) { throw new RuntimeException("Parse Error"); }
list.add(el.get(0));
}
}
return new EList(list);
}
private void addWithParam(final WithParam wp, final HashMap<String,Element> obj) {
final Key k = wp.f0;
String s = k.f0.choice.toString();
if(k.f0.which == 1) {
s = s.substring(1, s.length() - 1).replaceAll("\\\\\"", "\"").replaceAll("\\\\", "\\");
} else if(k.f0.which == 2) {
s = s.substring(1, s.length() - 1).replaceAll("\\\\'", "'").replaceAll("\\\\", "\\");
}
logger.debug(" '" + s + ": -");
final List<Element> el = parseCall(wp.f2);
if(el.size() > 1) { throw new RuntimeException("Parse Error"); }
obj.put(s, el.get(0));
}
EJSObj parseJSObj(final JSObj json) {
final HashMap<String,Element> obj = new HashMap<String,Element>();
if(json.f1.present()) {
final WithParams wps = (WithParams) json.f1.node;
addWithParam(wps.f0, obj);
for(Node n: wps.f1.nodes) {
addWithParam((WithParam) (((NodeSequence) n).nodes.get(1)), obj);
}
}
return new EJSObj(obj);
}
Element parseApply(final Apply a) {
String object = "";
String method = "";
final List<String> methodSeq = new ArrayList<String>();
methodSeq.add(a.f0.toString());
for(Node n: a.f1.nodes) {
methodSeq.add(((NodeSequence) n).nodes.get(1).toString());
}
if(methodSeq.size() > 1) { object = methodSeq.remove(0); }
if(methodSeq.size() == 0) {
method = object;
object = "";
} else {
method = methodSeq.remove(0);
}
logger.debug(" :" + object + "." + method);
List<Element> param = new ArrayList<Element>();
if(a.f2.present()) { param = parseCall((Expression) (a.f2.node)); }
if(param.size() == 0) {
Element r = new EImplicitEval(object, method);
while(methodSeq.size() > 0) { r = new EFieldEval(r, methodSeq.remove(0)); }
return r;
} else {
if(methodSeq.size() > 0) {
Element r = new EImplicitEval(object, method);
while(methodSeq.size() > 0) { r = new EFieldEval(r, methodSeq.remove(0)); }
return reduce(r, param);
} else {
Element e0 = param.remove(0);
if(e0 instanceof EJSObj) { e0 = new EShadowRO((EJSObj) e0); }
final Element r = new EEval(object, method, e0);
return reduce(r, param);
}
}
}
Element reduce(final Element left, final List<Element> param) {
Element r = left;
while(param.size() > 0) {
Element e0 = param.remove(0);
if(e0 instanceof EJSObj) { e0 = new EShadowRO((EJSObj) e0); }
r = new EReduce(r, e0);
}
return r;
}
List<Element> parseTuple(final Tuple t) {
final ArrayList<Element> list = new ArrayList<Element>();
final ArrayList<Element> expr = new ArrayList<Element>();
if(t.f1.present()) {
final Params p = (Params) t.f1.node;
List<Element> el = parseCall(p.f0);
if(el.size() > 1) { throw new RuntimeException("Parse Error"); }
list.add(el.get(0));
for(final Node n: p.f1.nodes) {
el =parseCall((Expression) (((NodeSequence) n).nodes.get(1)));
if(el.size() > 1) { throw new RuntimeException("Parse Error"); }
list.add(el.get(0));
}
}
expr.add(new ETuple(list));
if(t.f3.present()) {
expr.addAll(parseCall((Expression) t.f3.node));
}
return expr;
}
Object parseLiteral(final Literal l) {
final String s = l.f0.choice.toString();
if(l.f0.which == 0) {
return Long.parseLong(s);
} else if(l.f0.which == 1) {
return Long.parseLong(s.substring(2), 16);
} else if(l.f0.which == 2) {
return Double.parseDouble(s);
} else if(l.f0.which == 3) {
return s.substring(1, s.length() - 1).replaceAll("\\\\\"", "\"").replaceAll("\\\\", "\\");
} else if(l.f0.which == 4) {
return s.substring(1, s.length() - 1).replaceAll("\\\\'", "'").replaceAll("\\\\", "\\");
} else if(l.f0.which == 5) {
return ("true".equals(s)) ? Boolean.TRUE : Boolean.FALSE;
}
throw new RuntimeException("Error parsing literal: " + l.f0.choice.toString());
}
}