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

Last change on this file since 8256 was 8256, checked in by bastiK, 5 years ago

mapcss: improve expression parsing
Now it respects operator precedence (C/C++ style) and allows
mix of plus and minus like this:

3 + 2 * 5 - 2 + 10

This is parsed as

(((3 + (2 * 5)) - 2) + 10)

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