Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1009)

Unified Diff: pkg/csslib/lib/parser.dart

Issue 814113004: Pull args, intl, logging, shelf, and source_maps out of the SDK. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Also csslib. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/csslib/lib/css.dart ('k') | pkg/csslib/lib/src/analyzer.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/csslib/lib/parser.dart
diff --git a/pkg/csslib/lib/parser.dart b/pkg/csslib/lib/parser.dart
deleted file mode 100644
index 6060166944ec8d7fdbf5e4e16846840eee9fd1e0..0000000000000000000000000000000000000000
--- a/pkg/csslib/lib/parser.dart
+++ /dev/null
@@ -1,2704 +0,0 @@
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-library csslib.parser;
-
-import 'dart:math' as math;
-
-import 'package:source_span/source_span.dart';
-
-import "visitor.dart";
-import 'src/messages.dart';
-import 'src/options.dart';
-
-part 'src/analyzer.dart';
-part 'src/polyfill.dart';
-part 'src/property.dart';
-part 'src/token.dart';
-part 'src/tokenizer_base.dart';
-part 'src/tokenizer.dart';
-part 'src/tokenkind.dart';
-
-
-/** Used for parser lookup ahead (used for nested selectors Less support). */
-class ParserState extends TokenizerState {
- final Token peekToken;
- final Token previousToken;
-
- ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer)
- : super(tokenizer);
-}
-
-// TODO(jmesserly): this should not be global
-void _createMessages({List<Message> errors, List<String> options}) {
- if (errors == null) errors = [];
-
- if (options == null) {
- options = ['--no-colors', 'memory'];
- }
- var opt = PreprocessorOptions.parse(options);
- messages = new Messages(options: opt, printHandler: errors.add);
-}
-
-/** CSS checked mode enabled. */
-bool get isChecked => messages.options.checked;
-
-// TODO(terry): Remove nested name parameter.
-/** Parse and analyze the CSS file. */
-StyleSheet compile(input, {List<Message> errors, List<String> options,
- bool nested: true,
- bool polyfill: false,
- List<StyleSheet> includes: null}) {
-
- if (includes == null) {
- includes = [];
- }
-
- var source = _inputAsString(input);
-
- _createMessages(errors: errors, options: options);
-
- var file = new SourceFile(source);
-
- var tree = new _Parser(file, source).parse();
-
- analyze([tree], errors: errors, options: options);
-
- if (polyfill) {
- var processCss = new PolyFill(messages, true);
- processCss.process(tree, includes: includes);
- }
-
- return tree;
-}
-
-/** Analyze the CSS file. */
-void analyze(List<StyleSheet> styleSheets,
- {List<Message> errors, List<String> options}) {
-
- _createMessages(errors: errors, options: options);
- new Analyzer(styleSheets, messages).run();
-}
-
-/**
- * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String],
- * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
- * [errors] list will contain each error/warning as a [Message].
- */
-StyleSheet parse(input, {List<Message> errors, List<String> options}) {
- var source = _inputAsString(input);
-
- _createMessages(errors: errors, options: options);
-
- var file = new SourceFile(source);
- return new _Parser(file, source).parse();
-}
-
-/**
- * Parse the [input] CSS selector into a tree. The [input] can be a [String],
- * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
- * [errors] list will contain each error/warning as a [Message].
- */
-// TODO(jmesserly): should rename "parseSelector" and return Selector
-StyleSheet selector(input, {List<Message> errors}) {
- var source = _inputAsString(input);
-
- _createMessages(errors: errors);
-
- var file = new SourceFile(source);
- return (new _Parser(file, source)
- ..tokenizer.inSelector = true)
- .parseSelector();
-}
-
-SelectorGroup parseSelectorGroup(input, {List<Message> errors}) {
- var source = _inputAsString(input);
-
- _createMessages(errors: errors);
-
- var file = new SourceFile(source);
- return (new _Parser(file, source)
- // TODO(jmesserly): this fix should be applied to the parser. It's tricky
- // because by the time the flag is set one token has already been fetched.
- ..tokenizer.inSelector = true)
- .processSelectorGroup();
-}
-
-String _inputAsString(input) {
- String source;
-
- if (input is String) {
- source = input;
- } else if (input is List<int>) {
- // TODO(terry): The parse function needs an "encoding" argument and will
- // default to whatever encoding CSS defaults to.
- //
- // Here's some info about CSS encodings:
- // http://www.w3.org/International/questions/qa-css-charset.en.php
- //
- // As JMesserly suggests it will probably need a "preparser" html5lib
- // (encoding_parser.dart) that interprets the bytes as ASCII and scans for
- // @charset. But for now an "encoding" argument would work. Often the
- // HTTP header will indicate the correct encoding.
- //
- // See encoding helpers at: package:html5lib/lib/src/char_encodings.dart
- // These helpers can decode in different formats given an encoding name
- // (mostly unicode, ascii, windows-1252 which is html5 default encoding).
- source = new String.fromCharCodes(input);
- } else {
- // TODO(terry): Support RandomAccessFile using console.
- throw new ArgumentError("'source' must be a String or "
- "List<int> (of bytes). RandomAccessFile not supported from this "
- "simple interface");
- }
-
- return source;
-}
-
-// TODO(terry): Consider removing this class when all usages can be eliminated
-// or replaced with compile API.
-/** Public parsing interface for csslib. */
-class Parser {
- final _Parser _parser;
-
- // TODO(jmesserly): having file and text is redundant.
- Parser(SourceFile file, String text, {int start: 0, String baseUrl}) :
- _parser = new _Parser(file, text, start: start, baseUrl: baseUrl);
-
- StyleSheet parse() => _parser.parse();
-}
-
-/** A simple recursive descent parser for CSS. */
-class _Parser {
- final Tokenizer tokenizer;
-
- /** Base url of CSS file. */
- final String _baseUrl;
-
- /**
- * File containing the source being parsed, used to report errors with
- * source-span locations.
- */
- final SourceFile file;
-
- Token _previousToken;
- Token _peekToken;
-
- _Parser(SourceFile file, String text, {int start: 0, String baseUrl})
- : this.file = file,
- _baseUrl = baseUrl,
- tokenizer = new Tokenizer(file, text, true, start) {
- _peekToken = tokenizer.next();
- }
-
- /** Main entry point for parsing an entire CSS file. */
- StyleSheet parse() {
- List<TreeNode> productions = [];
-
- var start = _peekToken.span;
- while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) {
- // TODO(terry): Need to handle charset.
- var directive = processDirective();
- if (directive != null) {
- productions.add(directive);
- _maybeEat(TokenKind.SEMICOLON);
- } else {
- RuleSet ruleset = processRuleSet();
- if (ruleset != null) {
- productions.add(ruleset);
- } else {
- break;
- }
- }
- }
-
- checkEndOfFile();
-
- return new StyleSheet(productions, _makeSpan(start));
- }
-
- /** Main entry point for parsing a simple selector sequence. */
- StyleSheet parseSelector() {
- List<TreeNode> productions = [];
-
- var start = _peekToken.span;
- while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) {
- var selector = processSelector();
- if (selector != null) {
- productions.add(selector);
- }
- }
-
- checkEndOfFile();
-
- return new StyleSheet.selector(productions, _makeSpan(start));
- }
-
- /** Generate an error if [file] has not been completely consumed. */
- void checkEndOfFile() {
- if (!(_peekKind(TokenKind.END_OF_FILE) ||
- _peekKind(TokenKind.INCOMPLETE_COMMENT))) {
- _error('premature end of file unknown CSS', _peekToken.span);
- }
- }
-
- /** Guard to break out of parser when an unexpected end of file is found. */
- // TODO(jimhug): Failure to call this method can lead to inifinite parser
- // loops. Consider embracing exceptions for more errors to reduce
- // the danger here.
- bool isPrematureEndOfFile() {
- if (_maybeEat(TokenKind.END_OF_FILE)) {
- _error('unexpected end of file', _peekToken.span);
- return true;
- } else {
- return false;
- }
- }
-
- ///////////////////////////////////////////////////////////////////
- // Basic support methods
- ///////////////////////////////////////////////////////////////////
- int _peek() {
- return _peekToken.kind;
- }
-
- Token _next({unicodeRange : false}) {
- _previousToken = _peekToken;
- _peekToken = tokenizer.next(unicodeRange: unicodeRange);
- return _previousToken;
- }
-
- bool _peekKind(int kind) {
- return _peekToken.kind == kind;
- }
-
- /* Is the next token a legal identifier? This includes pseudo-keywords. */
- bool _peekIdentifier() {
- return TokenKind.isIdentifier(_peekToken.kind);
- }
-
- /** Marks the parser/tokenizer look ahead to support Less nested selectors. */
- ParserState get _mark =>
- new ParserState(_peekToken, _previousToken, tokenizer);
-
- /** Restores the parser/tokenizer state to state remembered by _mark. */
- void _restore(ParserState markedData) {
- tokenizer.restore(markedData);
- _peekToken = markedData.peekToken;
- _previousToken = markedData.previousToken;
- }
-
- bool _maybeEat(int kind, {unicodeRange : false}) {
- if (_peekToken.kind == kind) {
- _previousToken = _peekToken;
- _peekToken = tokenizer.next(unicodeRange: unicodeRange);
- return true;
- } else {
- return false;
- }
- }
-
- void _eat(int kind, {unicodeRange : false}) {
- if (!_maybeEat(kind, unicodeRange: unicodeRange)) {
- _errorExpected(TokenKind.kindToString(kind));
- }
- }
-
- void _eatSemicolon() {
- _eat(TokenKind.SEMICOLON);
- }
-
- void _errorExpected(String expected) {
- var tok = _next();
- var message;
- try {
- message = 'expected $expected, but found $tok';
- } catch (e) {
- message = 'parsing error expected $expected';
- }
- _error(message, tok.span);
- }
-
- void _error(String message, SourceSpan location) {
- if (location == null) {
- location = _peekToken.span;
- }
- messages.error(message, location);
- }
-
- void _warning(String message, SourceSpan location) {
- if (location == null) {
- location = _peekToken.span;
- }
- messages.warning(message, location);
- }
-
- SourceSpan _makeSpan(FileSpan start) {
- // TODO(terry): there are places where we are creating spans before we eat
- // the tokens, so using _previousToken is not always valid.
- // TODO(nweiz): use < rather than compareTo when SourceSpan supports it.
- if (_previousToken == null || _previousToken.span.compareTo(start) < 0) {
- return start;
- }
- return start.expand(_previousToken.span);
- }
-
- ///////////////////////////////////////////////////////////////////
- // Top level productions
- ///////////////////////////////////////////////////////////////////
-
- /**
- * The media_query_list production below replaces the media_list production
- * from CSS2 the new grammar is:
- *
- * media_query_list
- * : S* [media_query [ ',' S* media_query ]* ]?
- * media_query
- * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
- * | expression [ AND S* expression ]*
- * media_type
- * : IDENT
- * expression
- * : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
- * media_feature
- * : IDENT
- */
- List<MediaQuery> processMediaQueryList() {
- var mediaQueries = [];
-
- bool firstTime = true;
- var mediaQuery;
- do {
- mediaQuery = processMediaQuery(firstTime == true);
- if (mediaQuery != null) {
- mediaQueries.add(mediaQuery);
- firstTime = false;
- continue;
- }
-
- // Any more more media types separated by comma.
- if (!_maybeEat(TokenKind.COMMA)) break;
-
- // Yep more media types start again.
- firstTime = true;
- } while ((!firstTime && mediaQuery != null) || firstTime);
-
- return mediaQueries;
- }
-
- MediaQuery processMediaQuery([bool startQuery = true]) {
- // Grammar: [ONLY | NOT]? S* media_type S*
- // [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]*
-
- var start = _peekToken.span;
-
- // Is it a unary media operator?
- var op = _peekToken.text;
- var opLen = op.length;
- var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen);
- if (unaryOp != -1) {
- if (isChecked) {
- if (startQuery &&
- unaryOp != TokenKind.MEDIA_OP_NOT ||
- unaryOp != TokenKind.MEDIA_OP_ONLY) {
- _warning("Only the unary operators NOT and ONLY allowed",
- _makeSpan(start));
- }
- if (!startQuery && unaryOp != TokenKind.MEDIA_OP_AND) {
- _warning("Only the binary AND operator allowed", _makeSpan(start));
- }
- }
- _next();
- start = _peekToken.span;
- }
-
- var type;
- if (startQuery && unaryOp != TokenKind.MEDIA_OP_AND) {
- // Get the media type.
- if (_peekIdentifier()) type = identifier();
- }
-
- var exprs = [];
-
- if (unaryOp == -1 || unaryOp == TokenKind.MEDIA_OP_AND) {
- var andOp = false;
- while (true) {
- var expr = processMediaExpression(andOp);
- if (expr == null) break;
-
- exprs.add(expr);
- op = _peekToken.text;
- opLen = op.length;
- andOp = TokenKind.matchMediaOperator(op, 0, opLen) ==
- TokenKind.MEDIA_OP_AND;
- if (!andOp) break;
- _next();
- }
- }
-
- if (unaryOp != -1 || type != null || exprs.length > 0) {
- return new MediaQuery(unaryOp, type, exprs, _makeSpan(start));
- }
- }
-
- MediaExpression processMediaExpression([bool andOperator = false]) {
- var start = _peekToken.span;
-
- // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S*
- if (_maybeEat(TokenKind.LPAREN)) {
- if (_peekIdentifier()) {
- var feature = identifier(); // Media feature.
- while (_maybeEat(TokenKind.COLON)) {
- var startExpr = _peekToken.span;
- var exprs = processExpr();
- if (_maybeEat(TokenKind.RPAREN)) {
- return new MediaExpression(andOperator, feature, exprs,
- _makeSpan(startExpr));
- } else if (isChecked) {
- _warning("Missing parenthesis around media expression",
- _makeSpan(start));
- return null;
- }
- }
- } else if (isChecked) {
- _warning("Missing media feature in media expression", _makeSpan(start));
- return null;
- }
- }
- }
-
- /**
- * Directive grammar:
- *
- * import: '@import' [string | URI] media_list?
- * media: '@media' media_query_list '{' ruleset '}'
- * page: '@page' [':' IDENT]? '{' declarations '}'
- * stylet: '@stylet' IDENT '{' ruleset '}'
- * media_query_list: IDENT [',' IDENT]
- * keyframes: '@-webkit-keyframes ...' (see grammar below).
- * font_face: '@font-face' '{' declarations '}'
- * namespace: '@namespace name url("xmlns")
- * host: '@host '{' ruleset '}'
- * mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}'
- * include: '@include name [(@arg,@arg1)]
- * '@include name [(@arg...)]
- * content '@content'
- */
- processDirective() {
- var start = _peekToken.span;
-
- var tokId = processVariableOrDirective();
- if (tokId is VarDefinitionDirective) return tokId;
- switch (tokId) {
- case TokenKind.DIRECTIVE_IMPORT:
- _next();
-
- // @import "uri_string" or @import url("uri_string") are identical; only
- // a url can follow an @import.
- String importStr;
- if (_peekIdentifier()) {
- var func = processFunction(identifier());
- if (func is UriTerm) {
- importStr = func.text;
- }
- } else {
- importStr = processQuotedString(false);
- }
-
- // Any medias?
- var medias = processMediaQueryList();
-
- if (importStr == null) {
- _error('missing import string', _peekToken.span);
- }
-
- return new ImportDirective(importStr.trim(), medias, _makeSpan(start));
-
- case TokenKind.DIRECTIVE_MEDIA:
- _next();
-
- // Any medias?
- var media = processMediaQueryList();
-
- List<TreeNode> rulesets = [];
- if (_maybeEat(TokenKind.LBRACE)) {
- while (!_maybeEat(TokenKind.END_OF_FILE)) {
- RuleSet ruleset = processRuleSet();
- if (ruleset == null) break;
- rulesets.add(ruleset);
- }
-
- if (!_maybeEat(TokenKind.RBRACE)) {
- _error('expected } after ruleset for @media', _peekToken.span);
- }
- } else {
- _error('expected { after media before ruleset', _peekToken.span);
- }
- return new MediaDirective(media, rulesets, _makeSpan(start));
-
- case TokenKind.DIRECTIVE_HOST:
- _next();
-
- List<TreeNode> rulesets = [];
- if (_maybeEat(TokenKind.LBRACE)) {
- while (!_maybeEat(TokenKind.END_OF_FILE)) {
- RuleSet ruleset = processRuleSet();
- if (ruleset == null) break;
- rulesets.add(ruleset);
- }
-
- if (!_maybeEat(TokenKind.RBRACE)) {
- _error('expected } after ruleset for @host', _peekToken.span);
- }
- } else {
- _error('expected { after host before ruleset', _peekToken.span);
- }
- return new HostDirective(rulesets, _makeSpan(start));
-
- case TokenKind.DIRECTIVE_PAGE:
- /*
- * @page S* IDENT? pseudo_page?
- * S* '{' S*
- * [ declaration | margin ]?
- * [ ';' S* [ declaration | margin ]? ]* '}' S*
- *
- * pseudo_page :
- * ':' [ "left" | "right" | "first" ]
- *
- * margin :
- * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
- *
- * margin_sym : @top-left-corner, @top-left, @bottom-left, etc.
- *
- * See http://www.w3.org/TR/css3-page/#CSS21
- */
- _next();
-
- // Page name
- var name;
- if (_peekIdentifier()) {
- name = identifier();
- }
-
- // Any pseudo page?
- var pseudoPage;
- if (_maybeEat(TokenKind.COLON)) {
- if (_peekIdentifier()) {
- pseudoPage = identifier();
- // TODO(terry): Normalize pseudoPage to lowercase.
- if (isChecked &&
- !(pseudoPage.name == 'left' ||
- pseudoPage.name == 'right' ||
- pseudoPage.name == 'first')) {
- _warning("Pseudo page must be left, top or first",
- pseudoPage.span);
- return null;
- }
- }
- }
-
- String pseudoName = pseudoPage is Identifier ? pseudoPage.name : '';
- String ident = name is Identifier ? name.name : '';
- return new PageDirective(ident, pseudoName,
- processMarginsDeclarations(), _makeSpan(start));
-
- case TokenKind.DIRECTIVE_CHARSET:
- // @charset S* STRING S* ';'
- _next();
-
- var charEncoding = processQuotedString(false);
- if (isChecked && charEncoding == null) {
- // Missing character encoding.
- _warning('missing character encoding string', _makeSpan(start));
- }
-
- return new CharsetDirective(charEncoding, _makeSpan(start));
-
- // TODO(terry): Workaround Dart2js bug continue not implemented in switch
- // see https://code.google.com/p/dart/issues/detail?id=8270
- /*
- case TokenKind.DIRECTIVE_MS_KEYFRAMES:
- // TODO(terry): For now only IE 10 (are base level) supports @keyframes,
- // -moz- has only been optional since Oct 2012 release of Firefox, not
- // all versions of webkit support @keyframes and opera doesn't yet
- // support w/o -o- prefix. Add more warnings for other prefixes when
- // they become optional.
- if (isChecked) {
- _warning('@-ms-keyframes should be @keyframes', _makeSpan(start));
- }
- continue keyframeDirective;
-
- keyframeDirective:
- */
- case TokenKind.DIRECTIVE_KEYFRAMES:
- case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES:
- case TokenKind.DIRECTIVE_MOZ_KEYFRAMES:
- case TokenKind.DIRECTIVE_O_KEYFRAMES:
- // TODO(terry): Remove workaround when bug 8270 is fixed.
- case TokenKind.DIRECTIVE_MS_KEYFRAMES:
- if (tokId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) {
- _warning('@-ms-keyframes should be @keyframes', _makeSpan(start));
- }
- // TODO(terry): End of workaround.
-
- /* Key frames grammar:
- *
- * @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}';
- *
- * browser: [-webkit-, -moz-, -ms-, -o-]
- *
- * keyframes-blocks:
- * [keyframe-selectors '{' declarations '}']* ;
- *
- * keyframe-selectors:
- * ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ;
- */
- _next();
-
- var name;
- if (_peekIdentifier()) {
- name = identifier();
- }
-
- _eat(TokenKind.LBRACE);
-
- var keyframe = new KeyFrameDirective(tokId, name, _makeSpan(start));
-
- do {
- Expressions selectors = new Expressions(_makeSpan(start));
-
- do {
- var term = processTerm();
-
- // TODO(terry): Only allow from, to and PERCENTAGE ...
-
- selectors.add(term);
- } while (_maybeEat(TokenKind.COMMA));
-
- keyframe.add(new KeyFrameBlock(selectors, processDeclarations(),
- _makeSpan(start)));
-
- } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile());
-
- return keyframe;
-
- case TokenKind.DIRECTIVE_FONTFACE:
- _next();
- return new FontFaceDirective(processDeclarations(), _makeSpan(start));
-
- case TokenKind.DIRECTIVE_STYLET:
- /* Stylet grammar:
- *
- * @stylet IDENT '{'
- * ruleset
- * '}'
- */
- _next();
-
- var name;
- if (_peekIdentifier()) {
- name = identifier();
- }
-
- _eat(TokenKind.LBRACE);
-
- List<TreeNode> productions = [];
-
- start = _peekToken.span;
- while (!_maybeEat(TokenKind.END_OF_FILE)) {
- RuleSet ruleset = processRuleSet();
- if (ruleset == null) {
- break;
- }
- productions.add(ruleset);
- }
-
- _eat(TokenKind.RBRACE);
-
- return new StyletDirective(name, productions, _makeSpan(start));
-
- case TokenKind.DIRECTIVE_NAMESPACE:
- /* Namespace grammar:
- *
- * @namespace S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
- * namespace_prefix : IDENT
- *
- */
- _next();
-
- var prefix;
- if (_peekIdentifier()) {
- prefix = identifier();
- }
-
- // The namespace URI can be either a quoted string url("uri_string")
- // are identical.
- String namespaceUri;
- if (_peekIdentifier()) {
- var func = processFunction(identifier());
- if (func is UriTerm) {
- namespaceUri = func.text;
- }
- } else {
- if (prefix != null && prefix.name == 'url') {
- var func = processFunction(prefix);
- if (func is UriTerm) {
- // @namespace url("");
- namespaceUri = func.text;
- prefix = null;
- }
- } else {
- namespaceUri = processQuotedString(false);
- }
- }
-
- return new NamespaceDirective(prefix != null ? prefix.name : '',
- namespaceUri, _makeSpan(start));
-
- case TokenKind.DIRECTIVE_MIXIN:
- return processMixin();
-
- case TokenKind.DIRECTIVE_INCLUDE:
- return processInclude( _makeSpan(start));
-
- case TokenKind.DIRECTIVE_CONTENT:
- // TODO(terry): TBD
- _warning("@content not implemented.", _makeSpan(start));
- return null;
- }
- return null;
- }
-
- /**
- * Parse the mixin beginning token offset [start]. Returns a [MixinDefinition]
- * node.
- *
- * Mixin grammar:
- *
- * @mixin IDENT [(args,...)] '{'
- * [ruleset | property | directive]*
- * '}'
- */
- MixinDefinition processMixin() {
- _next();
-
- var name = identifier();
-
- List<VarDefinitionDirective> params = [];
- // Any parameters?
- if (_maybeEat(TokenKind.LPAREN)) {
- var mustHaveParam = false;
- var keepGoing = true;
- while (keepGoing) {
- var varDef = processVariableOrDirective(mixinParameter: true);
- if (varDef is VarDefinitionDirective || varDef is VarDefinition) {
- params.add(varDef);
- } else if (mustHaveParam) {
- _warning("Expecting parameter", _makeSpan(_peekToken.span));
- keepGoing = false;
- }
- if (_maybeEat(TokenKind.COMMA)) {
- mustHaveParam = true;
- continue;
- }
- keepGoing = !_maybeEat(TokenKind.RPAREN);
- }
- }
-
- _eat(TokenKind.LBRACE);
-
- List<TreeNode> productions = [];
- List<TreeNode> declarations = [];
- var mixinDirective;
-
- var start = _peekToken.span;
- while (!_maybeEat(TokenKind.END_OF_FILE)) {
- var directive = processDirective();
- if (directive != null) {
- productions.add(directive);
- continue;
- }
-
- var declGroup = processDeclarations(checkBrace: false);
- var decls = [];
- if (declGroup.declarations.any((decl) {
- return decl is Declaration &&
- decl is! IncludeMixinAtDeclaration;
- })) {
- var newDecls = [];
- productions.forEach((include) {
- // If declGroup has items that are declarations then we assume
- // this mixin is a declaration mixin not a top-level mixin.
- if (include is IncludeDirective) {
- newDecls.add(new IncludeMixinAtDeclaration(include,
- include.span));
- } else {
- _warning("Error mixing of top-level vs declarations mixins",
- _makeSpan(include.span));
- }
- });
- declGroup.declarations.insertAll(0, newDecls);
- productions = [];
- } else {
- // Declarations are just @includes make it a list of productions
- // not a declaration group (anything else is a ruleset). Make it a
- // list of productions, not a declaration group.
- for (var decl in declGroup.declarations) {
- productions.add(decl is IncludeMixinAtDeclaration ?
- decl.include : decl);
- };
- declGroup.declarations.clear();
- }
-
- if (declGroup.declarations.isNotEmpty) {
- if (productions.isEmpty) {
- mixinDirective = new MixinDeclarationDirective(name.name, params,
- false, declGroup, _makeSpan(start));
- break;
- } else {
- for (var decl in declGroup.declarations) {
- productions.add(decl is IncludeMixinAtDeclaration ?
- decl.include : decl);
- }
- }
- } else {
- mixinDirective = new MixinRulesetDirective(name.name, params,
- false, productions, _makeSpan(start));
- break;
- }
- }
-
- if (productions.isNotEmpty) {
- mixinDirective = new MixinRulesetDirective(name.name, params,
- false, productions, _makeSpan(start));
- }
-
- _eat(TokenKind.RBRACE);
-
- return mixinDirective;
- }
-
- /**
- * Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise
- * return the token id of a directive or -1 if neither.
- */
- processVariableOrDirective({bool mixinParameter: false}) {
- var start = _peekToken.span;
-
- var tokId = _peek();
- // Handle case for @ directive (where there's a whitespace between the @
- // sign and the directive name. Technically, it's not valid grammar but
- // a number of CSS tests test for whitespace between @ and name.
- if (tokId == TokenKind.AT) {
- Token tok = _next();
- tokId = _peek();
- if (_peekIdentifier()) {
- // Is it a directive?
- var directive = _peekToken.text;
- var directiveLen = directive.length;
- tokId = TokenKind.matchDirectives(directive, 0, directiveLen);
- if (tokId == -1) {
- tokId = TokenKind.matchMarginDirectives(directive, 0, directiveLen);
- }
- }
-
- if (tokId == -1) {
- if (messages.options.lessSupport) {
- // Less compatibility:
- // @name: value; => var-name: value; (VarDefinition)
- // property: @name; => property: var(name); (VarUsage)
- var name;
- if (_peekIdentifier()) {
- name = identifier();
- }
-
- Expressions exprs;
- if (mixinParameter && _maybeEat(TokenKind.COLON)) {
- exprs = processExpr();
- } else if (!mixinParameter) {
- _eat(TokenKind.COLON);
- exprs = processExpr();
- }
-
- var span = _makeSpan(start);
- return new VarDefinitionDirective(
- new VarDefinition(name, exprs, span), span);
- } else if (isChecked) {
- _error('unexpected directive @$_peekToken', _peekToken.span);
- }
- }
- } else if (mixinParameter && _peekToken.kind == TokenKind.VAR_DEFINITION) {
- _next();
- var definedName;
- if (_peekIdentifier()) definedName = identifier();
-
- Expressions exprs;
- if (_maybeEat(TokenKind.COLON)) {
- exprs = processExpr();
- }
-
- return new VarDefinition(definedName, exprs, _makeSpan(start));
- }
-
- return tokId;
- }
-
- IncludeDirective processInclude(SourceSpan span, {bool eatSemiColon: true}) {
- /* Stylet grammar:
- *
- * @include IDENT [(args,...)];
- */
- _next();
-
- var name;
- if (_peekIdentifier()) {
- name = identifier();
- }
-
- var params = [];
-
- // Any parameters? Parameters can be multiple terms per argument e.g.,
- // 3px solid yellow, green is two parameters:
- // 1. 3px solid yellow
- // 2. green
- // the first has 3 terms and the second has 1 term.
- if (_maybeEat(TokenKind.LPAREN)) {
- var terms = [];
- var expr;
- var keepGoing = true;
- while (keepGoing && (expr = processTerm()) != null) {
- // VarUsage is returns as a list
- terms.add(expr is List ? expr[0] : expr);
- keepGoing = !_peekKind(TokenKind.RPAREN);
- if (keepGoing) {
- if (_maybeEat(TokenKind.COMMA)) {
- params.add(terms);
- terms = [];
- }
- }
- }
- params.add(terms);
- _maybeEat(TokenKind.RPAREN);
- }
-
- if (eatSemiColon) {
- _eat(TokenKind.SEMICOLON);
- }
-
- return new IncludeDirective(name.name, params, span);
- }
-
- RuleSet processRuleSet([SelectorGroup selectorGroup]) {
- if (selectorGroup == null) {
- selectorGroup = processSelectorGroup();
- }
- if (selectorGroup != null) {
- return new RuleSet(selectorGroup, processDeclarations(),
- selectorGroup.span);
- }
- }
-
- /**
- * Look ahead to see if what should be a declaration is really a selector.
- * If it's a selector than it's a nested selector. This support's Less'
- * nested selector syntax (requires a look ahead). E.g.,
- *
- * div {
- * width : 20px;
- * span {
- * color: red;
- * }
- * }
- *
- * Two tag name selectors div and span equivalent to:
- *
- * div {
- * width: 20px;
- * }
- * div span {
- * color: red;
- * }
- *
- * Return [:null:] if no selector or [SelectorGroup] if a selector was parsed.
- */
- SelectorGroup _nestedSelector() {
- Messages oldMessages = messages;
- _createMessages();
-
- var markedData = _mark;
-
- // Look a head do we have a nested selector instead of a declaration?
- SelectorGroup selGroup = processSelectorGroup();
-
- var nestedSelector = selGroup != null && _peekKind(TokenKind.LBRACE) &&
- messages.messages.isEmpty;
-
- if (!nestedSelector) {
- // Not a selector so restore the world.
- _restore(markedData);
- messages = oldMessages;
- return null;
- } else {
- // Remember any messages from look ahead.
- oldMessages.mergeMessages(messages);
- messages = oldMessages;
- return selGroup;
- }
- }
-
- DeclarationGroup processDeclarations({bool checkBrace: true}) {
- var start = _peekToken.span;
-
- if (checkBrace) _eat(TokenKind.LBRACE);
-
- List decls = [];
- List dartStyles = []; // List of latest styles exposed to Dart.
-
- do {
- var selectorGroup = _nestedSelector();
- while (selectorGroup != null) {
- // Nested selector so process as a ruleset.
- var ruleset = processRuleSet(selectorGroup);
- decls.add(ruleset);
- selectorGroup = _nestedSelector();
- }
-
- Declaration decl = processDeclaration(dartStyles);
- if (decl != null) {
- if (decl.hasDartStyle) {
- var newDartStyle = decl.dartStyle;
-
- // Replace or add latest Dart style.
- bool replaced = false;
- for (var i = 0; i < dartStyles.length; i++) {
- var dartStyle = dartStyles[i];
- if (dartStyle.isSame(newDartStyle)) {
- dartStyles[i] = newDartStyle;
- replaced = true;
- break;
- }
- }
- if (!replaced) {
- dartStyles.add(newDartStyle);
- }
- }
- decls.add(decl);
- }
- } while (_maybeEat(TokenKind.SEMICOLON));
-
- if (checkBrace) _eat(TokenKind.RBRACE);
-
- // Fixup declaration to only have dartStyle that are live for this set of
- // declarations.
- for (var decl in decls) {
- if (decl is Declaration) {
- if (decl.hasDartStyle && dartStyles.indexOf(decl.dartStyle) < 0) {
- // Dart style not live, ignore these styles in this Declarations.
- decl.dartStyle = null;
- }
- }
- }
-
- return new DeclarationGroup(decls, _makeSpan(start));
- }
-
- List<DeclarationGroup> processMarginsDeclarations() {
- List groups = [];
-
- var start = _peekToken.span;
-
- _eat(TokenKind.LBRACE);
-
- List<Declaration> decls = [];
- List dartStyles = []; // List of latest styles exposed to Dart.
-
- do {
- switch (_peek()) {
- case TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER:
- case TokenKind.MARGIN_DIRECTIVE_TOPLEFT:
- case TokenKind.MARGIN_DIRECTIVE_TOPCENTER:
- case TokenKind.MARGIN_DIRECTIVE_TOPRIGHT:
- case TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER:
- case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER:
- case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT:
- case TokenKind.MARGIN_DIRECTIVE_BOTTOMCENTER:
- case TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHT:
- case TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER:
- case TokenKind.MARGIN_DIRECTIVE_LEFTTOP:
- case TokenKind.MARGIN_DIRECTIVE_LEFTMIDDLE:
- case TokenKind.MARGIN_DIRECTIVE_LEFTBOTTOM:
- case TokenKind.MARGIN_DIRECTIVE_RIGHTTOP:
- case TokenKind.MARGIN_DIRECTIVE_RIGHTMIDDLE:
- case TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM:
- // Margin syms processed.
- // margin :
- // margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
- //
- // margin_sym : @top-left-corner, @top-left, @bottom-left, etc.
- var marginSym = _peek();
-
- _next();
-
- var declGroup = processDeclarations();
- if (declGroup != null) {
- groups.add(new MarginGroup(marginSym, declGroup.declarations,
- _makeSpan(start)));
- }
- break;
- default:
- Declaration decl = processDeclaration(dartStyles);
- if (decl != null) {
- if (decl.hasDartStyle) {
- var newDartStyle = decl.dartStyle;
-
- // Replace or add latest Dart style.
- bool replaced = false;
- for (var i = 0; i < dartStyles.length; i++) {
- var dartStyle = dartStyles[i];
- if (dartStyle.isSame(newDartStyle)) {
- dartStyles[i] = newDartStyle;
- replaced = true;
- break;
- }
- }
- if (!replaced) {
- dartStyles.add(newDartStyle);
- }
- }
- decls.add(decl);
- }
- _maybeEat(TokenKind.SEMICOLON);
- break;
- }
- } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile());
-
- // Fixup declaration to only have dartStyle that are live for this set of
- // declarations.
- for (var decl in decls) {
- if (decl.hasDartStyle && dartStyles.indexOf(decl.dartStyle) < 0) {
- // Dart style not live, ignore these styles in this Declarations.
- decl.dartStyle = null;
- }
- }
-
- if (decls.length > 0) {
- groups.add(new DeclarationGroup(decls, _makeSpan(start)));
- }
-
- return groups;
- }
-
- SelectorGroup processSelectorGroup() {
- List<Selector> selectors = [];
- var start = _peekToken.span;
-
- do {
- Selector selector = processSelector();
- if (selector != null) {
- selectors.add(selector);
- }
- } while (_maybeEat(TokenKind.COMMA));
-
- if (selectors.length > 0) {
- return new SelectorGroup(selectors, _makeSpan(start));
- }
- }
-
- /**
- * Return list of selectors
- */
- Selector processSelector() {
- var simpleSequences = <SimpleSelectorSequence>[];
- var start = _peekToken.span;
- while (true) {
- // First item is never descendant make sure it's COMBINATOR_NONE.
- var selectorItem = simpleSelectorSequence(simpleSequences.length == 0);
- if (selectorItem != null) {
- simpleSequences.add(selectorItem);
- } else {
- break;
- }
- }
-
- if (simpleSequences.length > 0) {
- return new Selector(simpleSequences, _makeSpan(start));
- }
- }
-
- simpleSelectorSequence(bool forceCombinatorNone) {
- var start = _peekToken.span;
- var combinatorType = TokenKind.COMBINATOR_NONE;
- var thisOperator = false;
-
- switch (_peek()) {
- case TokenKind.PLUS:
- _eat(TokenKind.PLUS);
- combinatorType = TokenKind.COMBINATOR_PLUS;
- break;
- case TokenKind.GREATER:
- _eat(TokenKind.GREATER);
- combinatorType = TokenKind.COMBINATOR_GREATER;
- break;
- case TokenKind.TILDE:
- _eat(TokenKind.TILDE);
- combinatorType = TokenKind.COMBINATOR_TILDE;
- break;
- case TokenKind.AMPERSAND:
- _eat(TokenKind.AMPERSAND);
- thisOperator = true;
- break;
- }
-
- // Check if WHITESPACE existed between tokens if so we're descendent.
- if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) {
- if (this._previousToken != null &&
- this._previousToken.end != this._peekToken.start) {
- combinatorType = TokenKind.COMBINATOR_DESCENDANT;
- }
- }
-
- var span = _makeSpan(start);
- var simpleSel = thisOperator ?
- new ElementSelector(new ThisOperator(span), span) : simpleSelector();
- if (simpleSel == null &&
- (combinatorType == TokenKind.COMBINATOR_PLUS ||
- combinatorType == TokenKind.COMBINATOR_GREATER ||
- combinatorType == TokenKind.COMBINATOR_TILDE)) {
- // For "+ &", "~ &" or "> &" a selector sequence with no name is needed
- // so that the & will have a combinator too. This is needed to
- // disambiguate selector expressions:
- // .foo&:hover combinator before & is NONE
- // .foo & combinator before & is DESCDENDANT
- // .foo > & combinator before & is GREATER
- simpleSel = new ElementSelector(new Identifier("", span), span);
- }
- if (simpleSel != null) {
- return new SimpleSelectorSequence(simpleSel, span, combinatorType);
- }
- }
-
- /**
- * Simple selector grammar:
- *
- * simple_selector_sequence
- * : [ type_selector | universal ]
- * [ HASH | class | attrib | pseudo | negation ]*
- * | [ HASH | class | attrib | pseudo | negation ]+
- * type_selector
- * : [ namespace_prefix ]? element_name
- * namespace_prefix
- * : [ IDENT | '*' ]? '|'
- * element_name
- * : IDENT
- * universal
- * : [ namespace_prefix ]? '*'
- * class
- * : '.' IDENT
- */
- simpleSelector() {
- // TODO(terry): Natalie makes a good point parsing of namespace and element
- // are essentially the same (asterisk or identifier) other
- // than the error message for element. Should consolidate the
- // code.
- // TODO(terry): Need to handle attribute namespace too.
- var first;
- var start = _peekToken.span;
- switch (_peek()) {
- case TokenKind.ASTERISK:
- // Mark as universal namespace.
- var tok = _next();
- first = new Wildcard(_makeSpan(tok.span));
- break;
- case TokenKind.IDENTIFIER:
- first = identifier();
- break;
- default:
- // Expecting simple selector.
- // TODO(terry): Could be a synthesized token like value, etc.
- if (TokenKind.isKindIdentifier(_peek())) {
- first = identifier();
- } else if (_peekKind(TokenKind.SEMICOLON)) {
- // Can't be a selector if we found a semi-colon.
- return null;
- }
- break;
- }
-
- if (_maybeEat(TokenKind.NAMESPACE)) {
- var element;
- switch (_peek()) {
- case TokenKind.ASTERISK:
- // Mark as universal element
- var tok = _next();
- element = new Wildcard(_makeSpan(tok.span));
- break;
- case TokenKind.IDENTIFIER:
- element = identifier();
- break;
- default:
- _error('expected element name or universal(*), but found $_peekToken',
- _peekToken.span);
- break;
- }
-
- return new NamespaceSelector(first,
- new ElementSelector(element, element.span), _makeSpan(start));
- } else if (first != null) {
- return new ElementSelector(first, _makeSpan(start));
- } else {
- // Check for HASH | class | attrib | pseudo | negation
- return simpleSelectorTail();
- }
- }
-
- bool _anyWhiteSpaceBeforePeekToken(int kind) {
- if (_previousToken != null && _peekToken != null &&
- _previousToken.kind == kind) {
- // If end of previous token isn't same as the start of peek token then
- // there's something between these tokens probably whitespace.
- return _previousToken.end != _peekToken.start;
- }
-
- return false;
- }
-
- /**
- * type_selector | universal | HASH | class | attrib | pseudo
- */
- simpleSelectorTail() {
- // Check for HASH | class | attrib | pseudo | negation
- var start = _peekToken.span;
- switch (_peek()) {
- case TokenKind.HASH:
- _eat(TokenKind.HASH);
-
- var hasWhiteSpace = false;
- if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) {
- _warning("Not a valid ID selector expected #id", _makeSpan(start));
- hasWhiteSpace = true;
- }
- if (_peekIdentifier()) {
- var id = identifier();
- if (hasWhiteSpace) {
- // Generate bad selector id (normalized).
- id.name = " ${id.name}";
- }
- return new IdSelector(id, _makeSpan(start));
- }
- return null;
- case TokenKind.DOT:
- _eat(TokenKind.DOT);
-
- bool hasWhiteSpace = false;
- if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) {
- _warning("Not a valid class selector expected .className",
- _makeSpan(start));
- hasWhiteSpace = true;
- }
- var id = identifier();
- if (hasWhiteSpace) {
- // Generate bad selector class (normalized).
- id.name = " ${id.name}";
- }
- return new ClassSelector(id, _makeSpan(start));
- case TokenKind.COLON:
- // :pseudo-class ::pseudo-element
- return processPseudoSelector(start);
- case TokenKind.LBRACK:
- return processAttribute();
- case TokenKind.DOUBLE:
- _error('name must start with a alpha character, but found a number',
- _peekToken.span);
- _next();
- break;
- }
- }
-
- processPseudoSelector(FileSpan start) {
- // :pseudo-class ::pseudo-element
- // TODO(terry): '::' should be token.
- _eat(TokenKind.COLON);
- var pseudoElement = _maybeEat(TokenKind.COLON);
-
- // TODO(terry): If no identifier specified consider optimizing out the
- // : or :: and making this a normal selector. For now,
- // create an empty pseudoName.
- var pseudoName;
- if (_peekIdentifier()) {
- pseudoName = identifier();
- } else {
- return null;
- }
-
- // Functional pseudo?
-
- if (_peekToken.kind == TokenKind.LPAREN) {
-
- if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') {
- _eat(TokenKind.LPAREN);
-
- // Negation : ':NOT(' S* negation_arg S* ')'
- var negArg = simpleSelector();
-
- _eat(TokenKind.RPAREN);
- return new NegationSelector(negArg, _makeSpan(start));
- } else {
- // Special parsing for expressions in pseudo functions. Minus is used
- // as operator not identifier.
- // TODO(jmesserly): we need to flip this before we eat the "(" as the
- // next token will be fetched when we do that. I think we should try to
- // refactor so we don't need this boolean; it seems fragile.
- tokenizer.inSelectorExpression = true;
- _eat(TokenKind.LPAREN);
-
- // Handle function expression.
- var span = _makeSpan(start);
- var expr = processSelectorExpression();
-
- tokenizer.inSelectorExpression = false;
-
- // Used during selector look-a-head if not a SelectorExpression is
- // bad.
- if (expr is! SelectorExpression) {
- _errorExpected("CSS expression");
- return null;
- }
-
- _eat(TokenKind.RPAREN);
- return (pseudoElement) ?
- new PseudoElementFunctionSelector(pseudoName, expr, span) :
- new PseudoClassFunctionSelector(pseudoName, expr, span);
- }
- }
-
- // TODO(terry): Need to handle specific pseudo class/element name and
- // backward compatible names that are : as well as :: as well as
- // parameters. Current, spec uses :: for pseudo-element and : for
- // pseudo-class. However, CSS2.1 allows for : to specify old
- // pseudo-elements (:first-line, :first-letter, :before and :after) any
- // new pseudo-elements defined would require a ::.
- return pseudoElement ?
- new PseudoElementSelector(pseudoName, _makeSpan(start)) :
- new PseudoClassSelector(pseudoName, _makeSpan(start));
- }
-
- /**
- * In CSS3, the expressions are identifiers, strings, or of the form "an+b".
- *
- * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
- *
- * num [0-9]+|[0-9]*\.[0-9]+
- * PLUS '+'
- * DIMENSION {num}{ident}
- * NUMBER {num}
- */
- processSelectorExpression() {
- var start = _peekToken.span;
-
- var expressions = [];
-
- Token termToken;
- var value;
-
- var keepParsing = true;
- while (keepParsing) {
- switch (_peek()) {
- case TokenKind.PLUS:
- start = _peekToken.span;
- termToken = _next();
- expressions.add(new OperatorPlus(_makeSpan(start)));
- break;
- case TokenKind.MINUS:
- start = _peekToken.span;
- termToken = _next();
- expressions.add(new OperatorMinus(_makeSpan(start)));
- break;
- case TokenKind.INTEGER:
- termToken = _next();
- value = int.parse(termToken.text);
- break;
- case TokenKind.DOUBLE:
- termToken = _next();
- value = double.parse(termToken.text);
- break;
- case TokenKind.SINGLE_QUOTE:
- value = processQuotedString(false);
- value = "'${_escapeString(value, single: true)}'";
- return new LiteralTerm(value, value, _makeSpan(start));
- case TokenKind.DOUBLE_QUOTE:
- value = processQuotedString(false);
- value = '"${_escapeString(value)}"';
- return new LiteralTerm(value, value, _makeSpan(start));
- case TokenKind.IDENTIFIER:
- value = identifier(); // Snarf up the ident we'll remap, maybe.
- break;
- default:
- keepParsing = false;
- }
-
- if (keepParsing && value != null) {
- var unitTerm;
- // Don't process the dimension if MINUS or PLUS is next.
- if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) {
- unitTerm = processDimension(termToken, value, _makeSpan(start));
- }
- if (unitTerm == null) {
- unitTerm = new LiteralTerm(value, value.name, _makeSpan(start));
- }
- expressions.add(unitTerm);
-
- value = null;
- }
- }
-
- return new SelectorExpression(expressions, _makeSpan(start));
- }
-
- // Attribute grammar:
- //
- // attributes :
- // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']'
- //
- // ATTRIB_MATCHES :
- // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ]
- //
- // INCLUDES: '~='
- //
- // DASHMATCH: '|='
- //
- // PREFIXMATCH: '^='
- //
- // SUFFIXMATCH: '$='
- //
- // SUBSTRMATCH: '*='
- //
- //
- AttributeSelector processAttribute() {
- var start = _peekToken.span;
-
- if (_maybeEat(TokenKind.LBRACK)) {
- var attrName = identifier();
-
- int op;
- switch (_peek()) {
- case TokenKind.EQUALS:
- case TokenKind.INCLUDES: // ~=
- case TokenKind.DASH_MATCH: // |=
- case TokenKind.PREFIX_MATCH: // ^=
- case TokenKind.SUFFIX_MATCH: // $=
- case TokenKind.SUBSTRING_MATCH: // *=
- op = _peek();
- _next();
- break;
- default:
- op = TokenKind.NO_MATCH;
- }
-
- var value;
- if (op != TokenKind.NO_MATCH) {
- // Operator hit so we require a value too.
- if (_peekIdentifier()) {
- value = identifier();
- } else {
- value = processQuotedString(false);
- }
-
- if (value == null) {
- _error('expected attribute value string or ident', _peekToken.span);
- }
- }
-
- _eat(TokenKind.RBRACK);
-
- return new AttributeSelector(attrName, op, value, _makeSpan(start));
- }
- }
-
- // Declaration grammar:
- //
- // declaration: property ':' expr prio?
- //
- // property: IDENT [or IE hacks]
- // prio: !important
- // expr: (see processExpr)
- //
- // Here are the ugly IE hacks we need to support:
- // property: expr prio? \9; - IE8 and below property, /9 before semi-colon
- // *IDENT - IE7 or below
- // _IDENT - IE6 property (automatically a valid ident)
- //
- Declaration processDeclaration(List dartStyles) {
- Declaration decl;
-
- var start = _peekToken.span;
-
- // IE7 hack of * before property name if so the property is IE7 or below.
- var ie7 = _peekKind(TokenKind.ASTERISK);
- if (ie7) {
- _next();
- }
-
- // IDENT ':' expr '!important'?
- if (TokenKind.isIdentifier(_peekToken.kind)) {
- var propertyIdent = identifier();
-
- var ieFilterProperty = propertyIdent.name.toLowerCase() == 'filter';
-
- _eat(TokenKind.COLON);
-
- Expressions exprs = processExpr(ieFilterProperty);
-
- var dartComposite = _styleForDart(propertyIdent, exprs, dartStyles);
-
- // Handle !important (prio)
- var importantPriority = _maybeEat(TokenKind.IMPORTANT);
-
- decl = new Declaration(propertyIdent, exprs, dartComposite,
- _makeSpan(start), important: importantPriority, ie7: ie7);
- } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) {
- _next();
- var definedName;
- if (_peekIdentifier()) definedName = identifier();
-
- _eat(TokenKind.COLON);
-
- Expressions exprs = processExpr();
-
- decl = new VarDefinition(definedName, exprs, _makeSpan(start));
- } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) {
- // @include mixinName in the declaration area.
- var span = _makeSpan(start);
- var include = processInclude(span, eatSemiColon: false);
- decl = new IncludeMixinAtDeclaration(include, span);
- } else if (_peekToken.kind == TokenKind.DIRECTIVE_EXTEND) {
- var simpleSequences = <TreeNode>[];
-
- _next();
- var span = _makeSpan(start);
- var selector = simpleSelector();
- if (selector == null) {
- _warning("@extends expecting simple selector name", span);
- } else {
- simpleSequences.add(selector);
- }
- if (_peekKind(TokenKind.COLON)) {
- var pseudoSelector = processPseudoSelector(_peekToken.span);
- if (pseudoSelector is PseudoElementSelector ||
- pseudoSelector is PseudoClassSelector) {
- simpleSequences.add(pseudoSelector);
- } else {
- _warning("not a valid selector", span);
- }
- }
- decl = new ExtendDeclaration(simpleSequences, span);
- }
-
- return decl;
- }
-
- /** List of styles exposed to the Dart UI framework. */
- static const int _fontPartFont= 0;
- static const int _fontPartVariant = 1;
- static const int _fontPartWeight = 2;
- static const int _fontPartSize = 3;
- static const int _fontPartFamily = 4;
- static const int _fontPartStyle = 5;
- static const int _marginPartMargin = 6;
- static const int _marginPartLeft = 7;
- static const int _marginPartTop = 8;
- static const int _marginPartRight = 9;
- static const int _marginPartBottom = 10;
- static const int _lineHeightPart = 11;
- static const int _borderPartBorder = 12;
- static const int _borderPartLeft = 13;
- static const int _borderPartTop = 14;
- static const int _borderPartRight = 15;
- static const int _borderPartBottom = 16;
- static const int _borderPartWidth = 17;
- static const int _borderPartLeftWidth = 18;
- static const int _borderPartTopWidth = 19;
- static const int _borderPartRightWidth = 20;
- static const int _borderPartBottomWidth = 21;
- static const int _heightPart = 22;
- static const int _widthPart = 23;
- static const int _paddingPartPadding = 24;
- static const int _paddingPartLeft = 25;
- static const int _paddingPartTop = 26;
- static const int _paddingPartRight = 27;
- static const int _paddingPartBottom = 28;
-
- static const Map<String, int> _stylesToDart = const {
- 'font': _fontPartFont,
- 'font-family': _fontPartFamily,
- 'font-size': _fontPartSize,
- 'font-style': _fontPartStyle,
- 'font-variant': _fontPartVariant,
- 'font-weight': _fontPartWeight,
- 'line-height': _lineHeightPart,
- 'margin': _marginPartMargin,
- 'margin-left': _marginPartLeft,
- 'margin-right': _marginPartRight,
- 'margin-top': _marginPartTop,
- 'margin-bottom': _marginPartBottom,
- 'border': _borderPartBorder,
- 'border-left': _borderPartLeft,
- 'border-right': _borderPartRight,
- 'border-top': _borderPartTop,
- 'border-bottom': _borderPartBottom,
- 'border-width': _borderPartWidth,
- 'border-left-width': _borderPartLeftWidth,
- 'border-top-width': _borderPartTopWidth,
- 'border-right-width': _borderPartRightWidth,
- 'border-bottom-width': _borderPartBottomWidth,
- 'height': _heightPart,
- 'width': _widthPart,
- 'padding': _paddingPartPadding,
- 'padding-left': _paddingPartLeft,
- 'padding-top': _paddingPartTop,
- 'padding-right': _paddingPartRight,
- 'padding-bottom': _paddingPartBottom
- };
-
- static const Map<String, int> _nameToFontWeight = const {
- 'bold' : FontWeight.bold,
- 'normal' : FontWeight.normal
- };
-
- static int _findStyle(String styleName) => _stylesToDart[styleName];
-
- DartStyleExpression _styleForDart(Identifier property, Expressions exprs,
- List dartStyles) {
- var styleType = _findStyle(property.name.toLowerCase());
- if (styleType != null) {
- return buildDartStyleNode(styleType, exprs, dartStyles);
- }
- }
-
- FontExpression _mergeFontStyles(FontExpression fontExpr, List dartStyles) {
- // Merge all font styles for this class selector.
- for (var dartStyle in dartStyles) {
- if (dartStyle.isFont) {
- fontExpr = new FontExpression.merge(dartStyle, fontExpr);
- }
- }
-
- return fontExpr;
- }
-
- DartStyleExpression buildDartStyleNode(int styleType, Expressions exprs,
- List dartStyles) {
-
- switch (styleType) {
- /*
- * Properties in order:
- *
- * font-style font-variant font-weight font-size/line-height font-family
- *
- * The font-size and font-family values are required. If other values are
- * missing; a default, if it exist, will be used.
- */
- case _fontPartFont:
- var processor = new ExpressionsProcessor(exprs);
- return _mergeFontStyles(processor.processFont(), dartStyles);
- case _fontPartFamily:
- var processor = new ExpressionsProcessor(exprs);
-
- try {
- return _mergeFontStyles(processor.processFontFamily(), dartStyles);
- } catch (fontException) {
- _error(fontException, _peekToken.span);
- }
- break;
- case _fontPartSize:
- var processor = new ExpressionsProcessor(exprs);
- return _mergeFontStyles(processor.processFontSize(), dartStyles);
- case _fontPartStyle:
- /* Possible style values:
- * normal [default]
- * italic
- * oblique
- * inherit
- */
- // TODO(terry): TBD
- break;
- case _fontPartVariant:
- /* Possible variant values:
- * normal [default]
- * small-caps
- * inherit
- */
- // TODO(terry): TBD
- break;
- case _fontPartWeight:
- /* Possible weight values:
- * normal [default]
- * bold
- * bolder
- * lighter
- * 100 - 900
- * inherit
- */
- // TODO(terry): Only 'normal', 'bold', or values of 100-900 supoorted
- // need to handle bolder, lighter, and inherit. See
- // https://github.com/dart-lang/csslib/issues/1
- var expr = exprs.expressions[0];
- if (expr is NumberTerm) {
- var fontExpr = new FontExpression(expr.span,
- weight: expr.value);
- return _mergeFontStyles(fontExpr, dartStyles);
- } else if (expr is LiteralTerm) {
- int weight = _nameToFontWeight[expr.value.toString()];
- if (weight != null) {
- var fontExpr = new FontExpression(expr.span, weight: weight);
- return _mergeFontStyles(fontExpr, dartStyles);
- }
- }
- break;
- case _lineHeightPart:
- num lineHeight;
- if (exprs.expressions.length == 1) {
- var expr = exprs.expressions[0];
- if (expr is UnitTerm) {
- UnitTerm unitTerm = expr;
- // TODO(terry): Need to handle other units and LiteralTerm normal
- // See https://github.com/dart-lang/csslib/issues/2.
- if (unitTerm.unit == TokenKind.UNIT_LENGTH_PX ||
- unitTerm.unit == TokenKind.UNIT_LENGTH_PT) {
- var fontExpr = new FontExpression(expr.span,
- lineHeight: new LineHeight(expr.value, inPixels: true));
- return _mergeFontStyles(fontExpr, dartStyles);
- } else if (isChecked) {
- _warning("Unexpected unit for line-height", expr.span);
- }
- } else if (expr is NumberTerm) {
- var fontExpr = new FontExpression(expr.span,
- lineHeight: new LineHeight(expr.value, inPixels: false));
- return _mergeFontStyles(fontExpr, dartStyles);
- } else if (isChecked) {
- _warning("Unexpected value for line-height", expr.span);
- }
- }
- break;
- case _marginPartMargin:
- return new MarginExpression.boxEdge(exprs.span, processFourNums(exprs));
- case _borderPartBorder:
- for (var expr in exprs.expressions) {
- var v = marginValue(expr);
- if (v != null) {
- final box = new BoxEdge.uniform(v);
- return new BorderExpression.boxEdge(exprs.span, box);
- }
- }
- break;
- case _borderPartWidth:
- var v = marginValue(exprs.expressions[0]);
- if (v != null) {
- final box = new BoxEdge.uniform(v);
- return new BorderExpression.boxEdge(exprs.span, box);
- }
- break;
- case _paddingPartPadding:
- return new PaddingExpression.boxEdge(exprs.span,
- processFourNums(exprs));
- case _marginPartLeft:
- case _marginPartTop:
- case _marginPartRight:
- case _marginPartBottom:
- case _borderPartLeft:
- case _borderPartTop:
- case _borderPartRight:
- case _borderPartBottom:
- case _borderPartLeftWidth:
- case _borderPartTopWidth:
- case _borderPartRightWidth:
- case _borderPartBottomWidth:
- case _heightPart:
- case _widthPart:
- case _paddingPartLeft:
- case _paddingPartTop:
- case _paddingPartRight:
- case _paddingPartBottom:
- if (exprs.expressions.length > 0) {
- return processOneNumber(exprs, styleType);
- }
- break;
- default:
- // Don't handle it.
- return null;
- }
- }
-
- // TODO(terry): Look at handling width of thin, thick, etc. any none numbers
- // to convert to a number.
- DartStyleExpression processOneNumber(Expressions exprs, int part) {
- var value = marginValue(exprs.expressions[0]);
- if (value != null) {
- switch (part) {
- case _marginPartLeft:
- return new MarginExpression(exprs.span, left: value);
- case _marginPartTop:
- return new MarginExpression(exprs.span, top: value);
- case _marginPartRight:
- return new MarginExpression(exprs.span, right: value);
- case _marginPartBottom:
- return new MarginExpression(exprs.span, bottom: value);
- case _borderPartLeft:
- case _borderPartLeftWidth:
- return new BorderExpression(exprs.span, left: value);
- case _borderPartTop:
- case _borderPartTopWidth:
- return new BorderExpression(exprs.span, top: value);
- case _borderPartRight:
- case _borderPartRightWidth:
- return new BorderExpression(exprs.span, right: value);
- case _borderPartBottom:
- case _borderPartBottomWidth:
- return new BorderExpression(exprs.span, bottom: value);
- case _heightPart:
- return new HeightExpression(exprs.span, value);
- case _widthPart:
- return new WidthExpression(exprs.span, value);
- case _paddingPartLeft:
- return new PaddingExpression(exprs.span, left: value);
- case _paddingPartTop:
- return new PaddingExpression(exprs.span, top: value);
- case _paddingPartRight:
- return new PaddingExpression(exprs.span, right: value);
- case _paddingPartBottom:
- return new PaddingExpression(exprs.span, bottom: value);
- }
- }
- }
-
- /**
- * Margins are of the format:
- *
- * top,right,bottom,left (4 parameters)
- * top,right/left, bottom (3 parameters)
- * top/bottom,right/left (2 parameters)
- * top/right/bottom/left (1 parameter)
- *
- * The values of the margins can be a unit or unitless or auto.
- */
- BoxEdge processFourNums(Expressions exprs) {
- num top;
- num right;
- num bottom;
- num left;
-
- int totalExprs = exprs.expressions.length;
- switch (totalExprs) {
- case 1:
- top = marginValue(exprs.expressions[0]);
- right = top;
- bottom = top;
- left = top;
- break;
- case 2:
- top = marginValue(exprs.expressions[0]);
- bottom = top;
- right = marginValue(exprs.expressions[1]);
- left = right;
- break;
- case 3:
- top = marginValue(exprs.expressions[0]);
- right = marginValue(exprs.expressions[1]);
- left = right;
- bottom = marginValue(exprs.expressions[2]);
- break;
- case 4:
- top = marginValue(exprs.expressions[0]);
- right = marginValue(exprs.expressions[1]);
- bottom = marginValue(exprs.expressions[2]);
- left = marginValue(exprs.expressions[3]);
- break;
- default:
- return null;
- }
-
- return new BoxEdge.clockwiseFromTop(top, right, bottom, left);
- }
-
- // TODO(terry): Need to handle auto.
- marginValue(var exprTerm) {
- if (exprTerm is UnitTerm || exprTerm is NumberTerm) {
- return exprTerm.value;
- }
- }
-
- // Expression grammar:
- //
- // expression: term [ operator? term]*
- //
- // operator: '/' | ','
- // term: (see processTerm)
- //
- Expressions processExpr([bool ieFilter = false]) {
- var start = _peekToken.span;
- var expressions = new Expressions(_makeSpan(start));
-
- var keepGoing = true;
- var expr;
- while (keepGoing && (expr = processTerm(ieFilter)) != null) {
- var op;
-
- var opStart = _peekToken.span;
-
- switch (_peek()) {
- case TokenKind.SLASH:
- op = new OperatorSlash(_makeSpan(opStart));
- break;
- case TokenKind.COMMA:
- op = new OperatorComma(_makeSpan(opStart));
- break;
- case TokenKind.BACKSLASH:
- // Backslash outside of string; detected IE8 or older signaled by \9 at
- // end of an expression.
- var ie8Start = _peekToken.span;
-
- _next();
- if (_peekKind(TokenKind.INTEGER)) {
- var numToken = _next();
- var value = int.parse(numToken.text);
- if (value == 9) {
- op = new IE8Term(_makeSpan(ie8Start));
- } else if (isChecked) {
- _warning("\$value is not valid in an expression", _makeSpan(start));
- }
- }
- break;
- }
-
- if (expr != null) {
- if (expr is List) {
- expr.forEach((exprItem) {
- expressions.add(exprItem);
- });
- } else {
- expressions.add(expr);
- }
- } else {
- keepGoing = false;
- }
-
- if (op != null) {
- expressions.add(op);
- if (op is IE8Term) {
- keepGoing = false;
- } else {
- _next();
- }
- }
- }
-
- return expressions;
- }
-
- static const int MAX_UNICODE = 0x10FFFF;
-
- // Term grammar:
- //
- // term:
- // unary_operator?
- // [ term_value ]
- // | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
- //
- // term_value:
- // NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
- // TIME S* | FREQ S* | function
- //
- // NUMBER: {num}
- // PERCENTAGE: {num}%
- // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc']
- // EMS: {num}'em'
- // EXS: {num}'ex'
- // ANGLE: {num}['deg' | 'rad' | 'grad']
- // TIME: {num}['ms' | 's']
- // FREQ: {num}['hz' | 'khz']
- // function: IDENT '(' expr ')'
- //
- processTerm([bool ieFilter = false]) {
- var start = _peekToken.span;
- Token t; // token for term's value
- var value; // value of term (numeric values)
-
- var unary = "";
- switch (_peek()) {
- case TokenKind.HASH:
- this._eat(TokenKind.HASH);
- if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) {
- String hexText;
- if (_peekKind(TokenKind.INTEGER)) {
- String hexText1 = _peekToken.text;
- _next();
- if (_peekIdentifier()) {
- hexText = '$hexText1${identifier().name}';
- } else {
- hexText = hexText1;
- }
- } else if (_peekIdentifier()) {
- hexText = identifier().name;
- }
- if (hexText != null) {
- return _parseHex(hexText, _makeSpan(start));
- }
- }
-
- if (isChecked) {
- _warning("Expected hex number", _makeSpan(start));
- }
- // Construct the bad hex value with a #<space>number.
- return _parseHex(" ${processTerm().text}", _makeSpan(start));
- case TokenKind.INTEGER:
- t = _next();
- value = int.parse("${unary}${t.text}");
- break;
- case TokenKind.DOUBLE:
- t = _next();
- value = double.parse("${unary}${t.text}");
- break;
- case TokenKind.SINGLE_QUOTE:
- value = processQuotedString(false);
- value = "'${_escapeString(value, single: true)}'";
- return new LiteralTerm(value, value, _makeSpan(start));
- case TokenKind.DOUBLE_QUOTE:
- value = processQuotedString(false);
- value = '"${_escapeString(value)}"';
- return new LiteralTerm(value, value, _makeSpan(start));
- case TokenKind.LPAREN:
- _next();
-
- GroupTerm group = new GroupTerm(_makeSpan(start));
-
- var term;
- do {
- term = processTerm();
- if (term != null && term is LiteralTerm) {
- group.add(term);
- }
- } while (term != null && !_maybeEat(TokenKind.RPAREN) &&
- !isPrematureEndOfFile());
-
- return group;
- case TokenKind.LBRACK:
- _next();
-
- var term = processTerm();
- if (!(term is NumberTerm)) {
- _error('Expecting a positive number', _makeSpan(start));
- }
-
- _eat(TokenKind.RBRACK);
-
- return new ItemTerm(term.value, term.text, _makeSpan(start));
- case TokenKind.IDENTIFIER:
- var nameValue = identifier(); // Snarf up the ident we'll remap, maybe.
-
- if (!ieFilter && _maybeEat(TokenKind.LPAREN)) {
- // FUNCTION
- return processFunction(nameValue);
- } if (ieFilter) {
- if (_maybeEat(TokenKind.COLON) &&
- nameValue.name.toLowerCase() == 'progid') {
- // IE filter:progid:
- return processIEFilter(start);
- } else {
- // Handle filter:<name> where name is any filter e.g., alpha, chroma,
- // Wave, blur, etc.
- return processIEFilter(start);
- }
- }
-
- // TODO(terry): Need to have a list of known identifiers today only
- // 'from' is special.
- if (nameValue.name == 'from') {
- return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
- }
-
- // What kind of identifier is it, named color?
- var colorEntry = TokenKind.matchColorName(nameValue.name);
- if (colorEntry == null) {
- if (isChecked) {
- var propName = nameValue.name;
- var errMsg = TokenKind.isPredefinedName(propName) ?
- "Improper use of property value ${propName}" :
- "Unknown property value ${propName}";
- _warning(errMsg, _makeSpan(start));
- }
- return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
- }
-
- // Yes, process the color as an RGB value.
- var rgbColor =
- TokenKind.decimalToHex(TokenKind.colorValue(colorEntry), 6);
- return _parseHex(rgbColor, _makeSpan(start));
- case TokenKind.UNICODE_RANGE:
- var first;
- var second;
- var firstNumber;
- var secondNumber;
- _eat(TokenKind.UNICODE_RANGE, unicodeRange: true);
- if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) {
- first = _previousToken.text;
- firstNumber = int.parse('0x$first');
- if (firstNumber > MAX_UNICODE) {
- _error("unicode range must be less than 10FFFF", _makeSpan(start));
- }
- if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) {
- if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) {
- second = _previousToken.text;
- secondNumber = int.parse('0x$second');
- if (secondNumber > MAX_UNICODE) {
- _error("unicode range must be less than 10FFFF",
- _makeSpan(start));
- }
- if (firstNumber > secondNumber) {
- _error("unicode first range can not be greater than last",
- _makeSpan(start));
- }
- }
- }
- } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) {
- first = _previousToken.text;
- }
-
- return new UnicodeRangeTerm(first, second, _makeSpan(start));
- case TokenKind.AT:
- if (messages.options.lessSupport) {
- _next();
-
- var expr = processExpr();
- if (isChecked && expr.expressions.length > 1) {
- _error("only @name for Less syntax", _peekToken.span);
- }
-
- var param = expr.expressions[0];
- var varUsage = new VarUsage(param.text, [], _makeSpan(start));
- expr.expressions[0] = varUsage;
- return expr.expressions;
- }
- break;
- }
-
- return processDimension(t, value, _makeSpan(start));
- }
-
- /** Process all dimension units. */
- LiteralTerm processDimension(Token t, var value, SourceSpan span) {
- LiteralTerm term;
- var unitType = this._peek();
-
- switch (unitType) {
- case TokenKind.UNIT_EM:
- term = new EmTerm(value, t.text, span);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_EX:
- term = new ExTerm(value, t.text, span);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_LENGTH_PX:
- case TokenKind.UNIT_LENGTH_CM:
- case TokenKind.UNIT_LENGTH_MM:
- case TokenKind.UNIT_LENGTH_IN:
- case TokenKind.UNIT_LENGTH_PT:
- case TokenKind.UNIT_LENGTH_PC:
- term = new LengthTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_ANGLE_DEG:
- case TokenKind.UNIT_ANGLE_RAD:
- case TokenKind.UNIT_ANGLE_GRAD:
- case TokenKind.UNIT_ANGLE_TURN:
- term = new AngleTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_TIME_MS:
- case TokenKind.UNIT_TIME_S:
- term = new TimeTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_FREQ_HZ:
- case TokenKind.UNIT_FREQ_KHZ:
- term = new FreqTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.PERCENT:
- term = new PercentageTerm(value, t.text, span);
- _next(); // Skip the %
- break;
- case TokenKind.UNIT_FRACTION:
- term = new FractionTerm(value, t.text, span);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_RESOLUTION_DPI:
- case TokenKind.UNIT_RESOLUTION_DPCM:
- case TokenKind.UNIT_RESOLUTION_DPPX:
- term = new ResolutionTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_CH:
- term = new ChTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_REM:
- term = new RemTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- case TokenKind.UNIT_VIEWPORT_VW:
- case TokenKind.UNIT_VIEWPORT_VH:
- case TokenKind.UNIT_VIEWPORT_VMIN:
- case TokenKind.UNIT_VIEWPORT_VMAX:
- term = new ViewportTerm(value, t.text, span, unitType);
- _next(); // Skip the unit
- break;
- default:
- if (value != null && t != null) {
- term = (value is Identifier)
- ? new LiteralTerm(value, value.name, span)
- : new NumberTerm(value, t.text, span);
- }
- break;
- }
-
- return term;
- }
-
- String processQuotedString([bool urlString = false]) {
- var start = _peekToken.span;
-
- // URI term sucks up everything inside of quotes(' or ") or between parens
- var stopToken = urlString ? TokenKind.RPAREN : -1;
-
- // Note: disable skipping whitespace tokens inside a string.
- // TODO(jmesserly): the layering here feels wrong.
- var skipWhitespace = tokenizer._skipWhitespace;
- tokenizer._skipWhitespace = false;
-
- switch (_peek()) {
- case TokenKind.SINGLE_QUOTE:
- stopToken = TokenKind.SINGLE_QUOTE;
- _next(); // Skip the SINGLE_QUOTE.
- start = _peekToken.span;
- break;
- case TokenKind.DOUBLE_QUOTE:
- stopToken = TokenKind.DOUBLE_QUOTE;
- _next(); // Skip the DOUBLE_QUOTE.
- start = _peekToken.span;
- break;
- default:
- if (urlString) {
- if (_peek() == TokenKind.LPAREN) {
- _next(); // Skip the LPAREN.
- start = _peekToken.span;
- }
- stopToken = TokenKind.RPAREN;
- } else {
- _error('unexpected string', _makeSpan(start));
- }
- break;
- }
-
- // Gobble up everything until we hit our stop token.
- var stringValue = new StringBuffer();
- while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
- stringValue.write(_next().text);
- }
-
- tokenizer._skipWhitespace = skipWhitespace;
-
- // All characters between quotes is the string.
- if (stopToken != TokenKind.RPAREN) {
- _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
- }
-
- return stringValue.toString();
- }
-
- // TODO(terry): Should probably understand IE's non-standard filter syntax to
- // fully support calc, var(), etc.
- /**
- * IE's filter property breaks CSS value parsing. IE's format can be:
- *
- * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83');
- *
- * We'll just parse everything after the 'progid:' look for the left paren
- * then parse to the right paren ignoring everything in between.
- */
- processIEFilter(FileSpan startAfterProgidColon) {
- var parens = 0;
-
- while (_peek() != TokenKind.END_OF_FILE) {
- switch (_peek()) {
- case TokenKind.LPAREN:
- _eat(TokenKind.LPAREN);
- parens++;
- break;
- case TokenKind.RPAREN:
- _eat(TokenKind.RPAREN);
- if (--parens == 0) {
- var tok = tokenizer.makeIEFilter(startAfterProgidColon.start.offset,
- _peekToken.start);
- return new LiteralTerm(tok.text, tok.text, tok.span);
- }
- break;
- default:
- _eat(_peek());
- }
- }
- }
-
- // Function grammar:
- //
- // function: IDENT '(' expr ')'
- //
- processFunction(Identifier func) {
- var start = _peekToken.span;
-
- var name = func.name;
-
- switch (name) {
- case 'url':
- // URI term sucks up everything inside of quotes(' or ") or between parens
- var urlParam = processQuotedString(true);
-
- // TODO(terry): Better error messge and checking for mismatched quotes.
- if (_peek() == TokenKind.END_OF_FILE) {
- _error("problem parsing URI", _peekToken.span);
- }
-
- if (_peek() == TokenKind.RPAREN) {
- _next();
- }
-
- return new UriTerm(urlParam, _makeSpan(start));
- case 'calc':
- // TODO(terry): Implement expression handling...
- break;
- case 'var':
- // TODO(terry): Consider handling var in IE specific filter/progid. This
- // will require parsing entire IE specific syntax e.g.,
- // param = value or progid:com_id, etc. for example:
- //
- // var-blur: Blur(Add = 0, Direction = 225, Strength = 10);
- // var-gradient: progid:DXImageTransform.Microsoft.gradient"
- // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
- var expr = processExpr();
- if (!_maybeEat(TokenKind.RPAREN)) {
- _error("problem parsing var expected ), ", _peekToken.span);
- }
- if (isChecked &&
- expr.expressions.where((e) => e is OperatorComma).length > 1) {
- _error("too many parameters to var()", _peekToken.span);
- }
-
- var paramName = expr.expressions[0].text;
-
- // [0] - var name, [1] - OperatorComma, [2] - default value.
- var defaultValues = expr.expressions.length >= 3
- ? expr.expressions.sublist(2) : [];
- return new VarUsage(paramName, defaultValues, _makeSpan(start));
- default:
- var expr = processExpr();
- if (!_maybeEat(TokenKind.RPAREN)) {
- _error("problem parsing function expected ), ", _peekToken.span);
- }
-
- return new FunctionTerm(name, name, expr, _makeSpan(start));
- }
-
- return null;
- }
-
- Identifier identifier() {
- var tok = _next();
-
- if (!TokenKind.isIdentifier(tok.kind) &&
- !TokenKind.isKindIdentifier(tok.kind)) {
- if (isChecked) {
- _warning('expected identifier, but found $tok', tok.span);
- }
- return new Identifier("", _makeSpan(tok.span));
- }
-
- return new Identifier(tok.text, _makeSpan(tok.span));
- }
-
- // TODO(terry): Move this to base <= 36 and into shared code.
- static int _hexDigit(int c) {
- if (c >= 48/*0*/ && c <= 57/*9*/) {
- return c - 48;
- } else if (c >= 97/*a*/ && c <= 102/*f*/) {
- return c - 87;
- } else if (c >= 65/*A*/ && c <= 70/*F*/) {
- return c - 55;
- } else {
- return -1;
- }
- }
-
- HexColorTerm _parseHex(String hexText, SourceSpan span) {
- var hexValue = 0;
-
- for (var i = 0; i < hexText.length; i++) {
- var digit = _hexDigit(hexText.codeUnitAt(i));
- if (digit < 0) {
- _warning('Bad hex number', span);
- return new HexColorTerm(new BAD_HEX_VALUE(), hexText, span);
- }
- hexValue = (hexValue << 4) + digit;
- }
-
- // Make 3 character hex value #RRGGBB => #RGB iff:
- // high/low nibble of RR is the same, high/low nibble of GG is the same and
- // high/low nibble of BB is the same.
- if (hexText.length == 6 &&
- hexText[0] == hexText[1] &&
- hexText[2] == hexText[3] &&
- hexText[4] == hexText[5]) {
- hexText = '${hexText[0]}${hexText[2]}${hexText[4]}';
- } else if (hexText.length == 4 &&
- hexText[0] == hexText[1] &&
- hexText[2] == hexText[3]) {
- hexText = '${hexText[0]}${hexText[2]}';
- } else if (hexText.length == 2 && hexText[0] == hexText[1]) {
- hexText = '${hexText[0]}';
- }
- return new HexColorTerm(hexValue, hexText, span);
- }
-}
-
-class ExpressionsProcessor {
- final Expressions _exprs;
- int _index = 0;
-
- ExpressionsProcessor(this._exprs);
-
- // TODO(terry): Only handles ##px unit.
- FontExpression processFontSize() {
- /* font-size[/line-height]
- *
- * Possible size values:
- * xx-small
- * small
- * medium [default]
- * large
- * x-large
- * xx-large
- * smaller
- * larger
- * ##length in px, pt, etc.
- * ##%, percent of parent elem's font-size
- * inherit
- */
- LengthTerm size;
- LineHeight lineHt;
- var nextIsLineHeight = false;
- for (; _index < _exprs.expressions.length; _index++) {
- var expr = _exprs.expressions[_index];
- if (size == null && expr is LengthTerm) {
- // font-size part.
- size = expr;
- } else if (size != null) {
- if (expr is OperatorSlash) {
- // LineHeight could follow?
- nextIsLineHeight = true;
- } else if (nextIsLineHeight && expr is LengthTerm) {
- assert(expr.unit == TokenKind.UNIT_LENGTH_PX);
- lineHt = new LineHeight(expr.value, inPixels: true);
- nextIsLineHeight = false;
- _index++;
- break;
- } else {
- break;
- }
- } else {
- break;
- }
- }
-
- return new FontExpression(_exprs.span, size: size, lineHeight: lineHt);
- }
-
- FontExpression processFontFamily() {
- var family = <String>[];
-
- /* Possible family values:
- * font-family: arial, Times new roman ,Lucida Sans Unicode,Courier;
- * font-family: "Times New Roman", arial, Lucida Sans Unicode, Courier;
- */
- var moreFamilies = false;
-
- for (; _index < _exprs.expressions.length; _index++) {
- Expression expr = _exprs.expressions[_index];
- if (expr is LiteralTerm) {
- if (family.length == 0 || moreFamilies) {
- // It's font-family now.
- family.add(expr.toString());
- moreFamilies = false;
- } else if (isChecked) {
- messages.warning('Only font-family can be a list', _exprs.span);
- }
- } else if (expr is OperatorComma && family.length > 0) {
- moreFamilies = true;
- } else {
- break;
- }
- }
-
- return new FontExpression(_exprs.span, family: family);
- }
-
- FontExpression processFont() {
- List<String> family;
-
- // Process all parts of the font expression.
- FontExpression fontSize;
- FontExpression fontFamily;
- for (; _index < _exprs.expressions.length; _index++) {
- var expr = _exprs.expressions[_index];
- // Order is font-size font-family
- if (fontSize == null) {
- fontSize = processFontSize();
- }
- if (fontFamily == null) {
- fontFamily = processFontFamily();
- }
- //TODO(terry): Handle font-weight, font-style, and font-variant. See
- // https://github.com/dart-lang/csslib/issues/3
- // https://github.com/dart-lang/csslib/issues/4
- // https://github.com/dart-lang/csslib/issues/5
- }
-
- return new FontExpression(_exprs.span,
- size: fontSize.font.size,
- lineHeight: fontSize.font.lineHeight,
- family: fontFamily.font.family);
- }
-}
-
-/**
- * Escapes [text] for use in a CSS string.
- * [single] specifies single quote `'` vs double quote `"`.
- */
-String _escapeString(String text, {bool single: false}) {
- StringBuffer result = null;
-
- for (int i = 0; i < text.length; i++) {
- var code = text.codeUnitAt(i);
- String replace = null;
- switch (code) {
- case 34/*'"'*/: if (!single) replace = r'\"'; break;
- case 39/*"'"*/: if (single) replace = r"\'"; break;
- }
-
- if (replace != null && result == null) {
- result = new StringBuffer(text.substring(0, i));
- }
-
- if (result != null) result.write(replace != null ? replace : text[i]);
- }
-
- return result == null ? text : result.toString();
-}
« no previous file with comments | « pkg/csslib/lib/css.dart ('k') | pkg/csslib/lib/src/analyzer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698