// License: GPL. For details, see LICENSE file. options { STATIC = false; } PARSER_BEGIN(MapCSSParser) package org.openstreetmap.josm.gui.mappaint.mapcss.parser; import java.awt.Color; import java.util.ArrayList; import java.util.List; import org.openstreetmap.josm.gui.mappaint.mapcss.Condition; 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.Expression.FunctionExpression; import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.LiteralExpression; import org.openstreetmap.josm.tools.Pair; public class MapCSSParser { MapCSSStyleSource sheet; } PARSER_END(MapCSSParser) /************* * Token definitions */ 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"] | "\\\"" | "\\\\" )* "\"" > | < #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: "^" > | < COMMENT_START: "/*" > : COMMENT | < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from } TOKEN: { < COMMENT_END: "*/" > : DEFAULT } SKIP: { < ~[] > } /************* * 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". * */ int uint() : { Token i; } { i= { return Integer.parseInt(i.image); } } 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 string_or_ident() : { Token t; String s; } { t= { return t.image; } | 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; Token com = null; } { { this.sheet = sheet; } w() ( try { r=rule() { if (r != null) { sheet.rules.add(r); } } w() } catch (ParseException ex) { error_skipto(RBRACE); w(); } )* } MapCSSRule rule(): { List selectors = new ArrayList(); Selector sel; List decl; } { sel=selector() { selectors.add(sel); } w() ( w() sel=selector() { selectors.add(sel); } w() )* decl=declaration() { return new MapCSSRule(selectors, decl); } } Selector selector() : { Token base; Condition c; Pair r = null; List conditions = new ArrayList(); String sub = null; } { ( base= | base= ) ( r=zoom() )? ( ( c=condition() | c=pseudoclass() ) { conditions.add(c); } )* ( sub=subpart() )? { return new Selector(base.image, r, conditions, sub); } } Pair zoom() : { Integer min = 0; Integer max = Integer.MAX_VALUE; } { ( max=uint() | min=uint() ( ( max=uint() )? )? ) { return new Pair(min, max); } } Condition condition() : { Condition c; Expression e; } { ( LOOKAHEAD(3) c=simple_key_condition() { return c; } | LOOKAHEAD(5) c=simple_key_value_condition() { return c; } | e=expression() { return new Condition.ExpressionCondition(e); } ) } Condition simple_key_condition() : { boolean not = false; boolean yes = false; String key; } { ( { not = true; } )? key=string_or_ident() ( { yes = true; } )? { return new Condition.KeyCondition(key, not, yes); } } Condition simple_key_value_condition() : { String key; String val; float f; Condition.Op op; } { key=string_or_ident() ( ( { op=Condition.Op.NEQ; } val=string_or_ident() | { op=Condition.Op.EQ; } ( { op=Condition.Op.REGEX; } val=regex() | val=string_or_ident() ) | { op=Condition.Op.ONE_OF; } val=string_or_ident() | { op=Condition.Op.BEGINS_WITH; } val=string_or_ident() | { op=Condition.Op.ENDS_WITH; } val=string_or_ident() | { op=Condition.Op.CONTAINS; } val=string_or_ident() ) { return new Condition.KeyValueCondition(key, val, op); } | ( { op=Condition.Op.GREATER_OR_EQUAL; } | { op=Condition.Op.GREATER; } | { op=Condition.Op.LESS_OR_EQUAL; } | { op=Condition.Op.LESS; } ) f=float_() { return new Condition.KeyValueCondition(key, Float.toString(f), op); } ) } Condition pseudoclass() : { Token t; boolean not = false; } { ( { not = true; } )? t= { return new Condition.PseudoClassCondition(t.image, not); } } String subpart() : { Token t; } { ( t= | t= ) { return t.image; } } List declaration() : { List ins = new ArrayList(); Instruction i; Token key; Object val; } { w() ( key= w() w() ( LOOKAHEAD( float_array() w() ( | ) ) val=float_array() { ins.add(new Instruction.AssignmentInstruction(key.image, val)); } w() ( { return ins; } | w() ) | LOOKAHEAD( expression() ( | ) ) val=expression() { ins.add(new Instruction.AssignmentInstruction(key.image, val)); } ( { return ins; } | w() ) | val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val)); } ) )* { 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 new FunctionExpression(op, args); } } Expression primary() : { Expression nested; FunctionExpression fn; Object lit; } { LOOKAHEAD(2) // both function and identifier start with an identifier fn=function() { return fn; } | lit=literal() { return new LiteralExpression(lit); } | w() nested=expression() { return nested; } } FunctionExpression function() : { Token tmp; Expression arg; String name; List args = new ArrayList(); } { tmp= { name = tmp.image; } w() w() ( arg=expression() { args.add(arg); } ( w() arg=expression() { args.add(arg); } )* )? { return new FunctionExpression(name, args); } } Object literal() : { Object val; Token t; float f; } { val=string_or_ident() { return val; } | f=ufloat() { return new Instruction.RelativeFloat(f); } | f=ufloat() { return f; } | t= { String clr = t.image.substring(1); if (clr.length() == 3) { clr = new String(new char[] {clr.charAt(0),clr.charAt(0),clr.charAt(1),clr.charAt(1),clr.charAt(2),clr.charAt(2)}); } if (clr.length() != 6) throw new AssertionError(); return new Color(Integer.parseInt(clr, 16)); } } JAVACODE void error_skipto(int kind) { if (token.kind == EOF) throw new ParseException("Reached end of file while parsing"); ParseException e = generateParseException(); System.err.println("Skipping to the next rule, because of an error:"); System.err.println(e); if (sheet != null) { sheet.logError(new ParseException(e.getMessage())); } 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)); System.err.println(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(); }