// License: GPL. For details, see LICENSE file.
options {
STATIC = false;
OUTPUT_DIRECTORY = "parsergen";
}
PARSER_BEGIN(MapCSSParser)
package org.openstreetmap.josm.gui.mappaint.mapcss.parsergen;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import org.openstreetmap.josm.gui.mappaint.Keyword;
import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
import org.openstreetmap.josm.tools.ColorHelper;
import org.openstreetmap.josm.tools.Pair;
import org.openstreetmap.josm.tools.Utils;
import org.openstreetmap.josm.Main;
/*************
*
* Parser definitions
*
* rule
* _______________________|______________________________
* | |
* selector declaration
* _________|___________________ _________|____________
* | | | |
*
* way|z11-12[highway=residential] { color: red; width: 3 }
*
* |_____||___________________| |_________|
* | | |
* zoom condition instruction
*
* more general:
*
* way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
*
* 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
*
* {@literal @media} { ... } queries are supported, following http://www.w3.org/TR/css3-mediaqueries/#syntax
*
* media_query
* ___________________________|_______________________________
* | |
* {@literal @media} all and (min-josm-version: 7789) and (max-josm-version: 7790), all and (user-agent: xyz) { ... }
* |______________________|
* |
* media_expression
*
*/
public class MapCSSParser {
MapCSSStyleSource sheet;
}
PARSER_END(MapCSSParser)
/*************
* Token definitions
*/
TOKEN [IGNORE_CASE]:
{
/* Special keywords in some contexts, ordinary identifiers in other contexts.
Don't use the javacc token contexts (except DEFAULT and COMMENT) because
they complicate error handling. Instead simply add a parsing rule ident()
that parses the IDENT token and in addition all the keyword tokens. */
< AND: "and" >
| < SET: "set" >
| < NOT: "not" >
| < MEDIA: "@media" >
}
TOKEN:
{
< IDENT: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","-","0"-"9"] )* >
| < UINT: ["1"-"9"] ( ["0"-"9"] )* >
| < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
| < STRING: "\"" ( [" ","!","#"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\\"" | "\\\\" )* "\"" >
| < #PREDEFINED: "\\" ["d","D","s","S","w","W"] >
| < #REGEX_CHAR_WITHOUT_STAR: [" "-")","+"-".","0"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\/" | "\\\\" | "\\[" | "\\]" | "\\+" | "\\." | "\\'" | "\\\"" | >
| < REGEX: "/" ( | "*" )* "/" >
| < #H: ["0"-"9","a"-"f","A"-"F"] >
| < HEXCOLOR: "#" ( | | ) >
| < S: ( " " | "\t" | "\n" | "\r" | "\f" )+ >
| < STAR: "*" >
| < SLASH: "/" >
| < LBRACE: "{" >
| < RBRACE: "}" >
| < LSQUARE: "[" >
| < RSQUARE: "]" >
| < LPAR: "(" >
| < RPAR: ")" >
| < GREATER_EQUAL: ">=" >
| < LESS_EQUAL: "<=" >
| < GREATER: ">" >
| < LESS: "<" >
| < EQUAL: "=" >
| < EXCLAMATION: "!" >
| < TILDE: "~" >
| < COLON: ":" >
| < DCOLON: "::" >
| < SEMICOLON: ";" >
| < COMMA: "," >
| < PIPE: "|" >
| < PIPE_Z: "|z" >
| < PLUS: "+" >
| < MINUS: "-" >
| < AMPERSAND: "&" >
| < QUESTION: "?" >
| < DOLLAR: "$" >
| < CARET: "^" >
| < FULLSTOP: "." >
| < ELEMENT_OF: "∈" >
| < CROSSING: "⧉" >
| < COMMENT_START: "/*" > : COMMENT
| < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
}
TOKEN:
{
< COMMENT_END: "*/" > : DEFAULT
}
SKIP:
{
< ~[] >
}
int uint() :
{
Token i;
}
{
i= { return Integer.parseInt(i.image); }
}
int int_() :
{
int i;
}
{
i=uint() { return -i; } | i=uint() { return i; }
}
float ufloat() :
{
Token f;
}
{
( f= | f= )
{ return Float.parseFloat(f.image); }
}
float float_() :
{
float f;
}
{
f=ufloat() { return -f; } | f=ufloat() { return f; }
}
String string() :
{
Token t;
}
{
t=
{ return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
}
String ident():
{
Token t;
String s;
}
{
( t= | t= | t= | t= ) { return t.image; }
}
String string_or_ident() :
{
Token t;
String s;
}
{
( s=ident() | s=string() ) { return s; }
}
String regex() :
{
Token t;
}
{
t=
{ return t.image.substring(1, t.image.length() - 1); }
}
/**
* white-space
*/
void s() :
{
}
{
( )?
}
/**
* mix of white-space and comments
*/
void w() :
{
}
{
( | )*
}
/**
* comma delimited list of floats (at least 2, all >= 0)
*/
List float_array() :
{
float f;
List fs = new ArrayList();
}
{
f=ufloat() { fs.add(f); }
(
s()
f=ufloat() { fs.add(f); }
)+
{
return fs;
}
}
/**
* root
*/
void sheet(MapCSSStyleSource sheet):
{
MapCSSRule r;
}
{
{ this.sheet = sheet; }
w()
rules(true) ( media() w() rules(true) )*
}
void media():
{
boolean pass = false;
boolean q;
boolean empty = true;
}
{
w()
( q=media_query() { pass = pass || q; empty = false; }
( w() q=media_query() { pass = pass || q; } )*
)?
w()
rules(empty || pass)
}
boolean media_query():
{
Token t;
String mediatype = "all";
boolean pass = true;
boolean invert = false;
boolean e;
}
{
( { invert = true; } w() )?
(
t= { mediatype = t.image.toLowerCase(); } w()
( w() e=media_expression() { pass = pass && e; } w() )*
|
e=media_expression() { pass = pass && e; } w()
( w() e=media_expression() { pass = pass && e; } w() )*
)
{
if (!"all".equals(mediatype)) {
pass = false;
}
return invert ? (!pass) : pass;
}
}
boolean media_expression():
{
Token t;
String feature;
Object val = null;
}
{
w() t= { feature = t.image; } w() ( w() val=literal() )?
{ return this.sheet.evalMediaExpression(feature, val); }
}
void rules(boolean add):
{
MapCSSRule r;
}
{
(
try {
r=rule() { if (add && r != null) { sheet.rules.add(r); } } w()
} catch (MapCSSException mex) {
error_skipto(RBRACE, mex);
w();
} catch (ParseException ex) {
error_skipto(RBRACE, null);
w();
}
)*
}
MapCSSRule rule():
{
List selectors = new ArrayList();
Selector sel;
List decl;
}
{
sel=child_selector() { selectors.add(sel); }
(
w()
sel=child_selector() { selectors.add(sel); }
)*
decl=declaration()
{ return new MapCSSRule(selectors, decl); }
}
Selector child_selector() :
{
Selector.ChildOrParentSelectorType type = null;
Condition c;
List conditions = new ArrayList();
Selector selLeft;
LinkSelector selLink = null;
Selector selRight = null;
}
{
selLeft=selector() w()
(
(
( { type = Selector.ChildOrParentSelectorType.CHILD; } | { type = Selector.ChildOrParentSelectorType.PARENT; } )
( ( c=condition(Context.LINK) | c=class_or_pseudoclass(Context.LINK) ) { conditions.add(c); } )*
|
{ type = Selector.ChildOrParentSelectorType.ELEMENT_OF; }
|
{ type = Selector.ChildOrParentSelectorType.CROSSING; }
)
{ selLink = new LinkSelector(conditions); }
w()
selRight=selector() w()
)?
{ return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, type) : selLeft; }
}
Selector selector() :
{
Token base;
Condition c;
Pair r = null;
List conditions = new ArrayList();
String sub = null;
}
{
( base= | base= )
( r=zoom() )?
( ( c=condition(Context.PRIMITIVE) | c=class_or_pseudoclass(Context.PRIMITIVE) ) { conditions.add(c); } )*
( sub=subpart() )?
{ return new GeneralSelector(base.image, r, conditions, sub); }
}
Pair zoom() :
{
Integer min = 0;
Integer max = Integer.MAX_VALUE;
}
{
(
max=uint()
|
LOOKAHEAD(2)
min=uint() ( max=uint() )?
|
min=uint() { max = min; }
)
{ return new Pair(min, max); }
}
Condition condition(Context context) :
{
Condition c;
Expression e;
}
{
s()
(
LOOKAHEAD( simple_key_condition(context) s() )
c=simple_key_condition(context) s() { return c; }
|
LOOKAHEAD( simple_key_value_condition(context) s() )
c=simple_key_value_condition(context) s() { return c; }
|
e=expression() { return Condition.createExpressionCondition(e, context); }
)
}
String tag_key() :
{
String s, s2;
Token t;
}
{
s=string() { return s; }
|
s=ident() ( s2=ident() { s += ':' + s2; } )* { return s; }
}
Condition simple_key_condition(Context context) :
{
boolean not = false;
Condition.KeyMatchType matchType = null;;
String key;
}
{
( { not = true; } )?
(
{ matchType = Condition.KeyMatchType.REGEX; } key = regex()
|
key = tag_key()
)
( LOOKAHEAD(2) { matchType = Condition.KeyMatchType.FALSE; } )?
( { matchType = Condition.KeyMatchType.TRUE; } )?
{ return Condition.createKeyCondition(key, not, matchType, context); }
}
Condition simple_key_value_condition(Context context) :
{
String key;
String val;
float f;
int i;
Condition.Op op;
boolean considerValAsKey = false;
}
{
key=tag_key() s()
(
LOOKAHEAD(3)
(
{ op=Condition.Op.REGEX; }
|
{ op=Condition.Op.NREGEX; }
)
s()
( { considerValAsKey=true; } )?
val=regex()
|
(
{ op=Condition.Op.NEQ; }
|
{ op=Condition.Op.EQ; }
|
{ op=Condition.Op.ONE_OF; }
|
{ op=Condition.Op.BEGINS_WITH; }
|
{ op=Condition.Op.ENDS_WITH; }
|
{ op=Condition.Op.CONTAINS; }
)
s()
( { considerValAsKey=true; } )?
(
LOOKAHEAD(2)
i=int_() { val=Integer.toString(i); }
|
f=float_() { val=Float.toString(f); }
|
val=string_or_ident()
)
|
(
{ op=Condition.Op.GREATER_OR_EQUAL; }
|
{ op=Condition.Op.GREATER; }
|
{ op=Condition.Op.LESS_OR_EQUAL; }
|
{ op=Condition.Op.LESS; }
)
s()
f=float_() { val=Float.toString(f); }
)
{ return Condition.createKeyValueCondition(key, val, op, context, considerValAsKey); }
}
Condition class_or_pseudoclass(Context context) :
{
String s;
boolean not = false;
boolean pseudo;
}
{
( { not = true; } )?
(
{ pseudo = false; }
|
{ pseudo = true; }
)
s=ident()
{ return pseudo
? Condition.createPseudoClassCondition(s, not, context)
: Condition.createClassCondition(s, not, context); }
}
String subpart() :
{
String s;
}
{
( s=ident() { return s; } | { return "*"; } )
}
List declaration() :
{
List ins = new ArrayList();
Instruction i;
Token key;
Object val = null;
}
{
w()
(
(
w()
()? // specification allows "set .class" to set "class". we also support "set class"
key= w()
( val=expression() )?
{ ins.add(new Instruction.AssignmentInstruction(key.image, val == null ? true : val, true)); }
( { return ins; } | w() )
)
|
key= w() w()
(
LOOKAHEAD( float_array() w() ( | ) )
val=float_array()
{ ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
w()
( { return ins; } | w() )
|
LOOKAHEAD( expression() ( | ) )
val=expression()
{ ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
( { return ins; } | w() )
|
val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
)
)*
{ return ins; }
}
Expression expression():
{
List args = new ArrayList();
Expression e;
String op = null;
}
{
(
{ op = "not"; } w() e=primary() { args.add(e); } w()
|
{ op = "minus"; } w() e=primary() { args.add(e); } w()
|
(
e=primary() { args.add(e); } w()
(
( { op = "plus"; } w() e=primary() { args.add(e); } w() )+
|
( { op = "times"; } w() e=primary() { args.add(e); } w() )+
|
( { op = "minus"; } w() e=primary() { args.add(e); } w() )+
|
( { op = "divided_by"; } w() e=primary() { args.add(e); } w() )+
|
{ op = "greater_equal"; } w() e=primary() { args.add(e); } w()
|
{ op = "less_equal"; } w() e=primary() { args.add(e); } w()
|
{ op = "greater"; } w() e=primary() { args.add(e); } w()
|
( )? { op = "equal"; } w() e=primary() { args.add(e); } w()
|
{ op = "less"; } w() e=primary() { args.add(e); } w()
|
{ op = "and"; } w() e=primary() { args.add(e); } w()
|
{ op = "or"; } w() e=primary() { args.add(e); } w()
|
{ op = "cond"; } w() e=primary() { args.add(e); } w() w() e=primary() { args.add(e); } w()
)?
)
)
{
if (op == null)
return args.get(0);
return ExpressionFactory.createFunctionExpression(op, args);
}
}
Expression primary() :
{
Expression nested;
Expression fn;
Object lit;
}
{
LOOKAHEAD(3) // both function and identifier start with an identifier (+ optional whitespace)
fn=function() { return fn; }
|
lit=literal() { return new LiteralExpression(lit); }
|
w() nested=expression() { return nested; }
}
Expression function() :
{
Token tmp;
Expression arg;
String name;
List args = new ArrayList();
}
{
name=ident() w()
w()
(
arg=expression() { args.add(arg); }
( w() arg=expression() { args.add(arg); } )*
)?
{ return ExpressionFactory.createFunctionExpression(name, args); }
}
Object literal() :
{
String val;
Token t, t2;
float f;
}
{
LOOKAHEAD(2)
t2= t=
{ return Main.pref.getColor("mappaint." + (sheet == null ? "MapCSS" : sheet.title) + "." + t2.image, ColorHelper.html2color(t.image)); }
|
t= { return new Keyword(t.image); }
|
val=string() { return val; }
|
f=ufloat() { return new Instruction.RelativeFloat(f); }
|
f=ufloat() { return f; }
|
t= { return ColorHelper.html2color(t.image); }
}
JAVACODE
void error_skipto(int kind, MapCSSException me) {
if (token.kind == EOF)
throw new ParseException("Reached end of file while parsing");
Exception e = null;
ParseException pe = generateParseException();
if (me != null) {
me.setLine(pe.currentToken.next.beginLine);
me.setColumn(pe.currentToken.next.beginColumn);
e = me;
} else {
e = new ParseException(pe.getMessage()); // prevent memory leak
}
Main.error("Skipping to the next rule, because of an error:");
Main.error(e);
if (sheet != null) {
sheet.logError(e);
}
Token t;
do {
t = getNextToken();
} while (t.kind != kind && t.kind != EOF);
if (t.kind == EOF)
throw new ParseException("Reached end of file while parsing");
}
JAVACODE
/**
* read everything to the next semicolon
*/
String readRaw() {
Token t;
StringBuilder s = new StringBuilder();
while (true) {
t = getNextToken();
if ((t.kind == S || t.kind == STRING || t.kind == UNEXPECTED_CHAR) &&
t.image.contains("\n")) {
ParseException e = new ParseException(String.format("Warning: end of line while reading an unquoted string at line %s column %s.", t.beginLine, t.beginColumn));
Main.error(e);
if (sheet != null) {
sheet.logError(e);
}
}
if (t.kind == SEMICOLON || t.kind == EOF)
break;
s.append(t.image);
}
if (t.kind == EOF)
throw new ParseException("Reached end of file while parsing");
return s.toString();
}