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

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

mapcss: some rework of Error Handling, (Multi)Cascade and icon loading

File size: 13.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2options {
3  STATIC = false;
4}
5
6PARSER_BEGIN(MapCSSParser)
7package org.openstreetmap.josm.gui.mappaint.mapcss.parser;
8
9import java.awt.Color;
10import java.util.ArrayList;
11import java.util.List;
12
13import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
14import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
15import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
16import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
17import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
18import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
19import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.FunctionExpression;
20import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.LiteralExpression;
21import org.openstreetmap.josm.tools.Pair;
22
23public class MapCSSParser {
24    MapCSSStyleSource sheet;
25}
26PARSER_END(MapCSSParser)
27
28/*************
29 * Token definitions
30 */
31
32<DEFAULT>
33TOKEN:
34{
35    < IDENT: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","-","0"-"9"] )* >
36|   < UINT: ["1"-"9"] ( ["0"-"9"] )* >
37|   < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
38|   < STRING: "\"" ( [" ","!","#"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\\"" | "\\\\" )*  "\"" >
39|   < #REGEX_CHAR_WITHOUT_STAR: [" "-")","+"-".","0"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\/" | "\\\\" >
40|   < REGEX: "/" <REGEX_CHAR_WITHOUT_STAR> ( <REGEX_CHAR_WITHOUT_STAR> | "*" )*  "/" >
41|   < #H: ["0"-"9","a"-"f","A"-"F"] >
42|   < HEXCOLOR: "#" ( <H><H><H><H><H><H> | <H><H><H> ) >
43|   < S: ( " " | "\t" | "\n" | "\r" | "\f" )+ >
44|   < STAR: "*" >
45|   < SLASH: "/" >
46|   < LBRACE: "{" >
47|   < RBRACE: "}" >
48|   < LSQUARE: "[" >
49|   < RSQUARE: "]" >
50|   < LPAR: "(" >
51|   < RPAR: ")" >
52|   < GREATER_EQUAL: ">=" >
53|   < LESS_EQUAL: "<=" >
54|   < GREATER: ">" >
55|   < LESS: "<" >
56|   < EQUAL: "=" >
57|   < EXCLAMATION: "!" >
58|   < TILDE: "~" >
59|   < COLON: ":" >
60|   < DCOLON: "::" >
61|   < SEMICOLON: ";" >
62|   < COMMA: "," >
63|   < PIPE: "|" >
64|   < PIPE_Z: "|z" >
65|   < PLUS: "+" >
66|   < MINUS: "-" >
67|   < AMPERSAND: "&" >
68|   < QUESTION: "?" >
69|   < DOLLAR: "$" >
70|   < CARET: "^" >
71|   < COMMENT_START: "/*" > : COMMENT
72|   < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
73}
74
75<COMMENT>
76TOKEN:
77{
78    < COMMENT_END: "*/" > : DEFAULT
79}
80
81<COMMENT>
82SKIP:
83{
84    < ~[] >
85}
86
87/*************
88 * Parser definitions
89 *
90 *                       rule
91 *  _______________________|______________________________
92 * |                                                      |
93 *        selector                      declaration
94 *  _________|___________________   _________|____________
95 * |                             | |                      |
96 *
97 * way|z11-12[highway=residential] { color: red; width: 3 }
98 *
99 *    |_____||___________________|   |_________|
100 *       |            |                   |
101 *     zoom       condition          instruction
102 *
103 * more general:
104 *
105 * way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
106 *
107 * 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
108 *
109 */
110
111int uint() :
112{
113    Token i;
114}
115{
116    i=<UINT> { return Integer.parseInt(i.image); }
117}
118
119float ufloat() :
120{
121    Token f;
122}
123{
124    ( f=<UFLOAT> | f=<UINT> )
125    { return Float.parseFloat(f.image); }
126}
127
128float float_() :
129{
130    float f;
131}
132{
133    <MINUS> f=ufloat() { return -f; } | f=ufloat() { return f; }
134}
135
136String string() :
137{
138    Token t;
139}
140{
141    t=<STRING>
142    { return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
143}
144
145String string_or_ident() :
146{
147    Token t;
148    String s;
149}
150{
151    t=<IDENT> { return t.image; } | s=string() { return s; }
152}
153
154String regex() :
155{
156    Token t;
157}
158{
159    t=<REGEX>
160    { return t.image.substring(1, t.image.length() - 1); }
161}
162
163/**
164 * white-space
165 */
166void s() :
167{
168}
169{
170    ( <S> )?
171}
172
173/**
174 * mix of white-space and comments
175 */
176void w() :
177{
178}
179{
180    ( <S> | <COMMENT_START> <COMMENT_END> )*
181}
182
183/**
184 * comma delimited list of floats (at least 2, all >= 0)
185 */
186List<Float> float_array() :
187{
188    float f;
189    List<Float> fs = new ArrayList<Float>();
190}
191{
192    f=ufloat() { fs.add(f); }
193    (
194        <COMMA> s()
195        f=ufloat() { fs.add(f); }
196    )+
197    {
198        return fs;
199    }
200}
201
202/**
203 * root
204 */
205void sheet(MapCSSStyleSource sheet):
206{
207    MapCSSRule r;
208    Token com = null;
209}
210{
211    { this.sheet = sheet; }
212    w()
213    (
214        try {
215            r=rule() { if (r != null) { sheet.rules.add(r); } } w()
216        } catch (ParseException ex) {
217            error_skipto(RBRACE);
218            w();
219        }
220    )*
221    <EOF>
222}
223
224MapCSSRule rule():
225{
226    List<Selector> selectors = new ArrayList<Selector>();
227    Selector sel;
228    List<Instruction> decl;
229}
230{
231    sel=selector() { selectors.add(sel); } w()
232    (
233        <COMMA> w()
234        sel=selector() { selectors.add(sel); } w()
235    )*
236    decl=declaration()
237    { return new MapCSSRule(selectors, decl); }
238}
239
240Selector selector() :
241{
242    Token base;
243    Condition c;
244    Pair<Integer, Integer> r = null;
245    List<Condition> conditions = new ArrayList<Condition>();
246    String sub = null;
247}
248{
249    ( base=<IDENT> | base=<STAR> )
250    ( r=zoom() )?
251    ( ( c=condition() | c=pseudoclass() ) { conditions.add(c); } )*
252    ( sub=subpart() )?
253    { return new Selector(base.image, r, conditions, sub); }
254}
255
256Pair<Integer, Integer> zoom() :
257{
258    Integer min = 0;
259    Integer max = Integer.MAX_VALUE;
260}
261{
262    <PIPE_Z>
263    (
264            <MINUS> max=uint()
265        |
266            min=uint() ( <MINUS> ( max=uint() )? )?
267    )
268    { return new Pair<Integer, Integer>(min, max); }
269}
270
271Condition condition() :
272{
273    Condition c;
274    Expression e;
275}
276{
277    <LSQUARE>
278    (
279        LOOKAHEAD(3)
280            c=simple_key_condition() <RSQUARE> { return c; }
281        |
282        LOOKAHEAD(5)
283            c=simple_key_value_condition() <RSQUARE> { return c; }
284        |
285            e=expression() <RSQUARE> { return new Condition.ExpressionCondition(e); }
286    )
287}
288
289Condition simple_key_condition() :
290{
291    boolean not = false;
292    boolean yes = false;
293    String key;
294}
295{
296    ( <EXCLAMATION> { not = true; } )?
297    key=string_or_ident()
298    ( <QUESTION> { yes = true; } )?
299    { return new Condition.KeyCondition(key, not, yes); }
300}
301
302Condition simple_key_value_condition() :
303{
304    String key;
305    String val;
306    float f;
307    Condition.Op op;
308}
309{
310    key=string_or_ident()
311    (
312            (
313                    <EXCLAMATION> <EQUAL> { op=Condition.Op.NEQ; } val=string_or_ident()
314                |
315                    <EQUAL> { op=Condition.Op.EQ; }
316                    (
317                            <TILDE> { op=Condition.Op.REGEX; }
318                            val=regex()
319                        |
320                            val=string_or_ident()
321                    )
322                |
323                    <TILDE> <EQUAL> { op=Condition.Op.ONE_OF; } val=string_or_ident()
324                |
325                    <CARET> <EQUAL> { op=Condition.Op.BEGINS_WITH; } val=string_or_ident()
326                |
327                    <DOLLAR> <EQUAL> { op=Condition.Op.ENDS_WITH; } val=string_or_ident()
328                |
329                    <STAR> <EQUAL> { op=Condition.Op.CONTAINS; } val=string_or_ident()
330            )
331            { return new Condition.KeyValueCondition(key, val, op); }
332        |
333            (
334                    <GREATER_EQUAL> { op=Condition.Op.GREATER_OR_EQUAL; }
335                |
336                    <GREATER> { op=Condition.Op.GREATER; }
337                |
338                    <LESS_EQUAL> { op=Condition.Op.LESS_OR_EQUAL; }
339                |
340                    <LESS> { op=Condition.Op.LESS; }
341            )
342            f=float_()
343            { return new Condition.KeyValueCondition(key, Float.toString(f), op); }
344    )
345}
346
347Condition pseudoclass() :
348{
349    Token t;
350    boolean not = false;
351}
352{
353    ( <EXCLAMATION> { not = true; } )?
354    <COLON>
355    t=<IDENT>
356    { return new Condition.PseudoClassCondition(t.image, not); }
357}
358
359String subpart() :
360{
361    Token t;
362}
363{
364    <DCOLON>
365    ( t=<IDENT> | t=<STAR> )
366    { return t.image; }
367}
368
369List<Instruction> declaration() :
370{
371    List<Instruction> ins = new ArrayList<Instruction>();
372    Instruction i;
373    Token key;
374    Object val;
375}
376{
377    <LBRACE> w()
378    (
379        key=<IDENT> w() <COLON> w()
380        (
381            LOOKAHEAD( float_array() w() ( <SEMICOLON> | <RBRACE> ) )
382                val=float_array()
383                { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
384                w()
385                ( <RBRACE> { return ins; } | <SEMICOLON> w() )
386            |
387            LOOKAHEAD( expression() ( <SEMICOLON> | <RBRACE> ) )
388                val=expression()
389                { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
390                ( <RBRACE> { return ins; } | <SEMICOLON> w() )
391            |
392                val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
393        )
394    )*
395    <RBRACE>
396    { return ins; }
397}
398
399Expression expression():
400{
401    List<Expression> args = new ArrayList<Expression>();
402    Expression e;
403    String op = null;
404}
405{
406    (
407        <EXCLAMATION> { op = "not"; } w() e=primary() { args.add(e); } w()
408    |
409        <MINUS> { op = "minus"; } w() e=primary() { args.add(e); } w()
410    |
411
412        (
413            e=primary() { args.add(e); } w()
414            (
415                    ( <PLUS> { op = "plus"; } w() e=primary() { args.add(e); } w() )+
416                |
417                    ( <STAR> { op = "times"; } w() e=primary() { args.add(e); } w() )+
418                |
419                    ( <MINUS> { op = "minus"; } w() e=primary() { args.add(e); } w() )+
420                |
421                    ( <SLASH> { op = "divided_by"; } w() e=primary() { args.add(e); } w() )+
422                |
423                    <GREATER_EQUAL> { op = "greater_equal"; } w() e=primary() { args.add(e); } w()
424                |
425                    <LESS_EQUAL> { op = "less_equal"; } w() e=primary() { args.add(e); } w()
426                |
427                    <GREATER> { op = "greater"; } w() e=primary() { args.add(e); } w()
428                |
429                    <EQUAL> ( <EQUAL> )? { op = "equal"; } w() e=primary() { args.add(e); } w()
430                |
431                    <LESS> { op = "less"; } w() e=primary() { args.add(e); } w()
432                |
433                    <AMPERSAND> <AMPERSAND> { op = "and"; } w() e=primary() { args.add(e); } w()
434                |
435                    <PIPE> <PIPE> { op = "or"; } w() e=primary() { args.add(e); } w()
436                |
437                    <QUESTION> { op = "cond"; } w() e=primary() { args.add(e); } w() <COLON> w() e=primary() { args.add(e); } w()
438            )?
439        )
440    )
441    {
442        if (op == null)
443            return args.get(0);
444        return new FunctionExpression(op, args);
445    }
446}
447
448Expression primary() :
449{
450    Expression nested;
451    FunctionExpression fn;
452    Object lit;
453}
454{
455    LOOKAHEAD(2) // both function and identifier start with an identifier
456        fn=function() { return fn; }
457    |
458        lit=literal() { return new LiteralExpression(lit); }
459    |
460        <LPAR> w() nested=expression() <RPAR> { return nested; }
461}
462
463FunctionExpression function() :
464{
465    Token tmp;
466    Expression arg;
467    String name;
468    List<Expression> args = new ArrayList<Expression>();
469}
470{
471    tmp=<IDENT> { name = tmp.image; } w()
472    <LPAR> w()
473    (
474        arg=expression() { args.add(arg); }
475        ( <COMMA> w() arg=expression() { args.add(arg); } )*
476    )?
477    <RPAR>
478    { return new FunctionExpression(name, args); }
479}
480
481Object literal() :
482{
483    Object val;
484    Token t;
485    float f;
486}
487{
488        val=string_or_ident() { return val; }
489    |
490        <PLUS> f=ufloat() { return new Instruction.RelativeFloat(f); }
491    |
492        f=ufloat() { return f; }
493    |
494        t=<HEXCOLOR>
495            {
496                String clr = t.image.substring(1);
497                if (clr.length() == 3) {
498                    clr = new String(new char[] {clr.charAt(0),clr.charAt(0),clr.charAt(1),clr.charAt(1),clr.charAt(2),clr.charAt(2)});
499                }
500                if (clr.length() != 6)
501                    throw new AssertionError();
502                return new Color(Integer.parseInt(clr, 16));
503            }
504}
505
506JAVACODE
507void error_skipto(int kind) {
508    if (token.kind == EOF)
509        throw new ParseException("Reached end of file while parsing");
510    ParseException e = generateParseException();
511    System.err.println("Skipping to the next rule, because of an error:");
512    System.err.println(e);
513    if (sheet != null) {
514        sheet.logError(new ParseException(e.getMessage()));
515    }
516    Token t;
517    do {
518        t = getNextToken();
519    } while (t.kind != kind && t.kind != EOF);
520    if (t.kind == EOF)
521        throw new ParseException("Reached end of file while parsing");
522}
523
524JAVACODE
525/**
526 * read everything to the next semicolon
527 */
528String readRaw() {
529    Token t;
530    StringBuilder s = new StringBuilder();
531    while (true) {
532        t = getNextToken();
533        if ((t.kind == S || t.kind == STRING || t.kind == UNEXPECTED_CHAR) &&
534                t.image.contains("\n")) {
535            ParseException e = new ParseException(String.format("Warning: end of line while reading an unquoted string at line %s column %s.", t.beginLine, t.beginColumn));
536            System.err.println(e);
537            if (sheet != null) {
538                sheet.logError(e);
539            }
540        }
541        if (t.kind == SEMICOLON || t.kind == EOF)
542            break;
543        s.append(t.image);
544    }
545    if (t.kind == EOF)
546        throw new ParseException("Reached end of file while parsing");
547    return s.toString();
548}
549
Note: See TracBrowser for help on using the repository browser.