| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| /** |
| * This is a grammar for advanced CSS in Flex. It parses the CSS document and |
| * generate DOM objects. |
| */ |
| grammar CSS; |
| |
| options |
| { |
| language = Java; |
| output = AST; |
| k = 2; |
| } |
| |
| tokens |
| { |
| I_RULES; I_MEDIUM_CONDITIONS; I_DECL; I_RULE; I_SELECTOR_GROUP; I_SELECTOR; |
| I_SIMPLE_SELECTOR; I_CHILD_SELECTOR; I_PRECEDED_SELECTOR; I_SIBLING_SELECTOR; I_ARRAY; |
| } |
| |
| @header |
| { |
| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.flex.compiler.internal.css; |
| |
| import java.util.Map; |
| import java.util.HashMap; |
| import org.apache.flex.compiler.problems.CSSParserProblem; |
| } |
| |
| @lexer::header |
| { |
| package org.apache.flex.compiler.internal.css; |
| |
| import org.apache.flex.compiler.problems.CSSParserProblem; |
| } |
| |
| @lexer::members |
| { |
| |
| /** |
| * Lexer problems. |
| */ |
| protected List<CSSParserProblem> problems = new ArrayList<CSSParserProblem>(); |
| |
| /** |
| * Collect lexer problems. |
| */ |
| @Override |
| public void displayRecognitionError(String[] tokenNames, RecognitionException e) |
| { |
| problems.add(CSSParserProblem.create(this, tokenNames, e)); |
| } |
| } |
| |
| @members |
| { |
| |
| /** |
| * Parser problems. |
| */ |
| protected List<CSSParserProblem> problems = new ArrayList<CSSParserProblem>(); |
| |
| /** |
| * Collect parser problems. |
| */ |
| @Override |
| public void displayRecognitionError(String[] tokenNames, RecognitionException e) |
| { |
| problems.add(CSSParserProblem.create(this, tokenNames, e)); |
| } |
| |
| /** |
| * Check if the cursor is at the end of a simple selector. |
| * @return -1 if not end |
| * @return 0 if end |
| * @return 1 if ended by Child combinator |
| * @return 2 if ended by Preceded combinator |
| * @return 3 if ended by Sibling combinator |
| */ |
| private final int endOfSimpleSelector() |
| { |
| final CommonToken nextToken = (CommonToken) getTokenStream().LT(1); |
| if (nextToken == null) |
| return 0; |
| |
| final int nextType = nextToken.getType(); |
| |
| if (nextType == CHILD) |
| { |
| getTokenStream().consume(); |
| return 1; |
| } |
| if (nextType == PRECEDED) |
| { |
| getTokenStream().consume(); |
| return 2; |
| } |
| if (nextType == TILDE) |
| { |
| getTokenStream().consume(); |
| return 3; |
| } |
| |
| // Check if there's white space between the previous token and the next token. |
| final CommonToken lastToken = (CommonToken) getTokenStream().LT(-1); |
| if (lastToken != null && nextToken != null) |
| { |
| final int lastStop = lastToken.getStopIndex(); |
| final int nextStart = nextToken.getStartIndex(); |
| if (lastStop + 1 < nextStart) |
| { |
| return 0; |
| } |
| } |
| |
| // If the next token is "{" or ",", it's also end of a selector. |
| if (nextType == BLOCK_OPEN || nextType == COMMA) |
| { |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| } |
| |
| /** |
| * Root rule for a CSS file. |
| */ |
| stylesheet |
| : ( namespaceStatement | fontFace | keyframes | mediaQuery | ruleset )* |
| ; |
| |
| /** |
| * This rule matches an "@namespace" statement. |
| * |
| * For example, declaring a namespace prefix: |
| * @namespace mx "library://ns.adobe.com/flex/mx"; |
| * |
| * Declaring the default namespace: |
| * @namespace "library://ns.adobe.com/flex/spark"; |
| */ |
| namespaceStatement |
| : AT_NAMESPACE ID? STRING SEMI_COLONS |
| -> ^(AT_NAMESPACE ID? STRING) |
| ; |
| |
| /** |
| * This rule matches a media query block. |
| * |
| * For example: |
| * @media all { ... } |
| * |
| * A media query block can have one or many rulesets inside. |
| */ |
| keyframes |
| : AT_KEYFRAMES ID BLOCK_OPEN ruleset* BLOCK_END |
| -> ^(AT_KEYFRAMES ID ruleset*) |
| | AT_WEBKIT_KEYFRAMES ID BLOCK_OPEN ruleset* BLOCK_END |
| -> ^(AT_WEBKIT_KEYFRAMES ID ruleset*) |
| ; |
| |
| /** |
| * This rule matches a media query block. |
| * |
| * For example: |
| * @media all { ... } |
| * |
| * A media query block can have one or many rulesets inside. |
| */ |
| mediaQuery |
| : AT_MEDIA medium BLOCK_OPEN ruleset* BLOCK_END |
| -> ^(AT_MEDIA medium ruleset*) |
| ; |
| |
| /** |
| * This rule matches the actual Flex media query conditions. |
| * For example: (application-dpi: 240) and (os-platform: "Android") |
| */ |
| medium |
| : mediumCondition |
| ( |
| // Flex only support "and" at the moment. |
| 'and' |
| mediumCondition |
| | mediumCondition |
| )* |
| -> ^(I_MEDIUM_CONDITIONS mediumCondition*) |
| ; |
| |
| /** |
| * This rule matches a media query condition expression. It is either a |
| * word (like "all", "screen") or an expression like "(....)". |
| */ |
| mediumCondition |
| : ID |
| | ONLY ID |
| | ARGUMENTS |
| | COMMA |
| ; |
| |
| /** |
| * For example: |
| * |
| * @font-face { |
| * src: url("../assets/MyriadWebPro.ttf"); |
| * fontFamily: myFontFamily; |
| * advancedAntiAliasing: true; |
| * } |
| * |
| * @see http://livedocs.adobe.com/flex/3/html/help.html?content=fonts_04.html |
| */ |
| fontFace |
| : AT_FONT_FACE declarationsBlock -> ^(AT_FONT_FACE declarationsBlock) |
| ; |
| |
| /** |
| * This rule matches a CSS Ruleset. |
| */ |
| ruleset |
| : selectorGroup declarationsBlock |
| -> ^(I_RULE selectorGroup declarationsBlock) |
| ; |
| |
| /** |
| * This rule matches a group of comma-separated selectors. |
| */ |
| selectorGroup |
| : compoundSelector ( COMMA compoundSelector )* |
| -> ^(I_SELECTOR_GROUP compoundSelector+) |
| ; |
| |
| /** |
| * Compound selector is a chain of simple selectors connected by combinators. |
| * Each compound selector has one "subject", which is the element name of the |
| * right most simple selector in the chain. |
| * |
| * For example: |
| * s|VBox s|HBox.content s|Button |
| */ |
| compoundSelector |
| @init |
| { |
| final List<Object> simpleSelectorNodeList = new ArrayList<Object>(); |
| Object currentSimpleSelectorNode = adaptor.create(I_SIMPLE_SELECTOR, "I_SIMPLE_SELECTOR"); |
| Token simpleSelectorStartToken = null; |
| Token simpleSelectorStopToken = null; |
| } |
| @after |
| { |
| for(final Object simpleSelectorNode : simpleSelectorNodeList) |
| adaptor.addChild($compoundSelector.tree, simpleSelectorNode); |
| } |
| : ( l=simpleSelectorFraction |
| { |
| // expand token range of the current simple selector |
| if (simpleSelectorStartToken == null) |
| simpleSelectorStartToken = $l.start; |
| simpleSelectorStopToken = $l.stop; |
| |
| adaptor.addChild(currentSimpleSelectorNode, $l.tree); |
| |
| int end = endOfSimpleSelector(); |
| if (end != -1) |
| { |
| adaptor.setTokenBoundaries( |
| currentSimpleSelectorNode, |
| simpleSelectorStartToken, |
| simpleSelectorStopToken); |
| simpleSelectorNodeList.add(currentSimpleSelectorNode); |
| |
| simpleSelectorStartToken = null; |
| simpleSelectorStopToken = null; |
| |
| if (end == 1) |
| currentSimpleSelectorNode = adaptor.create(I_CHILD_SELECTOR, "I_CHILD_SELECTOR"); |
| else if (end == 2) |
| currentSimpleSelectorNode = adaptor.create(I_PRECEDED_SELECTOR, "I_PRECEDED_SELECTOR"); |
| else if (end == 3) |
| currentSimpleSelectorNode = adaptor.create(I_SIBLING_SELECTOR, "I_SIBLING_SELECTOR"); |
| else |
| currentSimpleSelectorNode = adaptor.create(I_SIMPLE_SELECTOR, "I_SIMPLE_SELECTOR"); |
| } |
| } |
| )+ |
| -> ^(I_SELECTOR) |
| // Tree nodes for simple selectors are added in the @after block. |
| ; |
| |
| /** |
| * A compound selector consists of one or many simple selectors. |
| * This rule matches a fraction of a simple selector. |
| * |
| * s|HBox s|Button.confirm #activeLabel, <-- compound selector |
| * s|HBox <-- simple selector |
| * s|Button.confirm <-- simple selector |
| * #activeLabel <-- simple selector |
| * |
| * This rule will match each of the following 4 fractions: |
| * |
| * [s|HBox] [s|Button] [.confirm] [#activeLabel] |
| * |
| * As you can see, the parse tree isn't aware of the start and end of a simple |
| * selector. This is because white spaces have been ignored so that the parser |
| * can't distinguish between the following input: |
| * |
| * s|Button.rounded .labelText <-- 2 simple selectors |
| * s|Button.rounded.labelText <-- 1 simple selector |
| * |
| * As a result, this rule could match only part of a simple selector, because |
| * the conditions were broken into multiple tokens. |
| */ |
| simpleSelectorFraction |
| : condition |
| | element |
| ; |
| |
| /** |
| * This rule matches a condition selector. |
| * For example: ".styleName", "#loginButton", ":up" |
| */ |
| condition |
| : ( DOT^ ID |
| | HASH_WORD |
| | COLON^ NOT ARGUMENTS |
| | COLON^ ID |
| | DOUBLE_COLON^ ID |
| | attributeSelector |
| ) |
| ; |
| |
| /** |
| * This rule matches an element name with or without namespace prefix. It also |
| * matches the special "any" element "*". |
| * For example: "s|Panel", "Label", "*" |
| */ |
| element |
| : ID PIPE^ ID |
| | NUMBER_WITH_PERCENT |
| | ID |
| | STAR |
| ; |
| |
| /** |
| * This rule matches a declarations block. |
| * For example: |
| * { font-size:12; font-family: "sans"; } |
| */ |
| declarationsBlock |
| : BLOCK_OPEN |
| ( |
| SEMI_COLONS? // multiple semi-colons are legal |
| declaration |
| ( |
| SEMI_COLONS |
| declaration |
| )* |
| SEMI_COLONS? |
| )? |
| BLOCK_END |
| -> ^(I_DECL declaration*) |
| ; |
| |
| /** |
| * This rule matches property declaration. The declaration is a key value pair. |
| * For example: |
| * font-size:12px |
| */ |
| declaration |
| : ID COLON value -> ^(COLON ID value) |
| ; |
| |
| /** |
| * This rule matches an array of property values or a single value. |
| * If it matches an array, the output is an I_ARRAY tree of element values. |
| * If it matches an single value, the output is a "singleValue" tree. |
| * |
| * Array example: |
| * 2.0, 2.0, 3.0, 3.0 |
| * "tl", "tr", "bl", "br" |
| * #FFFFFF, #FFFFFF, #FFFFFF, #FFFFFF |
| * Verdana, Times, Sans-Serif |
| * solid 1px #666666 |
| * |
| */ |
| value |
| @init { int count = 1; } |
| : singleValue ( COMMA? singleValue { count++; } )* |
| -> {count > 1}? ^(I_ARRAY singleValue+) |
| -> singleValue |
| ; |
| |
| /** |
| * This rule matches one property value. |
| * |
| * For example: |
| * 12, 12px, 100%. |
| * #FF3322 |
| * rgb(100%, 100%, 100%) |
| * ClassReference("mx.controls.Button") |
| * PropertyReference("size") |
| * Embed(source="assets/logo.png", mimeType="images/png") |
| * url("../fonts/myfont.ttf") |
| * local("My Font") |
| * "Times New Roman" |
| * italic |
| */ |
| singleValue |
| : NUMBER_WITH_PERCENT |
| | NUMBER_WITH_UNIT |
| | HASH_WORD |
| | CLASS_REFERENCE ARGUMENTS |
| -> ^(CLASS_REFERENCE ARGUMENTS) |
| | PROPERTY_REFERENCE ARGUMENTS |
| -> ^(PROPERTY_REFERENCE ARGUMENTS) |
| | EMBED ARGUMENTS |
| -> ^(EMBED ARGUMENTS) |
| | URL ARGUMENTS formatOption* -> ^(URL ARGUMENTS formatOption*) |
| | LOCAL ARGUMENTS -> ^(LOCAL ARGUMENTS) |
| | ALPHA_VALUE |
| | SCALE_VALUE |
| | RECT_VALUE |
| | ROTATE_VALUE |
| | TRANSLATE3D_VALUE |
| | RGB |
| | RGBA |
| | STRING |
| | ID |
| | OPERATOR |
| | IMPORTANT |
| ; |
| |
| formatOption |
| : FORMAT ARGUMENTS -> ^(FORMAT ARGUMENTS) |
| ; |
| |
| attributeSelector |
| : SQUARE_OPEN attributeName attributeOperator* attributeValue* SQUARE_END |
| ; |
| |
| attributeName |
| : ID |
| ; |
| |
| attributeOperator |
| : BEGINS_WITH |
| | ENDS_WITH |
| | CONTAINS |
| | LIST_MATCH |
| | HREFLANG_MATCH |
| | EQUALS |
| ; |
| |
| attributeValue |
| : STRING |
| ; |
| |
| /* Lexer Rules */ |
| |
| BEGINS_WITH : '^=' ; |
| ENDS_WITH : '$=' ; |
| CONTAINS : '*=' ; |
| LIST_MATCH : '~=' ; |
| HREFLANG_MATCH : '|=' ; |
| BLOCK_OPEN : '{' ; |
| BLOCK_END : '}' ; |
| SQUARE_OPEN : '[' ; |
| SQUARE_END : ']' ; |
| COMMA : ',' ; |
| PERCENT : '%' ; |
| PIPE : '|' ; |
| STAR : '*' ; |
| TILDE : '~' ; |
| DOT : '.' ; |
| EQUALS: '='; |
| AT_NAMESPACE : '@namespace' ; |
| AT_MEDIA : '@media' ; |
| AT_KEYFRAMES : '@keyframes' ; |
| AT_WEBKIT_KEYFRAMES : '@-webkit-keyframes' ; |
| DOUBLE_COLON : '::' ; |
| COLON : ':' ; |
| AT_FONT_FACE : '@font-face' ; |
| CLASS_REFERENCE : 'ClassReference' ; |
| PROPERTY_REFERENCE : 'PropertyReference' ; |
| IMPORTANT : '!important' ; |
| EMBED : 'Embed' ; |
| URL : 'url' ; |
| FORMAT : 'format' ; |
| LOCAL : 'local' ; |
| SCALE : 'scale' ; |
| NULL : 'null' ; |
| ONLY : 'only' ; |
| CHILD : '>' ; |
| PRECEDED : '+' ; |
| NOT |
| : 'not' |
| ; |
| |
| /** |
| * Matches an alpha filter - alpha(opacity=70) |
| */ |
| ALPHA_VALUE : 'alpha(' ( options {greedy=false;}: . )* ')' ; |
| |
| /** |
| * Matches an alpha filter - alpha(opacity=70) |
| */ |
| ROTATE_VALUE : 'rotate(' ( options {greedy=false;}: . )* ')' ; |
| |
| /** |
| * Matches an scale value - scale(1.001) |
| */ |
| SCALE_VALUE : 'scale(' ( options {greedy=false;}: . )* ')' ; |
| |
| /** |
| * Matches a translate3d value - translate3d(1.001) |
| */ |
| TRANSLATE3D_VALUE : 'translate3d(' ( options {greedy=false;}: . )* ')' ; |
| |
| /** |
| * Matches an rect value - rect(x,y,w,h) |
| */ |
| RECT_VALUE : 'rect(' ( options {greedy=false;}: . )* ')' ; |
| |
| /** |
| * Matches an rgba definition - rgba(100%,100%,100%,100%) |
| */ |
| RGBA : 'rgba(' ( WS* NUMBER ( PERCENT | ) WS* ) ',' |
| ( WS* NUMBER ( PERCENT | ) WS* ) ',' |
| ( WS* NUMBER ( PERCENT | ) WS* ) ',' |
| ( WS* NUMBER ( PERCENT | ) WS* ) |
| ')' ; |
| |
| /** |
| * Matches a rgb definition - rgb(100%,100%,100%) |
| */ |
| RGB : 'rgb(' ( WS* NUMBER ( PERCENT | ) WS* ) ',' |
| ( WS* NUMBER ( PERCENT | ) WS* ) ',' |
| ( WS* NUMBER ( PERCENT | ) WS* ) |
| ')' ; |
| |
| |
| /** Arguments of a function call property value. */ |
| ARGUMENTS |
| : '(' ( options {greedy=false;}: . )* ')' |
| ; |
| |
| /** |
| * Match multiple semi-colons in Lexer level so that the parser can have a |
| * finite number of look ahead, instead of LL(*); |
| */ |
| SEMI_COLONS : ';'+ ; |
| |
| /** |
| * Matches either ID selector or color value. Unfortunately, these two types of |
| * tokens are ambiguous in ANTLR world. |
| */ |
| HASH_WORD |
| : '#' ( LETTER | DIGIT | '-' | '_' )+ |
| ; |
| |
| ID : ( '-' | '_' )? LETTER ( LETTER | DIGIT | '-' | '_' )* |
| ; |
| |
| /** |
| * Matches: + - * / |
| */ |
| OPERATOR |
| : ('+'|'-'|'*'|'/') |
| ; |
| |
| |
| fragment |
| LETTER |
| : 'a'..'z' |
| | 'A'..'Z' |
| ; |
| |
| /** |
| * Matches: 100, -100, +3.14, .25, -0.25, +.25 |
| */ |
| fragment |
| NUMBER |
| : ('+'|'-')? |
| ( DIGIT+ (DOT DIGIT+)? |
| | DOT DIGIT+ |
| ) |
| ; |
| |
| |
| /** |
| * Matches a number with optional unit string. |
| * For example: |
| * 2.5 |
| * 2.5em |
| * 100% |
| * |
| * The grammar doesn't allow spaces between the number and the unit. |
| * For example: 35 em is a "NUMBER" and an "ID". |
| * Otherwise such input will be ambigiuous: |
| * font-style: bold 35 pt 25em |
| * "pt" could either be another keyword or the unit of "35". |
| */ |
| NUMBER_WITH_PERCENT |
| : NUMBER PERCENT |
| ; |
| |
| NUMBER_WITH_UNIT |
| : NUMBER (ID)? |
| ; |
| |
| fragment |
| DIGIT |
| : '0'..'9' |
| ; |
| |
| COMMENT |
| : '/*' ( options {greedy=false;}: . )* '*/' |
| { $channel = HIDDEN; } |
| ; |
| |
| WS : ( ' ' | '\t' | '\r' | '\n' ) { $channel = HIDDEN; } |
| ; |
| |
| STRING |
| : STRING_QUOTE |
| ( ~( '\\' | STRING_QUOTE ) | ESCAPED_HEX )* |
| STRING_QUOTE |
| ; |
| |
| fragment |
| STRING_QUOTE |
| : '"' |
| | '\'' |
| ; |
| |
| fragment |
| HEX_DIGIT |
| : DIGIT |
| | ('a'..'f'|'A'..'F') |
| ; |
| |
| fragment |
| ESCAPED_HEX |
| : '\\' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT |
| | '\\' HEX_DIGIT HEX_DIGIT HEX_DIGIT |
| | '\\' HEX_DIGIT HEX_DIGIT |
| | '\\' HEX_DIGIT |
| ; |