Parser.java
/***************************************************************************
Copyright 2015 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.sqltest;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.metanotion.sqltest.parser.ParseException;
import net.metanotion.sqltest.parser.TestLogParser;
import net.metanotion.sqltest.parser.syntaxtree.Apply;
import net.metanotion.sqltest.parser.syntaxtree.Arr;
import net.metanotion.sqltest.parser.syntaxtree.Capture;
import net.metanotion.sqltest.parser.syntaxtree.Executable;
import net.metanotion.sqltest.parser.syntaxtree.Expressions;
import net.metanotion.sqltest.parser.syntaxtree.Imports;
import net.metanotion.sqltest.parser.syntaxtree.Key;
import net.metanotion.sqltest.parser.syntaxtree.Literal;
import net.metanotion.sqltest.parser.syntaxtree.Node;
import net.metanotion.sqltest.parser.syntaxtree.NodeSequence;
import net.metanotion.sqltest.parser.syntaxtree.Obj;
import net.metanotion.sqltest.parser.syntaxtree.Params;
import net.metanotion.sqltest.parser.syntaxtree.SideEffect;
import net.metanotion.sqltest.parser.syntaxtree.SimpleValue;
import net.metanotion.sqltest.parser.syntaxtree.TestExpression;
import net.metanotion.sqltest.parser.syntaxtree.TestStatement;
import net.metanotion.sqltest.parser.syntaxtree.ValueExpr;
import net.metanotion.sqltest.parser.syntaxtree.ValueList;
import net.metanotion.sqltest.parser.syntaxtree.WithParam;
import net.metanotion.sqltest.parser.syntaxtree.WithParams;
final class Parser {
private static final Logger logger = LoggerFactory.getLogger(Parser.class);
private String parseKey(final Key k) {
final String s = k.f0.choice.toString();
switch(k.f0.which) {
case 0: // ID
return s;
case 1: // String
return s.substring(1, s.length() - 1).replaceAll("\\\\\"", "\"").replaceAll("\\\\", "\\");
case 2: // Single Quote string
return s.substring(1, s.length() - 1).replaceAll("\\\\'", "'").replaceAll("\\\\", "\\");
default:
throw new RuntimeException("Bad column name for row literal " + k.f0.choice.toString());
}
}
private void parseWP(final Map<String,Value> struct, final WithParam wp) {
final String key = parseKey(wp.f0);
final Value val = parseSimpleValue(wp.f2);
struct.put(key, val);
}
private Value parseObj(final Obj obj) {
logger.debug("Parsing Row");
final Map<String,Value> struct = new HashMap<>();
if(obj.f1.present()) {
final WithParams wParams = (WithParams) obj.f1.node;
parseWP(struct, wParams.f0);
for(final Node n: wParams.f1.nodes) {
parseWP(struct, (WithParam) ((NodeSequence) n).nodes.get(1));
}
}
return new RowValue(struct);
}
private Value parseArr(final Arr arr) {
logger.debug("Parsing List");
final ArrayList<Value> list = new ArrayList<>();
if(arr.f1.present()) {
final ValueList vList = (ValueList) arr.f1.node;
list.add(parseValue(vList.f0));
for(final Node n: vList.f1.nodes) {
list.add(parseValue((ValueExpr) ((NodeSequence) n).nodes.get(1)));
}
}
return new ListValue(list);
}
private Value parseValue(final ValueExpr val) {
switch(val.f0.which) {
case 0: // Literal
return parseLiteral((Literal) val.f0.choice);
case 1: // Obj/struct
return parseObj((Obj) val.f0.choice);
case 2: // Arr/list
return parseArr((Arr) val.f0.choice);
case 3: // environment look up.
return new VariableValue(val.f0.choice.toString());
default:
throw new RuntimeException("Bad Value " + val.f0.choice.toString());
}
}
private GenerateValue parseSQL(final String sql) { return new ApplySql(sql); }
private Value parseLiteral(final Literal lit) {
final String s = lit.f0.choice.toString();
switch(lit.f0.which) {
case 0: // Decimal literal
return new NumberValue(new BigDecimal(s));
case 1: // Float literal
return new NumberValue(new BigDecimal(s));
case 2: // String literal
return new StringValue(s.substring(1, s.length() - 1).replaceAll("\\\\\"", "\"").replaceAll("\\\\", "\\"));
case 3: // Single quote string literal.
return new StringValue(s.substring(1, s.length() - 1).replaceAll("\\\\'", "'").replaceAll("\\\\", "\\"));
case 4: // Boolean literal
return new BooleanValue("true".equals(s) ? Boolean.TRUE : Boolean.FALSE);
case 5: // Null literal.
return NullValue.INSTANCE;
case 6: // Error literal
logger.debug("Error Literal");
return ErrorValue.INSTANCE;
case 7: // Any literal
logger.debug("Any Literal");
return AnyValue.INSTANCE;
default:
throw new RuntimeException("Invalid literal " + s);
}
}
private Value parseSimpleValue(final SimpleValue sv) {
if(sv.f0.which == 0) {
return parseLiteral((Literal) sv.f0.choice);
} else {
return new VariableValue(sv.f0.choice.toString());
}
}
private GenerateValue parseExecutable(final Map<String,String> imports, final Executable exec) {
final String sql = exec.f0.choice.toString();
switch(exec.f0.which) {
case 0: // apply
return parseMethod(imports, (Apply) exec.f0.choice);
case 1: // string literal
return parseSQL(sql.substring(1, sql.length() - 1).replaceAll("\\\\\"", "\"").replaceAll("\\\\", "\\"));
case 2: // single quote string literal
return parseSQL(sql.substring(1, sql.length() - 1).replaceAll("\\\\'", "'").replaceAll("\\\\", "\\"));
default:
throw new RuntimeException("Bad executable");
}
}
private GenerateValue parseMethod(final Map<String, String> imports, final Apply apply) {
String klazz = (apply.f0.present()) ? ((NodeSequence) apply.f0.node).nodes.get(0).toString() : "";
final String impClass = imports.get(klazz);
if(impClass != null) { klazz = impClass; }
final String method = apply.f1.toString();
final ArrayList<Value> params = new ArrayList<>();
if(apply.f3.present()) {
final Params p = (Params) apply.f3.node;
params.add(parseSimpleValue(p.f0));
for(final Node n: p.f1.nodes) {
params.add(parseSimpleValue((SimpleValue) ((NodeSequence) n).nodes.get(1)));
}
}
return new ApplyMethod(klazz, method, params);
}
private Test parseImport(final Map<String,String> imports, final Imports i) {
String fullName = i.f1.toString();
String rename = fullName;
for(final Node n: i.f2.nodes) {
final NodeSequence ns = (NodeSequence) n;
rename = ns.nodes.get(1).toString();
fullName += "." + rename;
}
if(i.f3.present()) {
rename = ((NodeSequence) i.f3.node).nodes.get(1).toString();
}
logger.debug("Importing {} AS {}", fullName, rename);
imports.put(rename, fullName);
return null;
}
private Test parseTest(final Map<String,String> imports, final TestStatement stmt) {
switch (stmt.f0.which) {
case 0:
final SideEffect se = (SideEffect) stmt.f0.choice;
return new Exec(parseExecutable(imports, se.f1));
case 1:
final TestExpression te = (TestExpression) stmt.f0.choice;
return new Check(te.f0.beginLine, parseExecutable(imports, te.f1), parseValue(te.f3));
case 2:
final Capture c = (Capture) stmt.f0.choice;
return new Let(c.f1.toString(), parseExecutable(imports, c.f3));
case 3:
final Imports i = (Imports) stmt.f0.choice;
return parseImport(imports, i);
default:
throw new RuntimeException("Invalid Statement type");
}
}
public Iterable<Test> load(final Map<String,String> imports, final InputStream in) throws ParseException {
final TestLogParser parser = new TestLogParser(in);
final Expressions expressions = parser.Expressions();
final ArrayList<Test> tests = new ArrayList<>();
for(final Node n: expressions.f0.nodes) {
logger.debug("Expression");
final Test t = parseTest(imports, (TestStatement) n);
if(t!=null) { tests.add(t); }
}
return tests;
}
}