/*
* Copyright (C) 2012 Timo Vesalainen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.vesalainen.parsers.sql.dsql;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Category;
import com.google.appengine.api.datastore.Email;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.GeoPt;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Link;
import com.google.appengine.api.datastore.PhoneNumber;
import com.google.appengine.api.datastore.PostalAddress;
import com.google.appengine.api.datastore.Rating;
import com.google.appengine.api.datastore.ShortBlob;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.users.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.vesalainen.bcc.model.El;
import org.vesalainen.parser.GenClassFactory;
import org.vesalainen.parser.ParserCompiler;
import org.vesalainen.parser.ParserConstants;
import org.vesalainen.parser.ParserInfo;
import org.vesalainen.parser.Trace;
import org.vesalainen.parser.TraceHelper;
import org.vesalainen.parser.annotation.GenClassname;
import org.vesalainen.parser.annotation.GenRegex;
import org.vesalainen.parser.annotation.GrammarDef;
import org.vesalainen.parser.annotation.ParseMethod;
import org.vesalainen.parser.annotation.ParserContext;
import org.vesalainen.parser.annotation.ReservedWords;
import org.vesalainen.parser.annotation.Rule;
import org.vesalainen.parser.annotation.Rules;
import org.vesalainen.parser.annotation.Terminal;
import org.vesalainen.parser.util.InputReader;
import org.vesalainen.parser.util.OffsetLocatorException;
import org.vesalainen.parsers.sql.Relation;
import org.vesalainen.parsers.sql.ColumnReferenceImpl;
import org.vesalainen.parsers.sql.Condition;
import org.vesalainen.parsers.sql.Engine;
import org.vesalainen.parsers.sql.Literal;
import org.vesalainen.parsers.sql.LiteralImpl;
import org.vesalainen.parsers.sql.RowValue;
import org.vesalainen.parsers.sql.SQLLocator;
import org.vesalainen.parsers.sql.SqlParser;
import org.vesalainen.parsers.sql.Table;
import org.vesalainen.regex.Regex;
/**
* @author Timo Vesalainen
* @see <a href="doc-files/DSQLParser-statement.html#BNF">BNF Syntax for DSQL-statement</a>
* <p>
* Functions
* @see Engine.createFunction
* @see DSQLEngine.createFunction
*/
@GenClassname("org.vesalainen.parsers.sql.dsql.DSQLParserImpl")
@GrammarDef()
public abstract class DSQLParser extends SqlParser<Entity,Object> implements ParserInfo
{
@GenRegex("\\$\\{[^\\}]+\\}")
public static Regex dollarTag;
private static Map<String,Class<?>> googleTypeMap = new HashMap<>();
static
{
addGoogleType(Integer.class);
addGoogleType(Long.class);
addGoogleType(Double.class);
addGoogleType(Boolean.class);
addGoogleType(Date.class);
addGoogleType(Category.class);
addGoogleType(Email.class);
addGoogleType(Link.class);
addGoogleType(PhoneNumber.class);
addGoogleType(PostalAddress.class);
addGoogleType(Rating.class);
addGoogleType(Blob.class);
addGoogleType(ShortBlob.class);
addGoogleType(Text.class);
addGoogleType(Key.class);
addGoogleType(User.class);
addGoogleType(GeoPt.class);
}
public static DSQLParser getInstance()
{
return (DSQLParser) GenClassFactory.loadGenInstance(DSQLParser.class);
}
private static void addGoogleType(Class<?> aClass)
{
googleTypeMap.put(aClass.getSimpleName().toLowerCase(), aClass);
}
/**
*
* @param text
* @return
* @see <a href="doc-files/DSQLParser-coordinate.html#BNF">BNF Syntax for Geological Coordinate</a>
*/
@ParseMethod(start="coordinate", whiteSpace ="whiteSpace")
public abstract GeoPt parseCoordinate(String text, @ParserContext("locator") SQLLocator locator) throws OffsetLocatorException;
@Rule(left="comparisonPredicate", value="rowValuePredicant is key of identifier")
protected Condition comparisonKeyOf(
RowValue rv1,
String identifier,
@ParserContext("engine") Engine<Entity,Object> engine,
@ParserContext("tableListStack") Deque<List<Table<Entity,Object>>> tableListStack
)
{
List<String> list = new ArrayList<>();
list.add(identifier);
list.add(Entity.KEY_RESERVED_PROPERTY);
Condition<Object, Object> comparisonCondition = newComparisonCondition(rv1, Relation.EQ, new ColumnReferenceImpl(list), tableListStack.peek());
return comparisonCondition;
}
@Rule(left="comparisonPredicate", value="identifier is ancestor of identifier")
protected Condition comparisonAncestorOf(String ancestor, String descendant)
{
return new AncestorOfCondition(ancestor, descendant);
}
@Rule(left="comparisonPredicate", value="identifier is parent of identifier")
protected Condition comparisonParentOf(
String parent,
String child,
@ParserContext("engine") Engine<Entity,Object> engine
)
{
Condition<Entity, Object> parentOfCondition = new ParentOfCondition(parent, child);
return parentOfCondition;
}
@Rule(left="rowValuePredicant", value="key")
protected RowValue keyPredicant1(
@ParserContext("engine") Engine engine
)
{
List<String> list = new ArrayList<>();
list.add(Entity.KEY_RESERVED_PROPERTY);
return new ColumnReferenceImpl<>(list);
}
@Rule(left="rowValuePredicant", value="identifier '\\.' key")
protected RowValue keyPredicant2(
String id1,
@ParserContext("engine") Engine engine
)
{
List<String> list = new ArrayList<>();
list.add(id1);
list.add(Entity.KEY_RESERVED_PROPERTY);
return new ColumnReferenceImpl<>(list);
}
@Rule("key")
protected String column()
{
return Entity.KEY_RESERVED_PROPERTY;
}
@Rule(value="identifier", doc="one of java/google type: integer, long, double, date, category, email, link, phonenumber, postaladdress, rating, blob, shortblob, text, key, user, geopt")
protected Class<?> placeholderType(
String typeName,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader
)
{
Class<?> type = googleTypeMap.get(typeName.toLowerCase());
if (type == null)
{
reader.throwSyntaxErrorException("Key", typeName);
}
return type;
}
@Rule("key '\\(' keyValue '\\)'")
protected Literal<Entity, Object> literal(
Key key,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader
)
{
return new LiteralImpl<Entity, Object>(key);
}
@Rule("identifier '\\(' string '\\)'")
protected Key keyValue(String kind, String name, @ParserContext("engine") Engine<Entity,Object> engine)
{
DSQLEngine dse = (DSQLEngine) engine;
return dse.createKey(kind, name);
}
@Rule("identifier '\\(' integer '\\)'")
protected Key keyValue(String kind, Number id, @ParserContext("engine") Engine<Entity,Object> engine)
{
DSQLEngine dse = (DSQLEngine) engine;
return dse.createKey(kind, id.longValue());
}
@Rule("keyValue '/' identifier '\\(' string '\\)'")
protected Key keyValue(Key parent, String kind, String name, @ParserContext("engine") Engine<Entity,Object> engine)
{
DSQLEngine dse = (DSQLEngine) engine;
return dse.createKey(parent, kind, name);
}
@Rule("keyValue '/' identifier '\\(' integer '\\)'")
protected Key keyValue(Key parent, String kind, Number id, @ParserContext("engine") Engine<Entity,Object> engine)
{
DSQLEngine dse = (DSQLEngine) engine;
return dse.createKey(parent, kind, id.longValue());
}
@Rule("identifier '\\(' string ('\\,' string)* '\\)'")
protected Literal<Entity, Object> literal(
String typeName,
String string,
List<String> list,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader
)
{
list.add(0, string);
try
{
Class<?> type = googleTypeMap.get(typeName.toLowerCase());
Object obj = GObjectHelper.valueOf(type, list.toArray(new String[list.size()]));
return new LiteralImpl<>(obj);
}
catch (Exception ex)
{
reader.throwSyntaxErrorException("Long, Double, Boolean, Category, Email, Link, PhoneNumber, Text, User", typeName);
}
return null;
}
@Rule("identifier '\\(' integer '\\)'")
protected Literal<Entity, Object> literal(
String typeName,
Number number,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader
)
{
try
{
Class<?> type = googleTypeMap.get(typeName.toLowerCase());
Constructor constructor = type.getConstructor(int.class);
Object newInstance = constructor.newInstance(number.intValue());
return new LiteralImpl<>(newInstance);
}
catch (NullPointerException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex)
{
reader.throwSyntaxErrorException("Long, Rating", typeName);
}
return null;
}
@Rule("identifier '\\(' coordinate '\\)'")
protected Literal<Entity, Object> literal(
String typeName,
GeoPt pt,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader
)
{
Class<?> type = googleTypeMap.get(typeName.toLowerCase());
if (!GeoPt.class.equals(type))
{
reader.throwSyntaxErrorException("GeoPt", typeName);
}
return new LiteralImpl<Entity, Object>(pt);
}
@Rule("decimal '\\,' decimal")
protected GeoPt coordinate(Number lat, Number lon)
{
return new GeoPt(lat.floatValue(), lon.floatValue());
}
@Rule("ns latitude '\\,' we longitude")
protected GeoPt coordinate(int ns, Number lat, int we, Number lon)
{
return new GeoPt(ns*lat.floatValue(), we*lon.floatValue());
}
@Rule("integer degreeChar? decimal secondChar?")
protected Number latitude(Number degree, Number minutes,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader)
{
double deg = degree.doubleValue();
double min = minutes.doubleValue();
double d = deg + min/60.0;
if (d < 0 || d > 90 || min < 0 || min > 60)
{
reader.throwSyntaxErrorException("latitude coordinate", String.valueOf(d));
}
return new Double(d);
}
@Rule("integer degreeChar? integer secondChar? integer minuteChar?")
protected Number latitude(Number degree, Number minutes, Number seconds,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader)
{
double deg = degree.doubleValue();
double min = minutes.doubleValue();
double sec = seconds.doubleValue();
double d = deg + min/60.0 + sec/3600.0;
if (d < 0 || d > 90 || min < 0 || min > 60 || sec < 0 || sec > 60)
{
reader.throwSyntaxErrorException("latitude coordinate", String.valueOf(d));
}
return new Double(d);
}
@Rule("integer degreeChar? decimal secondChar?")
protected Number longitude(Number degree, Number minutes,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader)
{
double deg = degree.doubleValue();
double min = minutes.doubleValue();
double d = deg + min/60.0;
if (d < 0 || d > 180 || min < 0 || min > 60)
{
reader.throwSyntaxErrorException("longitude coordinate", String.valueOf(d));
}
return new Double(d);
}
@Rule("integer degreeChar? integer secondChar? integer minuteChar?")
protected Number longitude(Number degree, Number minutes, Number seconds,
@ParserContext(ParserConstants.INPUTREADER) InputReader reader)
{
double deg = degree.doubleValue();
double min = minutes.doubleValue();
double sec = seconds.doubleValue();
double d = deg + min/60.0 + sec/3600.0;
if (d < 0 || d > 180 || min < 0 || min > 60 || sec < 0 || sec > 60)
{
reader.throwSyntaxErrorException("longitude coordinate", String.valueOf(d));
}
return new Double(d);
}
@Terminal(expression="\u00b0")
protected abstract void degreeChar();
@Terminal(expression="\"")
protected abstract void minuteChar();
@Terminal(expression="'")
protected abstract void secondChar();
@Rules({
@Rule("north"),
@Rule("south")
})
protected abstract int ns(int sign);
@Rules({
@Rule("west"),
@Rule("east")
})
protected abstract int we(int sign);
@Rule("n")
protected int north()
{
return 1;
}
@Rule("e")
protected int east()
{
return 1;
}
@Rule("s")
protected int south()
{
return -1;
}
@Rule("w")
protected int west()
{
return -1;
}
@ReservedWords(value =
{
"n",
"s",
"w",
"e",
"key",
"of",
"parent",
"ancestor"
},
options =
{
Regex.Option.CASE_INSENSITIVE
})
protected void reservedWordsD(
@ParserContext(ParserConstants.INPUTREADER) InputReader reader,
@ParserContext("locator") SQLLocator locator
)
{
reservedWords(reader, locator);
}
//@TraceMethod
protected void trace(
int ord,
int ctx,
@ParserContext("$inputReader") InputReader reader,
@ParserContext("$token") int token,
@ParserContext("$laToken") int laToken,
@ParserContext("$curTok") int curtok,
@ParserContext("$stateStack") int[] stack,
@ParserContext("$sp") int sp,
@ParserContext("$typeStack") int[] typeStack,
@ParserContext("$valueStack") Object[] valueStack
)
{
Trace trace = Trace.values()[ord];
switch (trace)
{
case STATE:
System.err.println("state "+stack[sp]);
break;
case INPUT:
if (ctx >= 0)
{
System.err.println("input"+ctx+"='"+reader.getString()+"' token="+getToken(token));
}
else
{
System.err.println("re input='"+reader.getString()+"' token="+getToken(token));
}
break;
case LAINPUT:
if (ctx >= 0)
{
System.err.println("lainput"+ctx+"='"+reader.getString()+"' token="+getToken(laToken));
}
else
{
System.err.println("re lainput='"+reader.getString()+"' token="+getToken(laToken));
}
break;
case PUSHVALUE:
System.err.println("push value");
break;
case EXITLA:
System.err.println("exit La");
TraceHelper.printStacks(System.err, stack, typeStack, valueStack, sp);
break;
case BEFOREREDUCE:
System.err.println("Before reducing rule "+getRule(ctx));
TraceHelper.printStacks(System.err, stack, typeStack, valueStack, sp);
break;
case AFTERREDUCE:
System.err.println("After reducing rule "+getRule(ctx));
TraceHelper.printStacks(System.err, stack, typeStack, valueStack, sp);
break;
case GOTO:
System.err.println("Goto "+ctx);
break;
case SHIFT:
System.err.println("Shift "+ctx);
break;
case SHRD:
System.err.println("Shift/Reduce");
break;
case LASHRD:
System.err.println("La Shift/Reduce");
break;
case GTRD:
System.err.println("Goto/Reduce");
break;
case LASHIFT:
System.err.println("LaShift State "+ctx);
TraceHelper.printStacks(System.err, stack, typeStack, valueStack, sp);
break;
default:
System.err.println("unknown action "+trace);
break;
}
}
public static void main(String... args)
{
try
{
ParserCompiler pc = new ParserCompiler(El.getTypeElement(DSQLParser.class.getCanonicalName()));
pc.compile();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}