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

Last change on this file since 9099 was 9099, checked in by bastiK, 10 years ago

mapcss partial fill: move threshold parameter ([9063]) into the mapcss style
(new property fill-extent-threshold)

  • add support for this parameter when area is unclosed
  • smaller extent for unclosed areas

(see #12104)

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