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

Last change on this file since 12987 was 12987, checked in by bastiK, 7 years ago

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

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