source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj @ 12987

Last change on this file since 12987 was 12987, checked in by bastiK, 18 months ago

see #15410 - change preferences scheme for named colors - makes runtime color name registry obsolete

  • Property svn:eol-style set to native
File size: 30.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2options {
3  STATIC = false;
4  OUTPUT_DIRECTORY = "parsergen";
5}
6
7PARSER_BEGIN(MapCSSParser)
8package org.openstreetmap.josm.gui.mappaint.mapcss.parsergen;
9
10import static org.openstreetmap.josm.tools.I18n.tr;
11
12import java.io.InputStream;
13import java.io.Reader;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collections;
17import java.util.List;
18import java.util.Locale;
19
20import org.openstreetmap.josm.data.preferences.NamedColorProperty;
21import org.openstreetmap.josm.gui.mappaint.Keyword;
22import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
23import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
24import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory;
25import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType;
26import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.Op;
27import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
28import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
29import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory.NullExpression;
30import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
31import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
32import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
33import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
34import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule.Declaration;
35import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
36import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
37import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
38import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
39import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
40import org.openstreetmap.josm.gui.mappaint.mapcss.Subpart;
41import org.openstreetmap.josm.tools.ColorHelper;
42import org.openstreetmap.josm.tools.JosmRuntimeException;
43import org.openstreetmap.josm.tools.Logging;
44import org.openstreetmap.josm.tools.Pair;
45import org.openstreetmap.josm.tools.Utils;
46
47/**
48 * MapCSS parser.
49 *
50 * Contains two independent grammars:
51 * (a) the preprocessor and (b) the main mapcss parser.
52 *
53 * The preprocessor handles @supports and @media syntax (@media is deprecated).
54 * Basically this allows to write one style for different versions of JOSM (or different editors).
55 * When the @supports condition is not fulfilled, it should simply skip over
56 * the whole section and not attempt to parse the possibly unknown
57 * grammar. It preserves whitespace and comments, in order to keep the
58 * line and column numbers in the error messages correct for the second pass.
59 *
60 */
61public class MapCSSParser {
62    MapCSSStyleSource sheet;
63    StringBuilder sb;
64    int declarationCounter;
65
66    /**
67     * Nicer way to refer to a lexical state.
68     */
69    public enum LexicalState {
70        /** the preprocessor */
71        PREPROCESSOR(0),
72        /** the main parser */
73        DEFAULT(2);
74
75        final int idx; // the integer, which javacc assigns to this state
76
77        LexicalState(int idx) {
78            if (!this.name().equals(MapCSSParserTokenManager.lexStateNames[idx])) {
79                throw new JosmRuntimeException("Wrong name for index " + idx);
80            }
81            this.idx = idx;
82        }
83    }
84   
85    /**
86     * Constructor which initializes the parser with a certain lexical state.
87     * @param in input
88     * @param encoding contents encoding
89     * @param initState initial state
90     */
91    public MapCSSParser(InputStream in, String encoding, LexicalState initState) {
92        this(createTokenManager(in, encoding, initState));
93        declarationCounter = 0;
94    }
95
96    protected static MapCSSParserTokenManager createTokenManager(InputStream in, String encoding, LexicalState initState) {
97        SimpleCharStream scs;
98        try {
99            scs = new SimpleCharStream(in, encoding, 1, 1);
100        } catch (java.io.UnsupportedEncodingException e) {
101            throw new JosmRuntimeException(e);
102        }
103        return new MapCSSParserTokenManager(scs, initState.idx);
104    }
105
106    /**
107     * Constructor which initializes the parser with a certain lexical state.
108     * @param in input
109     * @param initState initial state
110     */
111    public MapCSSParser(Reader in, LexicalState initState) {
112        this(createTokenManager(in, initState));
113        declarationCounter = 0;
114    }
115
116    protected static MapCSSParserTokenManager createTokenManager(Reader in, LexicalState initState) {
117        final SimpleCharStream scs = new SimpleCharStream(in, 1, 1);
118        return new MapCSSParserTokenManager(scs, initState.idx);
119    }
120}
121PARSER_END(MapCSSParser)
122
123/**
124 * Token definitions
125 *
126 * Lexical states for the preprocessor: <PREPROCESSOR>, <PP_COMMENT>
127 * Lexical states for the main parser: <DEFAULT>, <COMMENT>
128 */
129 
130<PREPROCESSOR>
131TOKEN:
132{
133    < PP_AND: "and" >
134|   < PP_OR: "or" >
135|   < PP_NOT: "not" >
136|   < PP_SUPPORTS: "@supports" >
137|   < PP_MEDIA: "@media" >
138|   < PP_NEWLINECHAR: "\n" | "\r" | "\f" >
139|   < PP_WHITESPACE: " " | "\t" >
140|   < PP_COMMENT_START: "/*" > : PP_COMMENT
141}
142
143<PP_COMMENT>
144TOKEN:
145{
146    < PP_COMMENT_END: "*/" > : PREPROCESSOR
147}
148
149<PP_COMMENT>
150MORE:
151{
152    < ~[] >
153}
154
155<DEFAULT>
156TOKEN [IGNORE_CASE]:
157{
158 /* Special keyword in some contexts, ordinary identifier in other contexts.
159    Use the parsing rule <code>ident()</code> to refer to a general
160    identifier, including "set". */
161   < SET: "set" >
162}
163
164<DEFAULT,PREPROCESSOR>
165TOKEN:
166{
167    < IDENT: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","-","0"-"9"] )* >
168|   < UINT: ( ["0"-"9"] )+ >
169|   < STRING: "\"" ( [" ","!","#"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\\"" | "\\\\" )*  "\"" >
170|   < #PREDEFINED: "\\" ["d","D","s","S","w","W","b","B","A","G","Z","z"] >
171|   < #REGEX_CHAR_WITHOUT_STAR: [" "-")","+"-".","0"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\/" | "\\\\" | "\\[" | "\\]" | "\\+" | "\\." | "\\'" | "\\\"" |  "\\(" | "\\)" | "\\{" | "\\}" | "\\?" | "\\*" | "\\^" | "\\$" | "\\|" | "\\p" |<PREDEFINED> >
172|   < REGEX: "/" <REGEX_CHAR_WITHOUT_STAR> ( <REGEX_CHAR_WITHOUT_STAR> | "*" )*  "/" >
173|   < LBRACE: "{" >
174|   < RBRACE: "}" >
175|   < LPAR: "(" >
176|   < RPAR: ")" >
177|   < COMMA: "," >
178|   < COLON: ":" >
179}
180
181<PREPROCESSOR>
182TOKEN:
183{
184    < PP_SOMETHING_ELSE : ~[] >
185}
186
187<DEFAULT>
188TOKEN:
189{
190    < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
191|   < #H: ["0"-"9","a"-"f","A"-"F"] >
192|   < HEXCOLOR: "#" ( <H><H><H><H><H><H><H><H> | <H><H><H><H><H><H> | <H><H><H> ) >
193|   < S: ( " " | "\t" | "\n" | "\r" | "\f" )+ >
194|   < STAR: "*" >
195|   < SLASH: "/" >
196|   < LSQUARE: "[" >
197|   < RSQUARE: "]" >
198|   < GREATER_EQUAL: ">=" >
199|   < LESS_EQUAL: "<=" >
200|   < GREATER: ">" >
201|   < LESS: "<" >
202|   < EQUAL: "=" >
203|   < EXCLAMATION: "!" >
204|   < TILDE: "~" >
205|   < DCOLON: "::" >
206|   < SEMICOLON: ";" >
207|   < PIPE: "|" >
208|   < PIPE_Z: "|z" >
209|   < PLUS: "+" >
210|   < MINUS: "-" >
211|   < AMPERSAND: "&" >
212|   < QUESTION: "?" >
213|   < DOLLAR: "$" >
214|   < CARET: "^" >
215|   < FULLSTOP: "." >
216|   < DEG: "°" >
217|   < ELEMENT_OF: "∈" >
218|   < CROSSING: "⧉" >
219|   < PERCENT: "%" >
220|   < COMMENT_START: "/*" > : COMMENT
221|   < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
222}
223
224<COMMENT>
225TOKEN:
226{
227    < COMMENT_END: "*/" > : DEFAULT
228}
229
230<COMMENT>
231SKIP:
232{
233    < ~[] >
234}
235
236
237/*
238 * Preprocessor parser definitions:
239 *
240 * <pre>
241 *
242 * {@literal @media} { ... } queries are supported, following http://www.w3.org/TR/css3-mediaqueries/#syntax
243 *
244 *                               media_query
245 *         ___________________________|_______________________________
246 *        |                                                           |
247 * {@literal @media} all and (min-josm-version: 7789) and (max-josm-version: 7790), all and (user-agent: xyz) { ... }
248 *                |______________________|
249 *                          |
250 *                    media_expression
251 * </pre>
252 */
253 
254
255/**
256 * root method for the preprocessor.
257 * @param sheet MapCSS style source
258 * @return result string
259 * @throws ParseException in case of parsing error
260 */
261String pp_root(MapCSSStyleSource sheet):
262{
263}
264{
265    { sb = new StringBuilder(); this.sheet = sheet; }
266    pp_black_box(true) <EOF>
267    { return sb.toString(); }
268}
269
270/**
271 * Parse any unknown grammar (black box).
272 *
273 * Only stop when "@media" is encountered and keep track of correct number of
274 * opening and closing curly brackets.
275 *
276 * @param write false if this content should be skipped (@pp_media condition is not fulfilled), true otherwise
277 * @throws ParseException in case of parsing error
278 */
279void pp_black_box(boolean write):
280{
281    Token t;
282}
283{
284    (
285        (t=<PP_AND> | t=<PP_OR> | t=<PP_NOT> | t=<UINT> | t=<STRING> | t=<REGEX> | t=<LPAR> | t=<RPAR> | t=<COMMA> | t=<COLON> | t=<IDENT> | t=<PP_SOMETHING_ELSE>) { if (write) sb.append(t.image); }
286        |
287            pp_w1()
288        |
289            pp_supports(!write)
290        |
291            pp_media(!write)
292        |
293            t=<LBRACE> { if (write) sb.append(t.image); } pp_black_box(write) t=<RBRACE> { if (write) sb.append(t.image); }
294    )*
295}
296
297/**
298 * Parses an @supports rule.
299 *
300 * @param ignore if the content of this rule should be ignored
301 * (because we are already inside a @supports block that didn't pass)
302 * @throws ParseException in case of parsing error
303 */
304void pp_supports(boolean ignore):
305{
306    boolean pass;
307}
308{
309    <PP_SUPPORTS> pp_w()
310    pass=pp_supports_condition()
311    <LBRACE>
312    pp_black_box(pass && !ignore)
313    <RBRACE>
314}
315
316/**
317 * Parses the condition of the @supports rule.
318 *
319 * Unlike other parsing rules, grabs trailing whitespace.
320 * @return true, if the condition is fulfilled
321 * @throws ParseException in case of parsing error
322 */
323boolean pp_supports_condition():
324{
325    boolean pass;
326    boolean q;
327}
328{
329    (
330        <PP_NOT> pp_w() q=pp_supports_condition_in_parens() { pass = !q; } pp_w()
331    |
332        LOOKAHEAD(pp_supports_condition_in_parens() pp_w() <PP_AND>)
333        pass=pp_supports_condition_in_parens() pp_w()
334        ( <PP_AND> pp_w() q=pp_supports_condition_in_parens() { pass = pass && q; } pp_w() )+
335    |
336        LOOKAHEAD(pp_supports_condition_in_parens() pp_w() <PP_OR>)
337        pass=pp_supports_condition_in_parens() pp_w()
338        ( <PP_OR> pp_w() q=pp_supports_condition_in_parens() { pass = pass || q; } pp_w() )+
339    |
340        pass=pp_supports_condition_in_parens() pp_w()
341    )
342    { return pass; }
343}
344
345/**
346 * Parses something in parenthesis inside the condition of the @supports rule.
347 *
348 * @return true, if the condition is fulfilled
349 * @throws ParseException in case of parsing error
350 */
351boolean pp_supports_condition_in_parens():
352{
353    boolean pass;
354}
355{
356    (
357        LOOKAHEAD(pp_supports_declaration_condition())
358        pass=pp_supports_declaration_condition()
359    |
360        <LPAR> pp_w() pass=pp_supports_condition() <RPAR>
361    )
362    { return pass; }
363}
364
365/**
366 * Parse an @supports declaration condition, e.&nbsp;g. a single (key:value) or (key) statement.
367 *
368 * The parsing rule {@link #literal()} from the main mapcss parser is reused here.
369 *
370 * @return true if the condition is fulfilled
371 * @throws ParseException in case of parsing error
372 */
373boolean pp_supports_declaration_condition():
374{
375    Token t;
376    String feature;
377    Object val = null;
378}
379{
380    <LPAR> pp_w() t=<IDENT> { feature = t.image; } pp_w() ( <COLON> pp_w() val=literal() )? <RPAR>
381    { return this.sheet.evalSupportsDeclCondition(feature, val); }
382}
383
384// deprecated
385void pp_media(boolean ignore):
386{
387    boolean pass = false;
388    boolean q;
389    boolean empty = true;
390}
391{
392    {
393        if (sheet != null) {
394            String msg = tr("Detected deprecated ''{0}'' in ''{1}'' which will be removed shortly. Use ''{2}'' instead.",
395                            "@media", sheet.getDisplayString(), "@supports");
396            Logging.error(msg);
397            sheet.logWarning(msg);
398        }
399    }
400    <PP_MEDIA> pp_w()
401    ( q=pp_media_query() { pass = pass || q; empty = false; }
402        ( <COMMA> pp_w() q=pp_media_query() { pass = pass || q; } )*
403    )?
404    <LBRACE>
405    pp_black_box((empty || pass) && !ignore)
406    <RBRACE>
407}
408
409// deprecated
410boolean pp_media_query():
411{
412    Token t;
413    String mediatype = "all";
414    boolean pass = true;
415    boolean invert = false;
416    boolean e;
417}
418{
419    ( <PP_NOT> { invert = true; } pp_w() )?
420    (
421            t=<IDENT> { mediatype = t.image.toLowerCase(Locale.ENGLISH); } pp_w()
422            ( <PP_AND> pp_w() e=pp_media_expression() { pass = pass && e; } pp_w() )*
423        |
424            e=pp_media_expression() { pass = pass && e; } pp_w()
425            ( <PP_AND> pp_w() e=pp_media_expression() { pass = pass && e; } pp_w() )*
426    )
427    {
428        if (!"all".equals(mediatype)) {
429            pass = false;
430        }
431        return invert ? (!pass) : pass;
432    }
433}
434
435/**
436 * Parse an @media expression.
437 *
438 * The parsing rule {@link #literal()} from the main mapcss parser is reused here.
439 *
440 * @return true if the condition is fulfilled
441 * @throws ParseException in case of parsing error
442 */
443// deprecated
444boolean pp_media_expression():
445{
446    Token t;
447    String feature;
448    Object val = null;
449}
450{
451    <LPAR> pp_w() t=<IDENT> { feature = t.image; } pp_w() ( <COLON> pp_w() val=literal() )? <RPAR>
452    { return this.sheet.evalSupportsDeclCondition(feature, val); }
453}
454
455void pp_w1():
456{
457    Token t;
458}
459{
460    t=<PP_NEWLINECHAR> { sb.append(t.image); }
461        |
462    t=<PP_WHITESPACE> { sb.append(t.image); }
463        |
464    t=<PP_COMMENT_START> { sb.append(t.image); } t=<PP_COMMENT_END> { sb.append(t.image); }
465}
466
467void pp_w():
468{
469}
470{
471 ( pp_w1() )*
472}
473
474/*
475 * Parser definition for the main MapCSS parser:
476 *
477 * <pre>
478 *
479 *                       rule
480 *  _______________________|______________________________
481 * |                                                      |
482 *        selector                      declaration
483 *  _________|___________________   _________|____________
484 * |                             | |                      |
485 *
486 * way|z11-12[highway=residential] { color: red; width: 3 }
487 *
488 *    |_____||___________________|   |_________|
489 *       |            |                   |
490 *     zoom       condition          instruction
491 *
492 * more general:
493 *
494 * way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
495 *
496 * 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
497 *
498 * </pre>
499 */
500
501int uint() :
502{
503    Token i;
504}
505{
506    i=<UINT> { return Integer.parseInt(i.image); }
507}
508
509int int_() :
510{
511    int i;
512}
513{
514    <MINUS> i=uint() { return -i; } | i=uint() { return i; }
515}
516
517float ufloat() :
518{
519    Token f;
520}
521{
522    ( f=<UFLOAT> | f=<UINT> )
523    { return Float.parseFloat(f.image); }
524}
525
526float float_() :
527{
528    float f;
529}
530{
531    <MINUS> f=ufloat() { return -f; } | f=ufloat() { return f; }
532}
533
534String string() :
535{
536    Token t;
537}
538{
539    t=<STRING>
540    { return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
541}
542
543String ident():
544{
545    Token t;
546    String s;
547}
548{
549    ( t=<IDENT> | t=<SET> ) { return t.image; }
550}
551
552String string_or_ident() :
553{
554    Token t;
555    String s;
556}
557{
558    ( s=ident() | s=string() ) { return s; }
559}
560
561String regex() :
562{
563    Token t;
564}
565{
566    t=<REGEX>
567    { return t.image.substring(1, t.image.length() - 1); }
568}
569
570/**
571 * white-space
572 * @throws ParseException in case of parsing error
573 */
574void s() :
575{
576}
577{
578    ( <S> )?
579}
580
581/**
582 * mix of white-space and comments
583 * @throws ParseException in case of parsing error
584 */
585void w() :
586{
587}
588{
589    ( <S> | <COMMENT_START> <COMMENT_END> )*
590}
591
592/**
593 * comma delimited list of floats (at least 2, all &gt;= 0)
594 * @return list of floats
595 * @throws ParseException in case of parsing error
596 */
597List<Float> float_array() :
598{
599    float f;
600    List<Float> fs = new ArrayList<Float>();
601}
602{
603    f=ufloat() { fs.add(f); }
604    (
605        <COMMA> s()
606        f=ufloat() { fs.add(f); }
607    )+
608    {
609        return fs;
610    }
611}
612
613/**
614 * entry point for the main parser
615 * @param sheet MapCSS style source
616 * @throws ParseException in case of parsing error
617 */
618void sheet(MapCSSStyleSource sheet):
619{
620}
621{
622    { this.sheet = sheet; }
623    w()
624    (
625        try {
626            rule() w()
627        } catch (MapCSSException mex) {
628            Logging.error(mex);
629            error_skipto(RBRACE, mex);
630            w();
631        } catch (ParseException ex) {
632            error_skipto(RBRACE, null);
633            w();
634        }
635    )*
636    <EOF>
637}
638
639void rule():
640{
641    List<Selector> selectors;
642    Declaration decl;
643}
644{
645    selectors=selectors()
646    decl=declaration()
647    {
648        for (Selector s : selectors) {
649            sheet.rules.add(new MapCSSRule(s, decl));
650        }
651    }
652}
653
654List<Selector> selectors():
655{
656    List<Selector> selectors = new ArrayList<Selector>();
657    Selector sel;
658}
659{
660    sel=child_selector() { selectors.add(sel); }
661    (
662        <COMMA> w()
663        sel=child_selector() { selectors.add(sel); }
664    )*
665    { return selectors; }
666}
667
668Selector child_selector() :
669{
670    Selector.ChildOrParentSelectorType type = null;
671    Condition c;
672    List<Condition> conditions = new ArrayList<Condition>();
673    Selector selLeft;
674    LinkSelector selLink = null;
675    Selector selRight = null;
676}
677{
678    selLeft=selector() w()
679    (
680        (
681            (
682                (
683                    <GREATER> { type = Selector.ChildOrParentSelectorType.CHILD; }
684                |
685                    <LESS> { type = Selector.ChildOrParentSelectorType.PARENT; }
686                |
687                    <PLUS> { type = Selector.ChildOrParentSelectorType.SIBLING; }
688                )
689                ( ( c=condition(Context.LINK) | c=class_or_pseudoclass(Context.LINK) ) { conditions.add(c); } )*
690            |
691                <ELEMENT_OF> { type = Selector.ChildOrParentSelectorType.ELEMENT_OF; }
692            |
693                <CROSSING> { type = Selector.ChildOrParentSelectorType.CROSSING; }
694            )
695            w()
696        |
697            { /* <GREATER> is optional for child selector */ type = Selector.ChildOrParentSelectorType.CHILD; }
698        )
699        { selLink = new LinkSelector(conditions); }
700        selRight=selector() w()
701    )?
702    { return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, type) : selLeft; }
703}
704
705Selector selector() :
706{
707    Token base;
708    Condition c;
709    Pair<Integer, Integer> r = null;
710    List<Condition> conditions = new ArrayList<Condition>();
711    Subpart sub = null;
712}
713{
714    ( base=<IDENT> | base=<STAR> )
715    ( r=zoom() )?
716    ( ( c=condition(Context.PRIMITIVE) | c=class_or_pseudoclass(Context.PRIMITIVE) ) { conditions.add(c); } )*
717    ( sub=subpart() )?
718    { return new GeneralSelector(base.image, r, conditions, sub); }
719}
720
721Pair<Integer, Integer> zoom() :
722{
723    Integer min = 0;
724    Integer max = Integer.MAX_VALUE;
725}
726{
727    <PIPE_Z>
728    (
729            <MINUS> max=uint()
730        |
731        LOOKAHEAD(2)
732            min=uint() <MINUS> ( max=uint() )?
733        |
734            min=uint() { max = min; }
735    )
736    { return new Pair<Integer, Integer>(min, max); }
737}
738
739Condition condition(Context context) :
740{
741    Condition c;
742    Expression e;
743}
744{
745    <LSQUARE> s()
746    (
747        LOOKAHEAD( simple_key_condition(context) s() <RSQUARE> )
748            c=simple_key_condition(context) s() <RSQUARE> { return c; }
749        |
750        LOOKAHEAD( simple_key_value_condition(context) s() <RSQUARE> )
751            c=simple_key_value_condition(context) s() <RSQUARE> { return c; }
752        |
753            e=expression() <RSQUARE> { return ConditionFactory.createExpressionCondition(e, context); }
754    )
755}
756
757String tag_key() :
758{
759    String s, s2;
760    Token t;
761}
762{
763        s=string() { return s; }
764    |
765        s=ident() ( <COLON> s2=ident() { s += ':' + s2; } )* { return s; }
766}
767
768Condition simple_key_condition(Context context) :
769{
770    boolean not = false;
771    KeyMatchType matchType = null;;
772    String key;
773}
774{
775    ( <EXCLAMATION> { not = true; } )?
776    (
777        { matchType = KeyMatchType.REGEX; } key = regex()
778    |
779        key = tag_key()
780    )
781    ( LOOKAHEAD(2) <QUESTION> <EXCLAMATION> { matchType = KeyMatchType.FALSE; } )?
782    (              <QUESTION>               { matchType = KeyMatchType.TRUE;  } )?
783    { return ConditionFactory.createKeyCondition(key, not, matchType, context); }
784}
785
786Condition simple_key_value_condition(Context context) :
787{
788    String key;
789    String val;
790    float f;
791    int i;
792    KeyMatchType matchType = null;;
793    Op op;
794    boolean considerValAsKey = false;
795}
796{
797    (
798        key = regex() s() { matchType = KeyMatchType.REGEX; }
799    |
800        key=tag_key() s()
801    )
802    (
803        LOOKAHEAD(3)
804            (
805                    <EQUAL> <TILDE> { op=Op.REGEX; }
806                |
807                    <EXCLAMATION> <TILDE> { op=Op.NREGEX; }
808            )
809            s()
810            ( <STAR> { considerValAsKey=true; } )?
811            val=regex()
812        |
813            (
814                    <EXCLAMATION> <EQUAL> { op=Op.NEQ; }
815                |
816                    <EQUAL> { op=Op.EQ; }
817                |
818                    <TILDE> <EQUAL> { op=Op.ONE_OF; }
819                |
820                    <CARET> <EQUAL> { op=Op.BEGINS_WITH; }
821                |
822                    <DOLLAR> <EQUAL> { op=Op.ENDS_WITH; }
823                |
824                    <STAR> <EQUAL> { op=Op.CONTAINS; }
825            )
826            s()
827            ( <STAR> { considerValAsKey=true; } )?
828            (
829                LOOKAHEAD(2)
830                        i=int_() { val=Integer.toString(i); }
831                    |
832                        f=float_() { val=Float.toString(f); }
833                    |
834                        val=string_or_ident()
835            )
836        |
837            (
838                    <GREATER_EQUAL> { op=Op.GREATER_OR_EQUAL; }
839                |
840                    <GREATER> { op=Op.GREATER; }
841                |
842                    <LESS_EQUAL> { op=Op.LESS_OR_EQUAL; }
843                |
844                    <LESS> { op=Op.LESS; }
845            )
846            s()
847            f=float_() { val=Float.toString(f); }
848    )
849    { return KeyMatchType.REGEX == matchType
850            ? ConditionFactory.createRegexpKeyRegexpValueCondition(key, val, op)
851            : ConditionFactory.createKeyValueCondition(key, val, op, context, considerValAsKey); }
852}
853
854Condition class_or_pseudoclass(Context context) :
855{
856    String s;
857    boolean not = false;
858    boolean pseudo;
859}
860{
861    ( <EXCLAMATION> { not = true; } )?
862    (
863        <FULLSTOP> { pseudo = false; }
864    |
865        <COLON> { pseudo = true; }
866    )
867    s=ident()
868    { return pseudo
869        ? ConditionFactory.createPseudoClassCondition(s, not, context)
870        : ConditionFactory.createClassCondition(s, not, context); }
871}
872
873Subpart subpart() :
874{
875    String s;
876    Expression e;
877}
878{
879    <DCOLON>
880    (
881        s=ident() { return new Subpart.StringSubpart(s); }
882    |
883        <STAR> { return new Subpart.StringSubpart("*"); }
884    |
885        <LPAR> e=expression() <RPAR> { return new Subpart.ExpressionSubpart(e); }
886    )
887}
888
889Declaration declaration() :
890{
891    List<Instruction> ins = new ArrayList<Instruction>();
892    Instruction i;
893    Token key;
894    Object val = null;
895}
896{
897    <LBRACE> w()
898    (
899        (
900            <SET> w()
901            (<FULLSTOP>)? // specification allows "set .class" to set "class". we also support "set class"
902            key=<IDENT> w()
903            ( <EQUAL> val=expression() )?
904            { ins.add(new Instruction.AssignmentInstruction(key.image, val == null ? true : val, true)); }
905            ( <RBRACE> { return new Declaration(ins, declarationCounter++); } | <SEMICOLON> w() )
906        )
907    |
908        key=<IDENT> w() <COLON> w()
909        (
910            LOOKAHEAD( float_array() w() ( <SEMICOLON> | <RBRACE> ) )
911                val=float_array()
912                { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
913                w()
914                ( <RBRACE> { return new Declaration(ins, declarationCounter++); } | <SEMICOLON> w() )
915            |
916            LOOKAHEAD( expression() ( <SEMICOLON> | <RBRACE> ) )
917                val=expression()
918                { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
919                ( <RBRACE> { return new Declaration(ins, declarationCounter++); } | <SEMICOLON> w() )
920            |
921                val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
922        )
923    )*
924    <RBRACE>
925    { return new Declaration(ins, declarationCounter++); }
926}
927
928/**
929 * General expression.
930 * Separate production rule for each level of operator precedence (recursive descent).
931 */
932Expression expression() :
933{
934    Expression e;
935}
936{
937    e=conditional_expression()
938    {
939        return e;
940    }
941}
942
943Expression conditional_expression() :
944{
945    Expression e, e1, e2;
946    String op = null;
947}
948{
949    e=or_expression()
950    (
951        <QUESTION> w()
952        e1=conditional_expression()
953        <COLON> w()
954        e2=conditional_expression()
955        {
956            e = ExpressionFactory.createFunctionExpression("cond", Arrays.asList(e, e1, e2));
957        }
958    )?
959    {
960        return e;
961    }
962}
963
964Expression or_expression() :
965{
966    Expression e, e2;
967    String op = null;
968}
969{
970    e=and_expression()
971    (
972        <PIPE> <PIPE> w()
973        e2=and_expression()
974        {
975            e = ExpressionFactory.createFunctionExpression("or", Arrays.asList(e, e2));
976        }
977    )*
978    {
979        return e;
980    }
981}
982
983Expression and_expression() :
984{
985    Expression e, e2;
986    String op = null;
987}
988{
989    e=relational_expression()
990    (
991        <AMPERSAND> <AMPERSAND> w()
992        e2=relational_expression()
993        {
994            e = ExpressionFactory.createFunctionExpression("and", Arrays.asList(e, e2));
995        }
996    )*
997    {
998        return e;
999    }
1000}
1001
1002Expression relational_expression() :
1003{
1004    Expression e, e2;
1005    String op = null;
1006}
1007{
1008    e=additive_expression()
1009    (
1010        (
1011            <GREATER_EQUAL> { op = "greater_equal"; }
1012            |
1013            <LESS_EQUAL> { op = "less_equal"; }
1014            |
1015            <GREATER> { op = "greater"; }
1016            |
1017            <LESS> { op = "less"; }
1018            |
1019            <EQUAL> ( <EQUAL> )? { op = "equal"; }
1020            |
1021            <EXCLAMATION> <EQUAL> { op = "not_equal"; }
1022        ) w()
1023        e2=additive_expression()
1024        {
1025            e = ExpressionFactory.createFunctionExpression(op, Arrays.asList(e, e2));
1026        }
1027    )?
1028    {
1029        return e;
1030    }
1031}
1032
1033Expression additive_expression() :
1034{
1035    Expression e, e2;
1036    String op = null;
1037}
1038{
1039    e=multiplicative_expression()
1040    (
1041        ( <PLUS> { op = "plus"; } | <MINUS> { op = "minus"; } ) w()
1042        e2=multiplicative_expression()
1043        {
1044            e = ExpressionFactory.createFunctionExpression(op, Arrays.asList(e, e2));
1045        }
1046    )*
1047    {
1048        return e;
1049    }
1050}
1051
1052Expression multiplicative_expression() :
1053{
1054    Expression e, e2;
1055    String op = null;
1056}
1057{
1058    e=unary_expression()
1059    (
1060        ( <STAR> { op = "times"; } | <SLASH> { op = "divided_by"; } ) w()
1061        e2=unary_expression()
1062        {
1063            e = ExpressionFactory.createFunctionExpression(op, Arrays.asList(e, e2));
1064        }
1065    )*
1066    {
1067        return e;
1068    }
1069}
1070
1071Expression unary_expression() :
1072{
1073    Expression e;
1074    String op = null;
1075}
1076{
1077    (
1078        <MINUS> { op = "minus"; } w()
1079    |
1080        <EXCLAMATION> { op = "not"; } w()
1081    )?
1082    e=primary() w()
1083    {
1084        if (op == null)
1085            return e;
1086        return ExpressionFactory.createFunctionExpression(op, Collections.singletonList(e));
1087    }
1088}
1089
1090Expression primary() :
1091{
1092    Expression nested;
1093    Expression fn;
1094    Object lit;
1095}
1096{
1097    LOOKAHEAD(3) // both function and identifier start with an identifier (+ optional whitespace)
1098        fn=function() { return fn; }
1099    |
1100        lit=literal()
1101        {
1102            if (lit == null)
1103                return NullExpression.INSTANCE;
1104            return new LiteralExpression(lit);
1105        }
1106    |
1107        <LPAR> w() nested=expression() <RPAR> { return nested; }
1108}
1109
1110Expression function() :
1111{
1112    Expression arg;
1113    String name;
1114    List<Expression> args = new ArrayList<Expression>();
1115}
1116{
1117    name=ident() w()
1118    <LPAR> w()
1119    (
1120        arg=expression() { args.add(arg); }
1121        ( <COMMA> w() arg=expression() { args.add(arg); } )*
1122    )?
1123    <RPAR>
1124    { return ExpressionFactory.createFunctionExpression(name, args); }
1125}
1126
1127Object literal() :
1128{
1129    String val, pref;
1130    Token t;
1131    Float f;
1132}
1133{
1134        LOOKAHEAD(2)
1135        pref=ident() t=<HEXCOLOR>
1136        {
1137            return new NamedColorProperty(
1138                    NamedColorProperty.COLOR_CATEGORY_MAPPAINT,
1139                    sheet == null ? "MapCSS" : sheet.title, pref,
1140                    ColorHelper.html2color(t.image)).get();
1141        }
1142    |
1143        t=<IDENT> { return new Keyword(t.image); }
1144    |
1145        val=string() { return val; }
1146    |
1147        <PLUS> f=ufloat() { return new Instruction.RelativeFloat(f); }
1148    |
1149        LOOKAHEAD(2)
1150        f=ufloat_unit() { return f; }
1151    |
1152        f=ufloat() { return f; }
1153    |
1154        t=<HEXCOLOR> { return ColorHelper.html2color(t.image); }
1155}
1156
1157/**
1158 * Number followed by a unit.
1159 *
1160 * Returns angles in radians and lengths in pixels.
1161 */
1162Float ufloat_unit() :
1163{
1164    float f;
1165    String u;
1166}
1167{
1168    f=ufloat() ( u=ident() | <DEG> { u = "°"; } | <PERCENT> { u = "%"; } )
1169    {
1170        Double m = unit_factor(u);
1171        if (m == null)
1172            return null;
1173        return (float) (f * m);
1174    }
1175}
1176
1177JAVACODE
1178private Double unit_factor(String unit) {
1179    switch (unit) {
1180        case "deg":
1181        case "°": return Math.PI / 180;
1182        case "rad": return 1.;
1183        case "grad": return Math.PI / 200;
1184        case "turn": return 2 * Math.PI;
1185        case "%": return 0.01;
1186        case "px": return 1.;
1187        case "cm": return 96/2.54;
1188        case "mm": return 9.6/2.54;
1189        case "in": return 96.;
1190        case "q": return 2.4/2.54;
1191        case "pc": return 16.;
1192        case "pt": return 96./72;
1193        default: return null;
1194    }
1195}
1196
1197JAVACODE
1198void error_skipto(int kind, MapCSSException me) {
1199    if (token.kind == EOF)
1200        throw new ParseException("Reached end of file while parsing");
1201       
1202    Exception e = null;       
1203    ParseException pe = generateParseException();
1204
1205    if (me != null) {
1206        final Token token = Utils.firstNonNull(pe.currentToken.next, pe.currentToken);
1207        me.setLine(token.beginLine);
1208        me.setColumn(token.beginColumn);
1209        e = me;
1210    } else {
1211        e = new ParseException(pe.getMessage()); // prevent memory leak
1212    }
1213   
1214    Logging.error("Skipping to the next rule, because of an error:");
1215    Logging.error(e);
1216    if (sheet != null) {
1217        sheet.logError(e);
1218    }
1219    Token t;
1220    do {
1221        t = getNextToken();
1222    } while (t.kind != kind && t.kind != EOF);
1223    if (t.kind == EOF)
1224        throw new ParseException("Reached end of file while parsing");
1225}
1226
1227JAVACODE
1228/**
1229 * read everything to the next semicolon
1230 */
1231String readRaw() {
1232    Token t;
1233    StringBuilder s = new StringBuilder();
1234    while (true) {
1235        t = getNextToken();
1236        if ((t.kind == S || t.kind == STRING || t.kind == UNEXPECTED_CHAR) &&
1237                t.image.contains("\n")) {
1238            ParseException e = new ParseException(String.format("Warning: end of line while reading an unquoted string at line %s column %s.", t.beginLine, t.beginColumn));
1239            Logging.error(e);
1240            if (sheet != null) {
1241                sheet.logError(e);
1242            }
1243        }
1244        if (t.kind == SEMICOLON || t.kind == EOF)
1245            break;
1246        s.append(t.image);
1247    }
1248    if (t.kind == EOF)
1249        throw new ParseException("Reached end of file while parsing");
1250    return s.toString();
1251}
1252
Note: See TracBrowser for help on using the repository browser.