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, 9 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.