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

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

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

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