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

Last change on this file since 8260 was 8260, checked in by bastiK, 9 years ago

see #10217 - move unit handling to parser
adds support for negative angles
new: length units px,cm,mm,in,q,pc,pt as in CSS (immediately converted to px)

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