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

Last change on this file since 7057 was 7057, checked in by bastiK, 9 years ago

reverting [7056] because it won't work for execution in multiple threads (which will be more useful)

File size: 22.8 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.List;
13
14import org.openstreetmap.josm.gui.mappaint.Keyword;
15import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
16import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
17import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
18import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
19import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
20import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
21import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
22import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
23import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
24import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSException;
25import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
26import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
27import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
28import org.openstreetmap.josm.tools.ColorHelper;
29import org.openstreetmap.josm.tools.Pair;
30import org.openstreetmap.josm.Main;
31
32/**
33 * MapCSS parser.
34 *
35 * Contains two independent grammars:
36 * (a) the preprocessor and (b) the main mapcss parser.
37 *
38 * The preprocessor handles @media syntax. Basically this allows
39 * to write one style for different versions of JOSM (or different editors).
40 * When the @media condition is not fulfilled, it should simply skip over
41 * the whole section and not attempt to parse the possibly unknown
42 * grammar. It preserves whitespace and comments, in order to keep the
43 * line and column numbers in the error messages correct for the second pass.
44 *
45 */
46
47public class MapCSSParser {
48    MapCSSStyleSource sheet;
49    StringBuilder sb;
50
51    /**
52     * Nicer way to refer to a lexical state.
53     */
54    public static enum LexicalState {
55        PREPROCESSOR(0), /* the preprocessor */
56        DEFAULT(2);      /* the main parser */
57       
58        int idx; // the integer, which javacc assigns to this state
59       
60        LexicalState(int idx) {
61            if (!this.name().equals(MapCSSParserTokenManager.lexStateNames[idx])) {
62                throw new RuntimeException();
63            }
64            this.idx = idx;
65        }
66    };
67   
68    /**
69     * Constructor which initializes the parser with a certain lexical state.
70     */
71    public MapCSSParser(InputStream in, String encoding, LexicalState initState) {
72        this(createTokenManager(in, encoding, initState));
73    }
74
75    protected static MapCSSParserTokenManager createTokenManager(InputStream in, String encoding, LexicalState initState) {
76        SimpleCharStream scs;
77        try {
78            scs = new SimpleCharStream(in, encoding, 1, 1);
79        } catch(java.io.UnsupportedEncodingException e) {
80            throw new RuntimeException(e);
81        }
82        return new MapCSSParserTokenManager(scs, initState.idx);
83    }
84}
85PARSER_END(MapCSSParser)
86
87/*************
88 * Token definitions
89 *
90 * Lexical states for the preprocessor: <PREPROCESSOR>, <PP_COMMENT>
91 * Lexical states for the main parser: <DEFAULT>, <COMMENT>
92 */
93 
94<PREPROCESSOR>
95TOKEN:
96{
97    < PP_AND: "and" >
98|   < PP_NOT: "not" >
99|   < PP_MEDIA: "@media" >
100|   < PP_NEWLINECHAR: "\n" | "\r" | "\f" >
101|   < PP_WHITESPACE: " " | "\t" >
102|   < PP_COMMENT_START: "/*" > : PP_COMMENT
103}
104
105<PP_COMMENT>
106TOKEN:
107{
108    < PP_COMMENT_END: "*/" > : PREPROCESSOR
109}
110
111<PP_COMMENT>
112MORE:
113{
114    < ~[] >
115}
116
117<DEFAULT>
118TOKEN [IGNORE_CASE]:
119{
120 /* Special keyword in some contexts, ordinary identifier in other contexts.
121    Use the parsing rule <code>ident()</code> to refer to a general
122    identifier, including "set". */
123   < SET: "set" >
124}
125
126<DEFAULT,PREPROCESSOR>
127TOKEN:
128{
129    < IDENT: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","-","0"-"9"] )* >
130|   < UINT: ["1"-"9"] ( ["0"-"9"] )* >
131|   < STRING: "\"" ( [" ","!","#"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\\"" | "\\\\" )*  "\"" >
132|   < #PREDEFINED: "\\" ["d","D","s","S","w","W"] >
133|   < #REGEX_CHAR_WITHOUT_STAR: [" "-")","+"-".","0"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\/" | "\\\\" | "\\[" | "\\]" | "\\+" | "\\." | "\\'" | "\\\"" | <PREDEFINED> >
134|   < REGEX: "/" <REGEX_CHAR_WITHOUT_STAR> ( <REGEX_CHAR_WITHOUT_STAR> | "*" )*  "/" >
135|   < LBRACE: "{" >
136|   < RBRACE: "}" >
137|   < LPAR: "(" >
138|   < RPAR: ")" >
139|   < COLON: ":" >
140}
141
142<PREPROCESSOR>
143TOKEN:
144{
145    < PP_SOMETHING_ELSE : ~[] >
146}
147
148<DEFAULT>
149TOKEN:
150{
151    < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
152|   < #H: ["0"-"9","a"-"f","A"-"F"] >
153|   < HEXCOLOR: "#" ( <H><H><H><H><H><H><H><H> | <H><H><H><H><H><H> | <H><H><H> ) >
154|   < S: ( " " | "\t" | "\n" | "\r" | "\f" )+ >
155|   < STAR: "*" >
156|   < SLASH: "/" >
157|   < LSQUARE: "[" >
158|   < RSQUARE: "]" >
159|   < GREATER_EQUAL: ">=" >
160|   < LESS_EQUAL: "<=" >
161|   < GREATER: ">" >
162|   < LESS: "<" >
163|   < EQUAL: "=" >
164|   < EXCLAMATION: "!" >
165|   < TILDE: "~" >
166|   < DCOLON: "::" >
167|   < SEMICOLON: ";" >
168|   < COMMA: "," >
169|   < PIPE: "|" >
170|   < PIPE_Z: "|z" >
171|   < PLUS: "+" >
172|   < MINUS: "-" >
173|   < AMPERSAND: "&" >
174|   < QUESTION: "?" >
175|   < DOLLAR: "$" >
176|   < CARET: "^" >
177|   < FULLSTOP: "." >
178|   < ELEMENT_OF: "∈" >
179|   < CROSSING: "⧉" >
180|   < COMMENT_START: "/*" > : COMMENT
181|   < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
182}
183
184<COMMENT>
185TOKEN:
186{
187    < COMMENT_END: "*/" > : DEFAULT
188}
189
190<COMMENT>
191SKIP:
192{
193    < ~[] >
194}
195
196
197/*************
198 *
199 * Preprocessor parser definitions:
200 *
201 * <pre>
202 *
203 * {@literal @media} { ... } queries are supported, following http://www.w3.org/TR/css3-mediaqueries/#syntax
204 *
205 *                               media_query
206 *         ___________________________|_______________________________
207 *        |                                                           |
208 * {@literal @media} all and (min-josm-version: 7789) and (max-josm-version: 7790), all and (user-agent: xyz) { ... }
209 *                |______________________|
210 *                          |
211 *                    media_expression
212 * </pre>
213 */
214 
215
216/**
217 * root method for the preprocessor.
218 */
219String pp_root(MapCSSStyleSource sheet):
220{
221}
222{
223    { sb = new StringBuilder(); this.sheet = sheet; }
224    pp_black_box(true) <EOF>
225    { return sb.toString(); }
226}
227
228/**
229 * Parse any unknown grammar (black box).
230 *
231 * Only stop when "@media" is encountered and keep track of correct number of
232 * opening and closing curly brackets.
233 *
234 * @param write false if this content should be skipped (@pp_media condition is not fulfilled), true otherwise
235 */
236void pp_black_box(boolean write):
237{
238    Token t;
239}
240{
241    (
242        (t=<PP_AND> | t=<PP_NOT> | t=<UINT> | t=<STRING> | t=<REGEX> | t=<LPAR> | t=<RPAR> | t=<COLON> | t=<IDENT> | t=<PP_SOMETHING_ELSE>) { if (write) sb.append(t.image); }
243        |
244            pp_w1()
245        |
246            pp_media()
247        |
248            t=<LBRACE> { if (write) sb.append(t.image); } pp_black_box(write) t=<RBRACE> { if (write) sb.append(t.image); }
249    )*
250}
251
252void pp_media():
253{
254    boolean pass = false;
255    boolean q;
256    boolean empty = true;
257}
258{
259    <PP_MEDIA> pp_w()
260    ( q=pp_media_query() { pass = pass || q; empty = false; }
261        ( <COMMA> pp_w() q=pp_media_query() { pass = pass || q; } )*
262    )?
263    <LBRACE>
264    pp_black_box(empty || pass)
265    <RBRACE>
266}
267
268boolean pp_media_query():
269{
270    Token t;
271    String mediatype = "all";
272    boolean pass = true;
273    boolean invert = false;
274    boolean e;
275}
276{
277    ( <PP_NOT> { invert = true; } pp_w() )?
278    (
279            t=<IDENT> { mediatype = t.image.toLowerCase(); } pp_w()
280            ( <PP_AND> pp_w() e=pp_media_expression() { pass = pass && e; } pp_w() )*
281        |
282            e=pp_media_expression() { pass = pass && e; } pp_w()
283            ( <PP_AND> pp_w() e=pp_media_expression() { pass = pass && e; } pp_w() )*
284    )
285    {
286        if (!"all".equals(mediatype)) {
287            pass = false;
288        }
289        return invert ? (!pass) : pass;
290    }
291}
292
293/**
294 * Parse an @media expression.
295 *
296 * The parsing rule {@link #literal()} from the main mapcss parser is reused here.
297 *
298 * @return true if the condition is fulfilled
299 */
300boolean pp_media_expression():
301{
302    Token t;
303    String feature;
304    Object val = null;
305}
306{
307    <LPAR> pp_w() t=<IDENT> { feature = t.image; } pp_w() ( <COLON> pp_w() val=literal() )? <RPAR>
308    { return this.sheet.evalMediaExpression(feature, val); }
309}
310
311void pp_w1():
312{
313    Token t;
314}
315{
316    t=<PP_NEWLINECHAR> { sb.append(t.image); }
317        |
318    t=<PP_WHITESPACE> { sb.append(t.image); }
319        |
320    t=<PP_COMMENT_START> { sb.append(t.image); } t=<PP_COMMENT_END> { sb.append(t.image); }
321}
322
323void pp_w():
324{
325}
326{
327 ( pp_w1() )*
328}
329
330/*************
331 *
332 * Parser definition for the main MapCSS parser:
333 *
334 * <pre>
335 *
336 *                       rule
337 *  _______________________|______________________________
338 * |                                                      |
339 *        selector                      declaration
340 *  _________|___________________   _________|____________
341 * |                             | |                      |
342 *
343 * way|z11-12[highway=residential] { color: red; width: 3 }
344 *
345 *    |_____||___________________|   |_________|
346 *       |            |                   |
347 *     zoom       condition          instruction
348 *
349 * more general:
350 *
351 * way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
352 *
353 * 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
354 *
355 * </pre>
356 */
357
358int uint() :
359{
360    Token i;
361}
362{
363    i=<UINT> { return Integer.parseInt(i.image); }
364}
365
366int int_() :
367{
368    int i;
369}
370{
371    <MINUS> i=uint() { return -i; } | i=uint() { return i; }
372}
373
374float ufloat() :
375{
376    Token f;
377}
378{
379    ( f=<UFLOAT> | f=<UINT> )
380    { return Float.parseFloat(f.image); }
381}
382
383float float_() :
384{
385    float f;
386}
387{
388    <MINUS> f=ufloat() { return -f; } | f=ufloat() { return f; }
389}
390
391String string() :
392{
393    Token t;
394}
395{
396    t=<STRING>
397    { return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
398}
399
400String ident():
401{
402    Token t;
403    String s;
404}
405{
406    ( t=<IDENT> | t=<SET> ) { return t.image; }
407}
408
409String string_or_ident() :
410{
411    Token t;
412    String s;
413}
414{
415    ( s=ident() | s=string() ) { return s; }
416}
417
418String regex() :
419{
420    Token t;
421}
422{
423    t=<REGEX>
424    { return t.image.substring(1, t.image.length() - 1); }
425}
426
427/**
428 * white-space
429 */
430void s() :
431{
432}
433{
434    ( <S> )?
435}
436
437/**
438 * mix of white-space and comments
439 */
440void w() :
441{
442}
443{
444    ( <S> | <COMMENT_START> <COMMENT_END> )*
445}
446
447/**
448 * comma delimited list of floats (at least 2, all &gt;= 0)
449 */
450List<Float> float_array() :
451{
452    float f;
453    List<Float> fs = new ArrayList<Float>();
454}
455{
456    f=ufloat() { fs.add(f); }
457    (
458        <COMMA> s()
459        f=ufloat() { fs.add(f); }
460    )+
461    {
462        return fs;
463    }
464}
465
466/**
467 * entry point for the main parser
468 */
469void sheet(MapCSSStyleSource sheet):
470{
471    MapCSSRule r;
472}
473{
474    { this.sheet = sheet; }
475    w()
476    (
477        try {
478            r=rule() { if (r != null) { sheet.rules.add(r); } } w()
479        } catch (MapCSSException mex) {
480            error_skipto(RBRACE, mex);
481            w();
482        } catch (ParseException ex) {
483            error_skipto(RBRACE, null);
484            w();
485        }
486    )*
487    <EOF>
488}
489
490MapCSSRule rule():
491{
492    List<Selector> selectors = new ArrayList<Selector>();
493    Selector sel;
494    List<Instruction> decl;
495}
496{
497    sel=child_selector() { selectors.add(sel); }
498    (
499        <COMMA> w()
500        sel=child_selector() { selectors.add(sel); }
501    )*
502    decl=declaration()
503    { return new MapCSSRule(selectors, decl); }
504}
505
506Selector child_selector() :
507{
508    Selector.ChildOrParentSelectorType type = null;
509    Condition c;
510    List<Condition> conditions = new ArrayList<Condition>();
511    Selector selLeft;
512    LinkSelector selLink = null;
513    Selector selRight = null;
514}
515{
516    selLeft=selector() w()
517    (
518        (
519            (
520                <GREATER> { type = Selector.ChildOrParentSelectorType.CHILD; }
521            |
522                <LESS> { type = Selector.ChildOrParentSelectorType.PARENT; }
523            |
524                <PLUS> { type = Selector.ChildOrParentSelectorType.SIBLING; }
525            )
526            ( ( c=condition(Context.LINK) | c=class_or_pseudoclass(Context.LINK) ) { conditions.add(c); } )*
527        |
528            <ELEMENT_OF> { type = Selector.ChildOrParentSelectorType.ELEMENT_OF; }
529        |
530            <CROSSING> { type = Selector.ChildOrParentSelectorType.CROSSING; }
531        )
532        { selLink = new LinkSelector(conditions); }
533        w()
534        selRight=selector() w()
535    )?
536    { return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, type) : selLeft; }
537}
538
539Selector selector() :
540{
541    Token base;
542    Condition c;
543    Pair<Integer, Integer> r = null;
544    List<Condition> conditions = new ArrayList<Condition>();
545    String sub = null;
546}
547{
548    ( base=<IDENT> | base=<STAR> )
549    ( r=zoom() )?
550    ( ( c=condition(Context.PRIMITIVE) | c=class_or_pseudoclass(Context.PRIMITIVE) ) { conditions.add(c); } )*
551    ( sub=subpart() )?
552    { return new GeneralSelector(base.image, r, conditions, sub); }
553}
554
555Pair<Integer, Integer> zoom() :
556{
557    Integer min = 0;
558    Integer max = Integer.MAX_VALUE;
559}
560{
561    <PIPE_Z>
562    (
563            <MINUS> max=uint()
564        |
565        LOOKAHEAD(2)
566            min=uint() <MINUS> ( max=uint() )?
567        |
568            min=uint() { max = min; }
569    )
570    { return new Pair<Integer, Integer>(min, max); }
571}
572
573Condition condition(Context context) :
574{
575    Condition c;
576    Expression e;
577}
578{
579    <LSQUARE> s()
580    (
581        LOOKAHEAD( simple_key_condition(context) s() <RSQUARE> )
582            c=simple_key_condition(context) s() <RSQUARE> { return c; }
583        |
584        LOOKAHEAD( simple_key_value_condition(context) s() <RSQUARE> )
585            c=simple_key_value_condition(context) s() <RSQUARE> { return c; }
586        |
587            e=expression() <RSQUARE> { return Condition.createExpressionCondition(e, context); }
588    )
589}
590
591String tag_key() :
592{
593    String s, s2;
594    Token t;
595}
596{
597        s=string() { return s; }
598    |
599        s=ident() ( <COLON> s2=ident() { s += ':' + s2; } )* { return s; }
600}
601
602Condition simple_key_condition(Context context) :
603{
604    boolean not = false;
605    Condition.KeyMatchType matchType = null;;
606    String key;
607}
608{
609    ( <EXCLAMATION> { not = true; } )?
610    (
611        { matchType = Condition.KeyMatchType.REGEX; } key = regex()
612    |
613        key = tag_key()
614    )
615    ( LOOKAHEAD(2) <QUESTION> <EXCLAMATION> { matchType = Condition.KeyMatchType.FALSE; } )?
616    (              <QUESTION>               { matchType = Condition.KeyMatchType.TRUE;  } )?
617    { return Condition.createKeyCondition(key, not, matchType, context); }
618}
619
620Condition simple_key_value_condition(Context context) :
621{
622    String key;
623    String val;
624    float f;
625    int i;
626    Condition.Op op;
627    boolean considerValAsKey = false;
628}
629{
630    key=tag_key() s()
631    (
632        LOOKAHEAD(3)
633            (
634                    <EQUAL> <TILDE> { op=Condition.Op.REGEX; }
635                |
636                    <EXCLAMATION> <TILDE> { op=Condition.Op.NREGEX; }
637            )
638            s()
639            ( <STAR> { considerValAsKey=true; } )?
640            val=regex()
641        |
642            (
643                    <EXCLAMATION> <EQUAL> { op=Condition.Op.NEQ; }
644                |
645                    <EQUAL> { op=Condition.Op.EQ; }
646                |
647                    <TILDE> <EQUAL> { op=Condition.Op.ONE_OF; }
648                |
649                    <CARET> <EQUAL> { op=Condition.Op.BEGINS_WITH; }
650                |
651                    <DOLLAR> <EQUAL> { op=Condition.Op.ENDS_WITH; }
652                |
653                    <STAR> <EQUAL> { op=Condition.Op.CONTAINS; }
654            )
655            s()
656            ( <STAR> { considerValAsKey=true; } )?
657            (
658                LOOKAHEAD(2)
659                        i=int_() { val=Integer.toString(i); }
660                    |
661                        f=float_() { val=Float.toString(f); }
662                    |
663                        val=string_or_ident()
664            )
665        |
666            (
667                    <GREATER_EQUAL> { op=Condition.Op.GREATER_OR_EQUAL; }
668                |
669                    <GREATER> { op=Condition.Op.GREATER; }
670                |
671                    <LESS_EQUAL> { op=Condition.Op.LESS_OR_EQUAL; }
672                |
673                    <LESS> { op=Condition.Op.LESS; }
674            )
675            s()
676            f=float_() { val=Float.toString(f); }
677    )
678    { return Condition.createKeyValueCondition(key, val, op, context, considerValAsKey); }
679}
680
681Condition class_or_pseudoclass(Context context) :
682{
683    String s;
684    boolean not = false;
685    boolean pseudo;
686}
687{
688    ( <EXCLAMATION> { not = true; } )?
689    (
690        <FULLSTOP> { pseudo = false; }
691    |
692        <COLON> { pseudo = true; }
693    )
694    s=ident()
695    { return pseudo
696        ? Condition.createPseudoClassCondition(s, not, context)
697        : Condition.createClassCondition(s, not, context); }
698}
699
700String subpart() :
701{
702    String s;
703}
704{
705    <DCOLON>
706    ( s=ident() { return s; } | <STAR> { return "*"; } )
707}
708
709List<Instruction> declaration() :
710{
711    List<Instruction> ins = new ArrayList<Instruction>();
712    Instruction i;
713    Token key;
714    Object val = null;
715}
716{
717    <LBRACE> w()
718    (
719        (
720            <SET> w()
721            (<FULLSTOP>)? // specification allows "set .class" to set "class". we also support "set class"
722            key=<IDENT> w()
723            ( <EQUAL> val=expression() )?
724            { ins.add(new Instruction.AssignmentInstruction(key.image, val == null ? true : val, true)); }
725            ( <RBRACE> { return ins; } | <SEMICOLON> w() )
726        )
727    |
728        key=<IDENT> w() <COLON> w()
729        (
730            LOOKAHEAD( float_array() w() ( <SEMICOLON> | <RBRACE> ) )
731                val=float_array()
732                { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
733                w()
734                ( <RBRACE> { return ins; } | <SEMICOLON> w() )
735            |
736            LOOKAHEAD( expression() ( <SEMICOLON> | <RBRACE> ) )
737                val=expression()
738                { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
739                ( <RBRACE> { return ins; } | <SEMICOLON> w() )
740            |
741                val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
742        )
743    )*
744    <RBRACE>
745    { return ins; }
746}
747
748Expression expression():
749{
750    List<Expression> args = new ArrayList<Expression>();
751    Expression e;
752    String op = null;
753}
754{
755    (
756        <EXCLAMATION> { op = "not"; } w() e=primary() { args.add(e); } w()
757    |
758        <MINUS> { op = "minus"; } w() e=primary() { args.add(e); } w()
759    |
760
761        (
762            e=primary() { args.add(e); } w()
763            (
764                    ( <PLUS> { op = "plus"; } w() e=primary() { args.add(e); } w() )+
765                |
766                    ( <STAR> { op = "times"; } w() e=primary() { args.add(e); } w() )+
767                |
768                    ( <MINUS> { op = "minus"; } w() e=primary() { args.add(e); } w() )+
769                |
770                    ( <SLASH> { op = "divided_by"; } w() e=primary() { args.add(e); } w() )+
771                |
772                    <GREATER_EQUAL> { op = "greater_equal"; } w() e=primary() { args.add(e); } w()
773                |
774                    <LESS_EQUAL> { op = "less_equal"; } w() e=primary() { args.add(e); } w()
775                |
776                    <GREATER> { op = "greater"; } w() e=primary() { args.add(e); } w()
777                |
778                    <EQUAL> ( <EQUAL> )? { op = "equal"; } w() e=primary() { args.add(e); } w()
779                |
780                    <LESS> { op = "less"; } w() e=primary() { args.add(e); } w()
781                |
782                    <AMPERSAND> <AMPERSAND> { op = "and"; } w() e=primary() { args.add(e); } w()
783                |
784                    <PIPE> <PIPE> { op = "or"; } w() e=primary() { args.add(e); } w()
785                |
786                    <QUESTION> { op = "cond"; } w() e=primary() { args.add(e); } w() <COLON> w() e=primary() { args.add(e); } w()
787            )?
788        )
789    )
790    {
791        if (op == null)
792            return args.get(0);
793        return ExpressionFactory.createFunctionExpression(op, args);
794    }
795}
796
797Expression primary() :
798{
799    Expression nested;
800    Expression fn;
801    Object lit;
802}
803{
804    LOOKAHEAD(3) // both function and identifier start with an identifier (+ optional whitespace)
805        fn=function() { return fn; }
806    |
807        lit=literal() { return new LiteralExpression(lit); }
808    |
809        <LPAR> w() nested=expression() <RPAR> { return nested; }
810}
811
812Expression function() :
813{
814    Expression arg;
815    String name;
816    List<Expression> args = new ArrayList<Expression>();
817}
818{
819    name=ident() w()
820    <LPAR> w()
821    (
822        arg=expression() { args.add(arg); }
823        ( <COMMA> w() arg=expression() { args.add(arg); } )*
824    )?
825    <RPAR>
826    { return ExpressionFactory.createFunctionExpression(name, args); }
827}
828
829Object literal() :
830{
831    String val, pref;
832    Token t;
833    float f;
834}
835{
836        LOOKAHEAD(2)
837        pref=ident() t=<HEXCOLOR>
838        { return Main.pref.getColor("mappaint." + (sheet == null ? "MapCSS" : sheet.title) + "." + pref, ColorHelper.html2color(t.image)); }
839    |
840        t=<IDENT> { return new Keyword(t.image); }
841    |
842        val=string() { return val; }
843    |
844        <PLUS> f=ufloat() { return new Instruction.RelativeFloat(f); }
845    |
846        f=ufloat() { return f; }
847    |
848        t=<HEXCOLOR> { return ColorHelper.html2color(t.image); }
849}
850
851JAVACODE
852void error_skipto(int kind, MapCSSException me) {
853    if (token.kind == EOF)
854        throw new ParseException("Reached end of file while parsing");
855       
856    Exception e = null;       
857    ParseException pe = generateParseException();
858
859    if (me != null) {
860        me.setLine(pe.currentToken.next.beginLine);
861        me.setColumn(pe.currentToken.next.beginColumn);
862        e = me;
863    } else {
864        e = new ParseException(pe.getMessage()); // prevent memory leak
865    }
866   
867    Main.error("Skipping to the next rule, because of an error:");
868    Main.error(e);
869    if (sheet != null) {
870        sheet.logError(e);
871    }
872    Token t;
873    do {
874        t = getNextToken();
875    } while (t.kind != kind && t.kind != EOF);
876    if (t.kind == EOF)
877        throw new ParseException("Reached end of file while parsing");
878}
879
880JAVACODE
881/**
882 * read everything to the next semicolon
883 */
884String readRaw() {
885    Token t;
886    StringBuilder s = new StringBuilder();
887    while (true) {
888        t = getNextToken();
889        if ((t.kind == S || t.kind == STRING || t.kind == UNEXPECTED_CHAR) &&
890                t.image.contains("\n")) {
891            ParseException e = new ParseException(String.format("Warning: end of line while reading an unquoted string at line %s column %s.", t.beginLine, t.beginColumn));
892            Main.error(e);
893            if (sheet != null) {
894                sheet.logError(e);
895            }
896        }
897        if (t.kind == SEMICOLON || t.kind == EOF)
898            break;
899        s.append(t.image);
900    }
901    if (t.kind == EOF)
902        throw new ParseException("Reached end of file while parsing");
903    return s.toString();
904}
905
Note: See TracBrowser for help on using the repository browser.