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

Last change on this file since 11489 was 11489, checked in by Don-vip, 7 years ago

error-prone - enums should be immutable, and cannot have non-final fields

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