move csslib into dart svn

deleted files that duplicate those in the Dart repository (LICENSE, pubspec.yaml, codereview.settings, .gitignore)

Otherwise, just changed pkg.status and pubspec

R=terry@google.com

Review URL: https://codereview.chromium.org//23168002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/csslib@26155 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b84dae4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+csslib in Pure Dart
+===================
+
+This is a pure [Dart][dart] [CSS parser][cssparse]. Since it's 100%
+Dart you can use it safely from a script or server side app.
+
+[![Build Status](https://drone.io/github.com/dart-lang/csslib/status.png)](https://drone.io/github.com/dart-lang/csslib/latest)
+
+Installation
+------------
+
+Add this to your `pubspec.yaml` (or create it):
+```yaml
+dependencies:
+  csslib: any
+```
+Then run the [Pub Package Manager][pub] (comes with the Dart SDK):
+
+    pub install
+
+Usage
+-----
+
+Parsing CSS is easy!
+```dart
+import 'package:csslib/parser.dart' show parse;
+import 'package:csslib/css.dart';
+
+main() {
+  var stylesheet = parse(
+      '.foo { color: red; left: 20px; top: 20px; width: 100px; height:200px }');
+  print(stylesheet.toString());
+}
+```
+
+You can pass a String or list of bytes to `parse`.
+
+
+Updating
+--------
+
+You can upgrade the library with:
+
+    pub update
+
+Disclaimer: the APIs are not finished. Updating may break your code. If that
+happens, you can check the
+[commit log](https://github.com/dart-lang/csslib/commits/master), to figure
+out what the change was.
+
+If you want to avoid breakage, you can also put the version constraint in your
+`pubspec.yaml` in place of the word `any`.
+
+Running Tests
+-------------
+
+All tests (both canary and suite) should be passing.  Canary are quick test
+verifies that basic CSS is working.  The suite tests are a comprehensive set of
+~11,000 tests.
+
+```bash
+export DART_SDK=path/to/dart/sdk
+
+# Make sure dependencies are installed
+pub install
+
+# Run command both canary and the suite tests
+test/run.sh
+```
+
+  Run only the canary test:
+
+```bash
+ test/run.sh canary
+```
+
+  Run only the suite tests:
+
+```bash
+ test/run.sh suite
+```
+
+[dart]: http://www.dartlang.org/
+[pub]: http://www.dartlang.org/docs/pub-package-manager/
diff --git a/bin/css.dart b/bin/css.dart
new file mode 100644
index 0000000..08f2930
--- /dev/null
+++ b/bin/css.dart
@@ -0,0 +1,8 @@
+#!/usr/bin/env dart
+// 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.
+
+import 'package:csslib/css.dart' as css;
+
+void main() => css.main();
diff --git a/example/call_parser.dart b/example/call_parser.dart
new file mode 100644
index 0000000..6271057
--- /dev/null
+++ b/example/call_parser.dart
@@ -0,0 +1,90 @@
+// 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
+
+import 'package:csslib/parser.dart' as css;
+import 'package:csslib/visitor.dart';
+
+/**
+ * Spin-up CSS parser in checked mode to detect any problematic CSS.  Normally,
+ * CSS will allow any property/value pairs regardless of validity; all of our
+ * tests (by default) will ensure that the CSS is really valid.
+ */
+StyleSheet parseCss(String cssInput, {List errors, List opts}) =>
+    css.parse(cssInput, errors: errors, options: opts == null ?
+        ['--no-colors', '--checked', '--warnings_as_errors', 'memory'] : opts);
+
+// Pretty printer for CSS.
+var emitCss = new CssPrinter();
+String prettyPrint(StyleSheet ss) =>
+    (emitCss..visitTree(ss, pretty: true)).toString();
+
+main() {
+  var errors = [];
+
+  // Parse a simple stylesheet.
+  print('1. Good CSS, parsed CSS emitted:');
+  print('   =============================');
+  var stylesheet = parseCss(
+    '@import "support/at-charset-019.css"; div { color: red; }'
+    'button[type] { background-color: red; }'
+    '.foo { '
+      'color: red; left: 20px; top: 20px; width: 100px; height:200px'
+    '}'
+    '#div {'
+      'color : #00F578; border-color: #878787;'
+    '}', errors: errors);
+
+  if (!errors.isEmpty) {
+    print("Got ${errors.length} errors.\n");
+    for (var error in errors) {
+      print(error);
+    }
+  } else {
+    print(prettyPrint(stylesheet));
+  }
+
+  // Parse a stylesheet with errors
+  print('2. Catch severe syntax errors:');
+  print('   ===========================');
+  var stylesheetError = parseCss(
+    '.foo #%^&*asdf{ '
+      'color: red; left: 20px; top: 20px; width: 100px; height:200px'
+    '}', errors: errors);
+
+  if (!errors.isEmpty) {
+    print("Got ${errors.length} errors.\n");
+    for (var error in errors) {
+      print(error);
+    }
+  } else {
+    print(stylesheetError.toString());
+  }
+
+  // Parse a stylesheet that warns (checks) problematic CSS.
+  print('3. Detect CSS problem with checking on:');
+  print('   ===================================');
+  stylesheetError = parseCss( '# div1 { color: red; }', errors: errors);
+
+  if (!errors.isEmpty) {
+    print("Detected ${errors.length} problem in checked mode.\n");
+    for (var error in errors) {
+      print(error);
+    }
+  } else {
+    print(stylesheetError.toString());
+  }
+
+  // Parse a CSS selector.
+  print('4. Parse a selector only:');
+  print('   ======================');
+  var selectorAst = css.selector('#div .foo', errors: errors);
+  if (!errors.isEmpty) {
+    print("Got ${errors.length} errors.\n");
+    for (var error in errors) {
+      print(error);
+    }
+  } else {
+    print(prettyPrint(selectorAst));
+  }
+
+}
diff --git a/example/call_parser.html b/example/call_parser.html
new file mode 100644
index 0000000..8dc02e4
--- /dev/null
+++ b/example/call_parser.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Call CSS Parser from Browser (Dart2JS and Dartium)</title>
+  </head>
+  <body>
+    <h1>Dartium/Dart2JS Test</h1>
+    <script type="application/dart" src="call_parser.dart"></script>
+    <script src="packages/browser/dart.js"></script>
+  </body>
+</html>
diff --git a/example/test.css b/example/test.css
new file mode 100644
index 0000000..42aa048
--- /dev/null
+++ b/example/test.css
@@ -0,0 +1,6 @@
+.foo #abc {
+  color: red;
+  width: 300px;
+  left: 50px;
+  right: 50px;
+}
\ No newline at end of file
diff --git a/lib/css.dart b/lib/css.dart
new file mode 100644
index 0000000..ebfc675
--- /dev/null
+++ b/lib/css.dart
@@ -0,0 +1,84 @@
+// 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 css;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:io';
+import 'dart:math' as Math;
+
+import 'package:path/path.dart' as path;
+import 'package:source_maps/span.dart' show SourceFile;
+
+import 'parser.dart';
+import 'visitor.dart';
+import 'src/messages.dart';
+import 'src/options.dart';
+
+void main() {
+  // TODO(jmesserly): fix this to return a proper exit code
+  var options = PreprocessorOptions.parse(new Options().arguments);
+  if (options == null) return;
+
+  messages = new Messages(options: options);
+
+  _time('Total time spent on ${options.inputFile}', () {
+    _compile(options.inputFile, options.verbose);
+  }, true);
+}
+
+void _compile(String inputPath, bool verbose) {
+  var ext = path.extension(inputPath);
+  if (ext != '.css' && ext != '.scss') {
+    messages.error("Please provide a CSS/Sass file", null);
+    return;
+  }
+  try {
+    // Read the file.
+    var filename = path.basename(inputPath);
+    var contents = new File(inputPath).readAsStringSync();
+    var file = new SourceFile.text(inputPath, contents);
+
+    // Parse the CSS.
+    var tree = _time('Parse $filename',
+        () => new Parser(file, contents).parse(), verbose);
+
+    _time('Analyzer $filename',
+        () => new Analyzer([tree], messages), verbose).run();
+
+    // Emit the processed CSS.
+    var emitter = new CssPrinter();
+    _time('Codegen $filename',
+        () => emitter.visitTree(tree, pretty: true), verbose);
+
+    // Write the contents to a file.
+    var outPath = path.join(path.dirname(inputPath), '_$filename');
+    new File(outPath).writeAsStringSync(emitter.toString());
+  } catch (e) {
+    messages.error('error processing $inputPath. Original message:\n $e', null);
+  }
+}
+
+_time(String message, callback(), bool printTime) {
+  if (!printTime) return callback();
+  final watch = new Stopwatch();
+  watch.start();
+  var result = callback();
+  watch.stop();
+  final duration = watch.elapsedMilliseconds;
+  _printMessage(message, duration);
+  return result;
+}
+
+void _printMessage(String message, int duration) {
+  var buf = new StringBuffer();
+  buf.write(message);
+  for (int i = message.length; i < 60; i++) buf.write(' ');
+  buf.write(' -- ');
+  if (duration < 10) buf.write(' ');
+  if (duration < 100) buf.write(' ');
+  buf..write(duration)..write(' ms');
+  print(buf.toString());
+}
diff --git a/lib/parser.dart b/lib/parser.dart
new file mode 100644
index 0000000..a1cd715
--- /dev/null
+++ b/lib/parser.dart
@@ -0,0 +1,2402 @@
+// 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_maps/span.dart' show SourceFile, Span, FileSpan;
+
+import "visitor.dart";
+import 'src/messages.dart';
+import 'src/options.dart';
+
+part 'src/analyzer.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);
+}
+
+void _createMessages({List errors, List 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(var input, {List errors, List options, bool nested: true}) {
+  var source = _inputAsString(input);
+
+  _createMessages(errors: errors, options: options);
+
+  var file = new SourceFile.text(null, source);
+
+  var tree = new Parser(file, source).parse();
+
+  analyze([tree], errors: errors, options: options);
+
+  return tree;
+}
+
+/** Analyze the CSS file. */
+void analyze(List<StyleSheet> styleSheets,  {List errors, List 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(var input, {List errors, List options}) {
+  var source = _inputAsString(input);
+
+  _createMessages(errors: errors, options: options);
+
+  var file = new SourceFile.text(null, 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].
+ */
+StyleSheet selector(var input, {List errors}) {
+  var source = _inputAsString(input);
+
+  _createMessages(errors: errors);
+
+  var file = new SourceFile.text(null, source);
+
+  return new Parser(file, source).parseSelector();
+}
+
+String _inputAsString(var 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;
+}
+
+/** A simple recursive descent parser for CSS. */
+class Parser {
+  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 = [];
+
+    int start = _peekToken.start;
+    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 = [];
+
+    int start = _peekToken.start;
+    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, Span location) {
+    if (location == null) {
+      location = _peekToken.span;
+    }
+    messages.error(message, location);
+  }
+
+  void _warning(String message, Span location) {
+    if (location == null) {
+      location = _peekToken.span;
+    }
+    messages.warning(message, location);
+  }
+
+  Span _makeSpan(int start) {
+    // TODO(terry): there are places where we are creating spans before we eat
+    // the tokens, so using _previousToken.end is not always valid.
+    var end = _previousToken != null && _previousToken.end >= start
+        ? _previousToken.end : _peekToken.end;
+    return file.span(start, end);
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  // 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 ]*
+
+    int start = _peekToken.start;
+
+    // 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.start;
+    }
+
+    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]) {
+    int start = _peekToken.start;
+
+    // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+    if (_maybeEat(TokenKind.LPAREN)) {
+      if (_peekIdentifier()) {
+        var feature = identifier();           // Media feature.
+        while (_maybeEat(TokenKind.COLON)) {
+          int startExpr = _peekToken.start;
+          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 '}'
+  processDirective() {
+    int start = _peekToken.start;
+
+    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();
+          }
+
+          _eat(TokenKind.COLON);
+
+          Expressions exprs = processExpr();
+
+          var span = _makeSpan(start);
+          return new VarDefinitionDirective(
+              new VarDefinition(name, exprs, span), span);
+        } else if (isChecked) {
+          _error('unexpected directive @$_peekToken', _peekToken.span);
+        }
+      }
+    }
+
+    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;
+            }
+          }
+        }
+
+        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.start;
+        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));
+    }
+  }
+
+  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;
+    }
+  }
+
+  processDeclarations({bool checkBrace: true}) {
+    int start = _peekToken.start;
+
+    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 = [];
+
+    int start = _peekToken.start;
+
+    _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 = [];
+    int start = _peekToken.start;
+    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
+   */
+  processSelector() {
+    List<SimpleSelectorSequence> simpleSequences = [];
+    int start = _peekToken.start;
+    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.start;
+    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): Nathan 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;
+    int start = _peekToken.start;
+    switch (_peek()) {
+      case TokenKind.ASTERISK:
+        // Mark as universal namespace.
+        var tok = _next();
+        first = new Wildcard(_makeSpan(tok.start));
+        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.start));
+          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
+    int start = _peekToken.start;
+    switch (_peek()) {
+      case TokenKind.HASH:
+        _eat(TokenKind.HASH);
+
+        bool 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
+        // TODO(terry): '::' should be token.
+        _eat(TokenKind.COLON);
+        bool 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 (_maybeEat(TokenKind.LPAREN)) {
+          if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') {
+            // Negation :   ':NOT(' S* negation_arg S* ')'
+            var negArg = simpleSelector();
+
+            _eat(TokenKind.RPAREN);
+            return new NegationSelector(negArg, _makeSpan(start));
+          } else {
+            // Handle function expression.
+            var span = _makeSpan(start);
+            var expr = processSelectorExpression();
+
+            // 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));
+      case TokenKind.LBRACK:
+        return processAttribute();
+      case TokenKind.DOUBLE:
+        _error('name must start with a alpha character, but found a number',
+            _peekToken.span);
+        _next();
+        break;
+    }
+  }
+
+  /**
+   *  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() {
+    int start = _peekToken.start;
+
+    var expression = new SelectorExpression(_makeSpan(start));
+
+    Token termToken;
+    var value;
+
+    // Special parsing for expressions in pseudo functions.  Minus is used as
+    // operator not identifier.
+    tokenizer.selectorExpression = true;
+
+    bool keepParsing = true;
+    while (keepParsing) {
+      switch (_peek()) {
+        case TokenKind.PLUS:
+          start = _peekToken.start;
+          termToken = _next();
+          expression.add(new OperatorPlus(_makeSpan(start)));
+          break;
+        case TokenKind.MINUS:
+          start = _peekToken.start;
+          termToken = _next();
+          expression.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:
+        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));
+        }
+        expression.add(unitTerm);
+
+        value = null;
+      }
+    }
+
+    tokenizer.selectorExpression = false;
+
+    return expression;
+  }
+
+  //  Attribute grammar:
+  //
+  //  attributes :
+  //    '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']'
+  //
+  //  ATTRIB_MATCHES :
+  //    [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ]
+  //
+  //  INCLUDES:         '~='
+  //
+  //  DASHMATCH:        '|='
+  //
+  //  PREFIXMATCH:      '^='
+  //
+  //  SUFFIXMATCH:      '$='
+  //
+  //  SUBSTRMATCH:      '*='
+  //
+  //
+  processAttribute() {
+    int start = _peekToken.start;
+
+    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)
+  //
+  processDeclaration(List dartStyles) {
+    Declaration decl;
+
+    int start = _peekToken.start;
+
+    // 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));
+    }
+
+    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 _findStyle(String styleName) {
+    if (_stylesToDart.containsKey(styleName)) {
+      return _stylesToDart[styleName];
+    }
+  }
+
+  _styleForDart(Identifier property, Expressions exprs, List dartStyles) {
+    int 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;
+  }
+
+  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;
+    }
+  }
+
+  // TODO(terry): Look at handling width of thin, thick, etc. any none numbers
+  //              to convert to a number.
+  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.
+   */
+  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;
+    }
+
+    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)
+  //
+  processExpr([bool ieFilter = false]) {
+    int start = _peekToken.start;
+    Expressions expressions = new Expressions(_makeSpan(start));
+
+    bool keepGoing = true;
+    var expr;
+    while (keepGoing && (expr = processTerm(ieFilter)) != null) {
+      var op;
+
+      int opStart = _peekToken.start;
+
+      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.start;
+
+        _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) {
+        expressions.add(expr);
+      } else {
+        keepGoing = false;
+      }
+
+      if (op != null) {
+        expressions.add(op);
+        if (op is IE8Term) {
+          keepGoing = false;
+        } else {
+          _next();
+        }
+      }
+    }
+
+    return expressions;
+  }
+
+  static int MAX_UNICODE = int.parse('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]) {
+    int start = _peekToken.start;
+    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:
+    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.
+      String 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];
+        return new VarUsage(param.text, [], _makeSpan(start));
+      }
+      break;
+    }
+
+    return processDimension(t, value, _makeSpan(start));
+  }
+
+  /** Process all dimension units. */
+  processDimension(Token t, var value, Span span) {
+    var 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;
+  }
+
+  processQuotedString([bool urlString = false]) {
+    int start = _peekToken.start;
+
+    // URI term sucks up everything inside of quotes(' or ") or between parens
+    int stopToken = urlString ? TokenKind.RPAREN : -1;
+    switch (_peek()) {
+    case TokenKind.SINGLE_QUOTE:
+      stopToken = TokenKind.SINGLE_QUOTE;
+      start = _peekToken.start + 1;   // Skip the quote might have whitespace.
+      _next();    // Skip the SINGLE_QUOTE.
+      break;
+    case TokenKind.DOUBLE_QUOTE:
+      stopToken = TokenKind.DOUBLE_QUOTE;
+      start = _peekToken.start + 1;   // Skip the quote might have whitespace.
+      _next();    // Skip the DOUBLE_QUOTE.
+      break;
+    default:
+      if (urlString) {
+        if (_peek() == TokenKind.LPAREN) {
+          _next();    // Skip the LPAREN.
+          start = _peekToken.start;
+        }
+        stopToken = TokenKind.RPAREN;
+      } else {
+        _error('unexpected string', _makeSpan(start));
+      }
+      break;
+    }
+
+    // Gobble up everything until we hit our stop token.
+    int runningStart = _peekToken.start;
+    while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
+      var tok = _next();
+    }
+
+    // All characters between quotes is the string.
+    int end = _peekToken.end;
+    var stringValue = (_peekToken.span as FileSpan).file.getText(start,
+        end - 1);
+
+    if (stopToken != TokenKind.RPAREN) {
+      _next();    // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
+    }
+
+    return stringValue;
+  }
+
+  // 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(int startAfterProgidColon) {
+    int 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,
+                _peekToken.start);
+            return new LiteralTerm(tok.text, tok.text, tok.span);
+          }
+          break;
+        default:
+          _eat(_peek());
+      }
+    }
+  }
+
+  //  Function grammar:
+  //
+  //  function:     IDENT '(' expr ')'
+  //
+  processFunction(Identifier func) {
+    int start = _peekToken.start;
+
+    String name = func.name;
+
+    switch (name) {
+    case 'url':
+      // URI term sucks up everything inside of quotes(' or ") or between parens
+      String 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() {
+    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.start));
+    }
+
+    return new Identifier(tok.text, _makeSpan(tok.start));
+  }
+
+  // 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, Span span) {
+    int hexValue = 0;
+
+     for (int 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.
+  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;
+    bool 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);
+  }
+
+  processFontFamily() {
+    final List<String> 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;
+     */
+    bool 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);
+  }
+
+  processFont() {
+    var 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++) {
+    int code = text.codeUnitAt(i);
+    var 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();
+}
diff --git a/lib/src/analyzer.dart b/lib/src/analyzer.dart
new file mode 100644
index 0000000..9e366a5
--- /dev/null
+++ b/lib/src/analyzer.dart
@@ -0,0 +1,513 @@
+// 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.
+
+part of csslib.parser;
+
+
+// TODO(terry): Detect invalid directive usage.  All @imports must occur before
+//              all rules other than @charset directive.  Any @import directive
+//              after any non @charset or @import directive are ignored. e.g.,
+//                  @import "a.css";
+//                  div { color: red; }
+//                  @import "b.css";
+//              becomes:
+//                  @import "a.css";
+//                  div { color: red; }
+// <http://www.w3.org/TR/css3-syntax/#at-rules>
+
+/**
+ * Analysis phase will validate/fixup any new CSS feature or any SASS style
+ * feature.
+ */
+class Analyzer {
+  final List<StyleSheet> _styleSheets;
+  final Messages _messages;
+  VarDefinitions varDefs;
+
+  Analyzer(this._styleSheets, this._messages);
+
+  void run() {
+    varDefs = new VarDefinitions(_styleSheets);
+
+    // Any cycles?
+    var cycles = findAllCycles();
+    for (var cycle in cycles) {
+      _messages.warning("var cycle detected var-${cycle.definedName}",
+          cycle.span);
+      // TODO(terry): What if no var definition for a var usage an error?
+      // TODO(terry): Ensure a var definition imported from a different style
+      //              sheet works.
+    }
+
+    // Remove any var definition from the stylesheet that has a cycle.
+    _styleSheets.forEach((styleSheet) =>
+        new RemoveVarDefinitions(cycles).visitStyleSheet(styleSheet));
+
+    // Expand any nested selectors using selector desendant combinator to
+    // signal CSS inheritance notation.
+    _styleSheets.forEach((styleSheet) => new ExpandNestedSelectors()
+        ..visitStyleSheet(styleSheet)
+        ..flatten(styleSheet));
+  }
+
+  List<VarDefinition> findAllCycles() {
+    var cycles = [];
+
+    varDefs.map.values.forEach((value) {
+      if (hasCycle(value.property)) cycles.add(value);
+     });
+
+    // Update our local list of known varDefs remove any varDefs with a cycle.
+    // So the same varDef cycle isn't reported for each style sheet processed.
+    for (var cycle in cycles) {
+      varDefs.map.remove(cycle.property);
+    }
+
+    return cycles;
+  }
+
+  Iterable<VarUsage> variablesOf(Expressions exprs) =>
+      exprs.expressions.where((e) => e is VarUsage);
+
+  bool hasCycle(String varName, {Set<String> visiting, Set<String> visited}) {
+    if (visiting == null) visiting = new Set();
+    if (visited == null) visited = new Set();
+    if (visiting.contains(varName)) return true;
+    if (visited.contains(varName)) return false;
+    visiting.add(varName);
+    visited.add(varName);
+    bool cycleDetected = false;
+    if (varDefs.map[varName] != null) {
+      for (var usage in variablesOf(varDefs.map[varName].expression)) {
+        if (hasCycle(usage.name, visiting: visiting, visited: visited)) {
+          cycleDetected = true;
+          break;
+        }
+      }
+    }
+    visiting.remove(varName);
+    return cycleDetected;
+  }
+
+  // TODO(terry): Need to start supporting @host, custom pseudo elements,
+  //              composition, intrinsics, etc.
+}
+
+
+/** Find all var definitions from a list of stylesheets. */
+class VarDefinitions extends Visitor {
+  /** Map of variable name key to it's definition. */
+  final Map<String, VarDefinition> map = new Map<String, VarDefinition>();
+
+  VarDefinitions(List<StyleSheet> styleSheets) {
+    for (var styleSheet in styleSheets) {
+      visitTree(styleSheet);
+    }
+  }
+
+  void visitVarDefinition(VarDefinition node) {
+    // Replace with latest variable definition.
+    map[node.definedName] = node;
+    super.visitVarDefinition(node);
+  }
+
+  void visitVarDefinitionDirective(VarDefinitionDirective node) {
+    visitVarDefinition(node.def);
+  }
+}
+
+/**
+ * Remove the var definition from the stylesheet where it is defined; if it is
+ * a definition from the list to delete.
+ */
+class RemoveVarDefinitions extends Visitor {
+  final List<VarDefinition> _varDefsToRemove;
+
+  RemoveVarDefinitions(this._varDefsToRemove);
+
+  void visitStyleSheet(StyleSheet ss) {
+    var idx = ss.topLevels.length;
+    while(--idx >= 0) {
+      var topLevel = ss.topLevels[idx];
+      if (topLevel is VarDefinitionDirective &&
+          _varDefsToRemove.contains(topLevel.def)) {
+        ss.topLevels.removeAt(idx);
+      }
+    }
+
+    super.visitStyleSheet(ss);
+  }
+
+  void visitDeclarationGroup(DeclarationGroup node) {
+    var idx = node.declarations.length;
+    while (--idx >= 0) {
+      var decl = node.declarations[idx];
+      if (decl is VarDefinition && _varDefsToRemove.contains(decl)) {
+        node.declarations.removeAt(idx);
+      }
+    }
+
+    super.visitDeclarationGroup(node);
+  }
+}
+
+/**
+ * Traverse all rulesets looking for nested ones.  If a ruleset is in a
+ * declaration group (implies nested selector) then generate new ruleset(s) at
+ * level 0 of CSS using selector inheritance syntax (flattens the nesting).
+ *
+ * How the AST works for a rule [RuleSet] and nested rules.  First of all a
+ * CSS rule [RuleSet] consist of a selector and a declaration e.g.,
+ *
+ *    selector {
+ *      declaration
+ *    }
+ *
+ * AST structure of a [RuleSet] is:
+ *
+ *    RuleSet
+ *       SelectorGroup
+ *         List<Selector>
+ *            List<SimpleSelectorSequence>
+ *              Combinator      // +, >, ~, DESCENDENT, or NONE
+ *              SimpleSelector  // class, id, element, namespace, attribute
+ *        DeclarationGroup
+ *          List                // Declaration or RuleSet
+ *
+ * For the simple rule:
+ *
+ *    div + span { color: red; }
+ *
+ * the AST [RuleSet] is:
+ *
+ *    RuleSet
+ *       SelectorGroup
+ *         List<Selector>
+ *          [0]
+ *            List<SimpleSelectorSequence>
+ *              [0] Combinator = COMBINATOR_NONE
+ *                  ElementSelector (name = div)
+ *              [1] Combinator = COMBINATOR_PLUS
+ *                  ElementSelector (name = span)
+ *        DeclarationGroup
+ *          List                // Declarations or RuleSets
+ *            [0]
+ *              Declaration (property = color, expression = red)
+ *
+ * Usually a SelectorGroup contains 1 Selector.  Consider the selectors:
+ *
+ *    div { color: red; }
+ *    a { color: red; }
+ *
+ * are equivalent to
+ *
+ *    div, a { color : red; }
+ *
+ * In the above the RuleSet would have a SelectorGroup with 2 selectors e.g.,
+ *
+ *    RuleSet
+ *       SelectorGroup
+ *         List<Selector>
+ *          [0]
+ *            List<SimpleSelectorSequence>
+ *              [0] Combinator = COMBINATOR_NONE
+ *                  ElementSelector (name = div)
+ *          [1]
+ *            List<SimpleSelectorSequence>
+ *              [0] Combinator = COMBINATOR_NONE
+ *                  ElementSelector (name = a)
+ *        DeclarationGroup
+ *          List                // Declarations or RuleSets
+ *            [0]
+ *              Declaration (property = color, expression = red)
+ *
+ * For a nested rule e.g.,
+ *
+ *    div {
+ *      color : blue;
+ *      a { color : red; }
+ *    }
+ *
+ * Would map to the follow CSS rules:
+ *
+ *    div { color: blue; }
+ *    div a { color: red; }
+ *
+ * The AST for the former nested rule is:
+ *
+ *    RuleSet
+ *       SelectorGroup
+ *         List<Selector>
+ *          [0]
+ *            List<SimpleSelectorSequence>
+ *              [0] Combinator = COMBINATOR_NONE
+ *                  ElementSelector (name = div)
+ *        DeclarationGroup
+ *          List                // Declarations or RuleSets
+ *            [0]
+ *              Declaration (property = color, expression = blue)
+ *            [1]
+ *              RuleSet
+ *                SelectorGroup
+ *                  List<Selector>
+ *                    [0]
+ *                      List<SimpleSelectorSequence>
+ *                        [0] Combinator = COMBINATOR_NONE
+ *                            ElementSelector (name = a)
+ *                DeclarationGroup
+ *                  List                // Declarations or RuleSets
+ *                    [0]
+ *                      Declaration (property = color, expression = red)
+ *
+ * Nested rules is a terse mechanism to describe CSS inheritance.  The analyzer
+ * will flatten and expand the nested rules to it's flatten strucure.  Using the
+ * all parent [RuleSets] (selector expressions) and applying each nested
+ * [RuleSet] to the list of [Selectors] in a [SelectorGroup].
+ *
+ * Then result is a style sheet where all nested rules have been flatten and
+ * expanded.
+ */
+class ExpandNestedSelectors extends Visitor {
+  /** Parent [RuleSet] if a nested rule otherwise [null]. */
+  RuleSet _parentRuleSet;
+
+  /** Top-most rule if nested rules. */
+  SelectorGroup _topLevelSelectorGroup;
+
+  /** SelectorGroup at each nesting level. */
+  SelectorGroup _nestedSelectorGroup;
+
+  /** Declaration (sans the nested selectors). */
+  DeclarationGroup _flatDeclarationGroup;
+
+  /** Each nested selector get's a flatten RuleSet. */
+  List<RuleSet> _expandedRuleSets = [];
+
+  /** Maping of a nested rule set to the fully expanded list of RuleSet(s). */
+  final Map<RuleSet, List<RuleSet>> _expansions = new Map();
+
+  void visitRuleSet(RuleSet node) {
+    final oldParent = _parentRuleSet;
+
+    var oldNestedSelectorGroups = _nestedSelectorGroup;
+
+    if (_nestedSelectorGroup == null) {
+      // Create top-level selector (may have nested rules).
+      final newSelectors = node.selectorGroup.selectors.toList();
+      _topLevelSelectorGroup = new SelectorGroup(newSelectors, node.span);
+      _nestedSelectorGroup = _topLevelSelectorGroup;
+    } else {
+      // Generate new selector groups from the nested rules.
+      _nestedSelectorGroup = _mergeToFlatten(node);
+    }
+
+    _parentRuleSet = node;
+
+    super.visitRuleSet(node);
+
+    _parentRuleSet = oldParent;
+
+    // Remove nested rules; they're all flatten and in the _expandedRuleSets.
+    node.declarationGroup.declarations.removeWhere((declaration) =>
+        declaration is RuleSet);
+
+    _nestedSelectorGroup = oldNestedSelectorGroups;
+
+    // If any expandedRuleSets and we're back at the top-level rule set then
+    // there were nested rule set(s).
+    if (_parentRuleSet == null) {
+      if (!_expandedRuleSets.isEmpty) {
+        // Remember ruleset to replace with these flattened rulesets.
+        _expansions[node] = _expandedRuleSets;
+        _expandedRuleSets = [];
+      }
+      assert(_flatDeclarationGroup == null);
+      assert(_nestedSelectorGroup == null);
+    }
+  }
+
+  /**
+   * Build up the list of all inherited sequences from the parent selector
+   * [node] is the current nested selector and it's parent is the last entry in
+   * the [_nestedSelectorGroup].
+   */
+  SelectorGroup _mergeToFlatten(RuleSet node) {
+    // Create a new SelectorGroup for this nesting level.
+    var nestedSelectors = _nestedSelectorGroup.selectors;
+    var selectors = node.selectorGroup.selectors;
+
+    // Create a merged set of previous parent selectors and current selectors.
+    var newSelectors = [];
+    for (Selector selector in selectors) {
+      for (Selector nestedSelector in nestedSelectors) {
+        var seq = _mergeNestedSelector(nestedSelector.simpleSelectorSequences,
+            selector.simpleSelectorSequences);
+        newSelectors.add(new Selector(seq, node.span));
+      }
+    }
+
+    return new SelectorGroup(newSelectors, node.span);
+  }
+
+  /**
+   * Merge the nested selector sequences [current] to the [parent] sequences or
+   * substitue any & with the parent selector.
+   */
+  List<SimpleSelectorSequence> _mergeNestedSelector(
+      List<SimpleSelectorSequence> parent,
+      List<SimpleSelectorSequence> current) {
+
+    // If any & operator then the parent selector will be substituted otherwise
+    // the parent selector is pre-pended to the current selector.
+    var hasThis = current.any((s) => s.simpleSelector.isThis);
+
+    var newSequence = [];
+
+    if (!hasThis) {
+      // If no & in the sector group then prefix with the parent selector.
+      newSequence.addAll(parent);
+      newSequence.addAll(_convertToDescendentSequence(current));
+    } else {
+      for (var sequence in current) {
+        if (sequence.simpleSelector.isThis) {
+          // Substitue the & with the parent selector and only use a combinator
+          // descendant if & is prefix by a sequence with an empty name e.g.,
+          // "... + &", "&", "... ~ &", etc.
+          var hasPrefix = !newSequence.isEmpty &&
+              !newSequence.last.simpleSelector.name.isEmpty;
+          newSequence.addAll(
+              hasPrefix ? _convertToDescendentSequence(parent) : parent);
+        } else {
+          newSequence.add(sequence);
+        }
+      }
+    }
+
+    return newSequence;
+  }
+
+  /**
+   * Return selector sequences with first sequence combinator being a
+   * descendant.  Used for nested selectors when the parent selector needs to
+   * be prefixed to a nested selector or to substitute the this (&) with the
+   * parent selector.
+   */
+  List<SimpleSelectorSequence> _convertToDescendentSequence(
+      List<SimpleSelectorSequence> sequences) {
+    if (sequences.isEmpty) return sequences;
+
+    var newSequences = [];
+    var first = sequences.first;
+    newSequences.add(new SimpleSelectorSequence(first.simpleSelector,
+        first.span, TokenKind.COMBINATOR_DESCENDANT));
+    newSequences.addAll(sequences.skip(1));
+
+    return newSequences;
+  }
+
+  void visitDeclarationGroup(DeclarationGroup node) {
+    var span = node.span;
+
+    var currentGroup = new DeclarationGroup([], span);
+
+    var oldGroup = _flatDeclarationGroup;
+    _flatDeclarationGroup = currentGroup;
+
+    var expandedLength = _expandedRuleSets.length;
+
+    super.visitDeclarationGroup(node);
+
+    // We're done with the group.
+    _flatDeclarationGroup = oldGroup;
+
+    // No nested rule to process it's a top-level rule.
+    if (_nestedSelectorGroup == _topLevelSelectorGroup) return;
+
+    // If flatten selector's declaration is empty skip this selector, no need
+    // to emit an empty nested selector.
+    if (currentGroup.declarations.isEmpty) return;
+
+    var selectorGroup = _nestedSelectorGroup;
+
+    // Build new rule set from the nested selectors and declarations.
+    var newRuleSet = new RuleSet(selectorGroup, currentGroup, span);
+
+    // Place in order so outer-most rule is first.
+    if (expandedLength == _expandedRuleSets.length) {
+      _expandedRuleSets.add(newRuleSet);
+    } else {
+      _expandedRuleSets.insert(expandedLength, newRuleSet);
+    }
+  }
+
+  // Record all declarations in a nested selector (Declaration, VarDefinition
+  // and MarginGroup) but not the nested rule in the Declaration.
+
+  void visitDeclaration(Declaration node) {
+    if (_parentRuleSet != null) {
+      _flatDeclarationGroup.declarations.add(node);
+    }
+    super.visitDeclaration(node);
+  }
+
+  void visitVarDefinition(VarDefinition node) {
+    if (_parentRuleSet != null) {
+      _flatDeclarationGroup.declarations.add(node);
+    }
+    super.visitVarDefinition(node);
+  }
+
+  void visitMarginGroup(MarginGroup node) {
+    if (_parentRuleSet != null) {
+      _flatDeclarationGroup.declarations.add(node);
+    }
+    super.visitMarginGroup(node);
+  }
+
+  /**
+   * Replace the rule set that contains nested rules with the flatten rule sets.
+   */
+  void flatten(StyleSheet styleSheet) {
+    // TODO(terry): Iterate over topLevels instead of _expansions it's already
+    //              a map (this maybe quadratic).
+    _expansions.forEach((RuleSet ruleSet, List<RuleSet> newRules) {
+      var index = styleSheet.topLevels.indexOf(ruleSet);
+      if (index == -1) {
+        // Check any @media directives for nested rules and replace them.
+        var found = _MediaRulesReplacer.replace(styleSheet, ruleSet, newRules);
+        assert(found);
+      } else {
+        styleSheet.topLevels.insertAll(index + 1, newRules);
+      }
+    });
+    _expansions.clear();
+  }
+}
+
+class _MediaRulesReplacer extends Visitor {
+  RuleSet _ruleSet;
+  List<RuleSet> _newRules;
+  bool _foundAndReplaced = false;
+
+  /**
+   * Look for the [ruleSet] inside of an @media directive; if found then replace
+   * with the [newRules].  If [ruleSet] is found and replaced return true.
+   */
+  static bool replace(StyleSheet styleSheet, RuleSet ruleSet,
+                      List<RuleSet>newRules) {
+    var visitor = new _MediaRulesReplacer(ruleSet, newRules);
+    visitor.visitStyleSheet(styleSheet);
+    return visitor._foundAndReplaced;
+  }
+
+  _MediaRulesReplacer(this._ruleSet, this._newRules);
+
+  visitMediaDirective(MediaDirective node) {
+    var index = node.rulesets.indexOf(_ruleSet);
+    if (index != -1) {
+      node.rulesets.insertAll(index + 1, _newRules);
+      _foundAndReplaced = true;
+    }
+  }
+}
diff --git a/lib/src/css_printer.dart b/lib/src/css_printer.dart
new file mode 100644
index 0000000..b8000b7
--- /dev/null
+++ b/lib/src/css_printer.dart
@@ -0,0 +1,476 @@
+// Copyright (c) 2013, 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.
+
+part of csslib.visitor;
+
+/**
+ * Visitor that produces a formatted string representation of the CSS tree.
+ */
+class CssPrinter extends Visitor {
+  StringBuffer _buff = new StringBuffer();
+  bool prettyPrint = true;
+
+  /**
+   * Walk the [tree] Stylesheet. [pretty] if true emits line breaks, extra
+   * spaces, friendly property values, etc., if false emits compacted output.
+   */
+  void visitTree(StyleSheet tree, {bool pretty: false}) {
+    prettyPrint = pretty;
+    _buff = new StringBuffer();
+    visitStyleSheet(tree);
+  }
+
+  /** Appends [str] to the output buffer. */
+  void emit(String str) {
+    _buff.write(str);
+  }
+
+  /** Returns the output buffer. */
+  String toString() => _buff.toString().trim();
+
+  String get _newLine => prettyPrint ? '\n' : ' ';
+  String get _sp => prettyPrint ? ' ' : '';
+
+  // TODO(terry): When adding obfuscation we'll need isOptimized (compact w/
+  //              obufuscation) and have isTesting (compact no obfuscation) and
+  //              isCompact would be !prettyPrint.  We'll need another boolean
+  //              flag for obfuscation.
+  bool get _isTesting => !prettyPrint;
+
+  void visitCssComment(CssComment node) {
+    emit('/* ${node.comment} */');
+  }
+
+  void visitCommentDefinition(CommentDefinition node) {
+    emit('<!-- ${node.comment} -->');
+  }
+
+  void visitMediaExpression(MediaExpression node) {
+    emit(node.andOperator ? ' AND ' : ' ');
+    emit('(${node.mediaFeature}:');
+    visitExpressions(node.exprs);
+    emit(')');
+  }
+
+  void visitMediaQuery(MediaQuery query) {
+    var unary = query.hasUnary ? ' ${query.unary}' : '';
+    var mediaType = query.hasMediaType ? ' ${query.mediaType}' : '';
+    emit('$unary$mediaType');
+    for (var expression in query.expressions) {
+      visitMediaExpression(expression);
+    }
+  }
+
+  void emitMediaQueries(queries) {
+    var queriesLen = queries.length;
+    for (var i = 0; i < queriesLen; i++) {
+      var query = queries[i];
+      if (query.hasMediaType && i > 0) emit(',');
+      visitMediaQuery(query);
+    }
+  }
+
+  void visitMediaDirective(MediaDirective node) {
+    emit(' @media');
+    emitMediaQueries(node.mediaQueries);
+    emit(' {');
+    for (var ruleset in node.rulesets) {
+      ruleset.visit(this);
+    }
+    emit('$_newLine\}');
+  }
+
+  void visitHostDirective(HostDirective node) {
+    emit('\n@host {');
+    for (var ruleset in node.rulesets) {
+      ruleset.visit(this);
+    }
+    emit('$_newLine\}');
+  }
+
+  /**
+   *  @page : pseudoPage {
+   *    decls
+   *  }
+   */
+  void visitPageDirective(PageDirective node) {
+    emit('$_newLine@page');
+    if (node.hasIdent || node.hasPseudoPage) {
+      if (node.hasIdent) emit(' ');
+      emit(node._ident);
+      emit(node.hasPseudoPage ? ':${node._pseudoPage}' : '');
+    }
+    emit(' ');
+
+    var declsMargin = node._declsMargin;
+    int declsMarginLength = declsMargin.length;
+    for (var i = 0; i < declsMarginLength; i++) {
+      if (i > 0) emit(_newLine);
+      emit('{$_newLine');
+      declsMargin[i].visit(this);
+      emit('}');
+    }
+  }
+
+  /** @charset "charset encoding" */
+  void visitCharsetDirective(CharsetDirective node) {
+    emit('$_newLine@charset "${node.charEncoding}";');
+  }
+
+  void visitImportDirective(ImportDirective node) {
+    bool isStartingQuote(String ch) => ('\'"'.indexOf(ch[0]) >= 0);
+
+    if (_isTesting) {
+      // Emit assuming url() was parsed; most suite tests use url function.
+      emit(' @import url(${node.import})');
+    } else if (isStartingQuote(node.import)) {
+      emit(' @import ${node.import}');
+    } else {
+      // url(...) isn't needed only a URI can follow an @import directive; emit
+      // url as a string.
+      emit(' @import "${node.import}"');
+    }
+    emitMediaQueries(node.mediaQueries);
+    emit(';');
+  }
+
+  void visitKeyFrameDirective(KeyFrameDirective node) {
+    emit('$_newLine${node.keyFrameName} ');
+    node._name.visit(this);
+    emit('$_sp{$_newLine');
+    for (final block in node._blocks) {
+      block.visit(this);
+    }
+    emit('}');
+  }
+
+  void visitFontFaceDirective(FontFaceDirective node) {
+    emit('$_newLine@font-face ');
+    emit('$_sp{$_newLine');
+    node._declarations.visit(this);
+    emit('}');
+  }
+
+  void visitKeyFrameBlock(KeyFrameBlock node) {
+    emit('$_sp$_sp');
+    node._blockSelectors.visit(this);
+    emit('$_sp{$_newLine');
+    node._declarations.visit(this);
+    emit('$_sp$_sp}$_newLine');
+  }
+
+  void visitStyletDirective(StyletDirective node) {
+    emit('/* @stylet export as ${node._dartClassName} */\n');
+  }
+
+  void visitNamespaceDirective(NamespaceDirective node) {
+    bool isStartingQuote(String ch) => ('\'"'.indexOf(ch) >= 0);
+
+    if (isStartingQuote(node._uri)) {
+      emit(' @namespace ${node.prefix}"${node._uri}"');
+    } else {
+      if (_isTesting) {
+        // Emit exactly was we parsed.
+        emit(' @namespace ${node.prefix}url(${node._uri})');
+      } else {
+        // url(...) isn't needed only a URI can follow a:
+        //    @namespace prefix directive.
+        emit(' @namespace ${node.prefix}${node._uri}');
+      }
+    }
+    emit(';');
+  }
+
+  void visitVarDefinitionDirective(VarDefinitionDirective node) {
+    visitVarDefinition(node.def);
+    emit(';$_newLine');
+  }
+
+  void visitRuleSet(RuleSet node) {
+    emit("$_newLine");
+    node._selectorGroup.visit(this);
+    emit(" {$_newLine");
+    node._declarationGroup.visit(this);
+    emit("}");
+  }
+
+  void visitDeclarationGroup(DeclarationGroup node) {
+    var declarations = node._declarations;
+    var declarationsLength = declarations.length;
+    for (var i = 0; i < declarationsLength; i++) {
+      if (i > 0) emit(_newLine);
+      emit("$_sp$_sp");
+      declarations[i].visit(this);
+      emit(";");
+    }
+    if (declarationsLength > 0) emit(_newLine);
+  }
+
+  void visitMarginGroup(MarginGroup node) {
+    var margin_sym_name =
+        TokenKind.idToValue(TokenKind.MARGIN_DIRECTIVES, node.margin_sym);
+
+    emit("@$margin_sym_name {$_newLine");
+
+    visitDeclarationGroup(node);
+
+    emit("}$_newLine");
+  }
+
+  void visitDeclaration(Declaration node) {
+    String importantAsString() => node.important ? '$_sp!important' : '';
+
+    emit("${node.property}: ");
+    node._expression.visit(this);
+
+    emit("${importantAsString()}");
+  }
+
+  void visitVarDefinition(VarDefinition node) {
+    emit("var-${node.definedName}: ");
+    node._expression.visit(this);
+  }
+
+  void visitSelectorGroup(SelectorGroup node) {
+    var selectors = node._selectors;
+    var selectorsLength = selectors.length;
+    for (var i = 0; i < selectorsLength; i++) {
+      if (i > 0) emit(',$_sp');
+      selectors[i].visit(this);
+    }
+  }
+
+  void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+    emit('${node._combinatorToString}');
+    node._selector.visit(this);
+  }
+
+  void visitSimpleSelector(SimpleSelector node) {
+    emit(node.name);
+  }
+
+  void visitNamespaceSelector(NamespaceSelector node) {
+    emit("${node.namespace}|${node.nameAsSimpleSelector.name}");
+  }
+
+  void visitElementSelector(ElementSelector node) {
+    emit("${node.name}");
+  }
+
+  void visitAttributeSelector(AttributeSelector node) {
+    emit("[${node.name}${node.matchOperator()}${node.valueToString()}]");
+  }
+
+  void visitIdSelector(IdSelector node) {
+    emit("#${node.name}");
+  }
+
+  void visitClassSelector(ClassSelector node) {
+    emit(".${node.name}");
+  }
+
+  void visitPseudoClassSelector(PseudoClassSelector node) {
+    emit(":${node.name}");
+  }
+
+  void visitPseudoElementSelector(PseudoElementSelector node) {
+    emit("::${node.name}");
+  }
+
+  void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) {
+    emit(":${node.name}(");
+    node.expression.visit(this);
+    emit(')');
+  }
+
+  void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) {
+    emit("::${node.name}(");
+    node.expression.visit(this);
+    emit(')');
+  }
+
+  void visitNegationSelector(NegationSelector node) {
+    emit(':not(');
+    node.negationArg.visit(this);
+    emit(')');
+  }
+
+  void visitSelectorExpression(SelectorExpression node) {
+    var expressions = node._expressions;
+    var expressionsLength = expressions.length;
+    for (var i = 0; i < expressionsLength; i++) {
+      // Add space seperator between terms without an operator.
+      var expression = expressions[i];
+      expression.visit(this);
+    }
+  }
+
+  void visitUnicodeRangeTerm(UnicodeRangeTerm node) {
+    if (node.hasSecond) {
+      emit("U+${node.first}-${node.second}");
+    } else {
+      emit("U+${node.first}");
+    }
+  }
+
+  void visitLiteralTerm(LiteralTerm node) {
+    emit(node.text);
+  }
+
+  void visitHexColorTerm(HexColorTerm node) {
+    var mappedName;
+    if (_isTesting && (node.value is! BAD_HEX_VALUE)) {
+      mappedName = TokenKind.hexToColorName(node.value);
+    }
+    if (mappedName == null) {
+      mappedName = '#${node.text}';
+    }
+
+    emit(mappedName);
+  }
+
+  void visitNumberTerm(NumberTerm node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitUnitTerm(UnitTerm node) {
+    emit(node.toString());
+  }
+
+  void visitLengthTerm(LengthTerm node) {
+    emit(node.toString());
+  }
+
+  void visitPercentageTerm(PercentageTerm node) {
+    emit('${node.text}%');
+  }
+
+  void visitEmTerm(EmTerm node) {
+    emit('${node.text}em');
+  }
+
+  void visitExTerm(ExTerm node) {
+    emit('${node.text}ex');
+  }
+
+  void visitAngleTerm(AngleTerm node) {
+    emit(node.toString());
+  }
+
+  void visitTimeTerm(TimeTerm node) {
+    emit(node.toString());
+  }
+
+  void visitFreqTerm(FreqTerm node) {
+    emit(node.toString());
+  }
+
+  void visitFractionTerm(FractionTerm node) {
+    emit('${node.text}fr');
+  }
+
+  void visitUriTerm(UriTerm node) {
+    emit('url("${node.text}")');
+  }
+
+  void visitResolutionTerm(ResolutionTerm node) {
+    emit(node.toString());
+  }
+
+  void visitViewportTerm(ViewportTerm node) {
+    emit(node.toString());
+  }
+
+  void visitFunctionTerm(FunctionTerm node) {
+    // TODO(terry): Optimize rgb to a hexcolor.
+    emit('${node.text}(');
+    node._params.visit(this);
+    emit(')');
+  }
+
+  void visitGroupTerm(GroupTerm node) {
+    emit('(');
+    var terms = node._terms;
+    var termsLength = terms.length;
+    for (var i = 0; i < termsLength; i++) {
+      if (i > 0) emit('$_sp');
+      terms[i].visit(this);
+    }
+    emit(')');
+  }
+
+  void visitItemTerm(ItemTerm node) {
+    emit('[${node.text}]');
+  }
+
+  void visitIE8Term(IE8Term node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitOperatorSlash(OperatorSlash node) {
+    emit('/');
+  }
+
+  void visitOperatorComma(OperatorComma node) {
+    emit(',');
+  }
+
+  void visitOperatorPlus(OperatorPlus node) {
+    emit('+');
+  }
+
+  void visitOperatorMinus(OperatorMinus node) {
+    emit('-');
+  }
+
+  void visitVarUsage(VarUsage node) {
+    emit('var(${node.name}');
+    if (!node.defaultValues.isEmpty) {
+      emit(',');
+      for (var defaultValue in node.defaultValues) {
+        emit(' ');
+        defaultValue.visit(this);
+      }
+    }
+    emit(')');
+  }
+
+  void visitExpressions(Expressions node) {
+    var expressions = node.expressions;
+    var expressionsLength = expressions.length;
+    for (var i = 0; i < expressionsLength; i++) {
+      // Add space seperator between terms without an operator.
+      // TODO(terry): Should have a BinaryExpression to solve this problem.
+      var expression = expressions[i];
+      if (i > 0 &&
+          !(expression is OperatorComma || expression is OperatorSlash)) {
+        emit(' ');
+      }
+      expression.visit(this);
+    }
+  }
+
+  void visitBinaryExpression(BinaryExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitUnaryExpression(UnaryExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitIdentifier(Identifier node) {
+    emit(node.name);
+  }
+
+  void visitWildcard(Wildcard node) {
+    emit('*');
+  }
+
+  void visitDartStyleExpression(DartStyleExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+}
diff --git a/lib/src/messages.dart b/lib/src/messages.dart
new file mode 100644
index 0000000..3c9f3fc
--- /dev/null
+++ b/lib/src/messages.dart
@@ -0,0 +1,129 @@
+// 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.src.messages;
+
+import 'package:logging/logging.dart' show Level;
+import 'package:source_maps/span.dart' show Span;
+
+import 'package:csslib/parser.dart';
+
+import 'options.dart';
+
+// TODO(terry): Remove the global messages, use some object that tracks
+//              compilation state.
+
+/** The global [Messages] for tracking info/warnings/messages. */
+Messages messages;
+
+// Color constants used for generating messages.
+final String GREEN_COLOR = '\u001b[32m';
+final String RED_COLOR = '\u001b[31m';
+final String MAGENTA_COLOR = '\u001b[35m';
+final String NO_COLOR = '\u001b[0m';
+
+/** Map between error levels and their display color. */
+final Map<Level, String> _ERROR_COLORS = (() {
+  var colorsMap = new Map<Level, String>();
+  colorsMap[Level.SEVERE] = RED_COLOR;
+  colorsMap[Level.WARNING] = MAGENTA_COLOR;
+  colorsMap[Level.INFO] = GREEN_COLOR;
+  return colorsMap;
+})();
+
+/** Map between error levels and their friendly name. */
+final Map<Level, String> _ERROR_LABEL = (() {
+  var labels = new Map<Level, String>();
+  labels[Level.SEVERE] = 'error';
+  labels[Level.WARNING] = 'warning';
+  labels[Level.INFO] = 'info';
+  return labels;
+})();
+
+/** A single message from the compiler. */
+class Message {
+  final Level level;
+  final String message;
+  final Span span;
+  final bool useColors;
+
+  Message(this.level, this.message, {Span span, bool useColors: false})
+      : this.span = span, this.useColors = useColors;
+
+  String toString() {
+    var output = new StringBuffer();
+    bool colors = useColors && _ERROR_COLORS.containsKey(level);
+    var levelColor =  _ERROR_COLORS[level];
+    if (colors) output.write(levelColor);
+    output..write(_ERROR_LABEL[level])..write(' ');
+    if (colors) output.write(NO_COLOR);
+
+    if (span == null) {
+      output.write(message);
+    } else {
+      output.write(span.getLocationMessage(message, useColors: colors,
+          color: levelColor));
+    }
+
+    return output.toString();
+  }
+}
+
+typedef void PrintHandler(Object obj);
+
+/**
+ * This class tracks and prints information, warnings, and errors emitted by the
+ * compiler.
+ */
+class Messages {
+  /** Called on every error. Set to blank function to supress printing. */
+  final PrintHandler printHandler;
+
+  final PreprocessorOptions options;
+
+  final List<Message> messages = <Message>[];
+
+  Messages({PreprocessorOptions options, this.printHandler: print})
+      : options = options != null ? options : new PreprocessorOptions();
+
+  /** Report a compile-time CSS error. */
+  void error(String message, Span span) {
+    var msg = new Message(Level.SEVERE, message, span: span,
+        useColors: options.useColors);
+
+    messages.add(msg);
+
+    printHandler(msg);
+  }
+
+  /** Report a compile-time CSS warning. */
+  void warning(String message, Span span) {
+    if (options.warningsAsErrors) {
+      error(message, span);
+    } else {
+      var msg = new Message(Level.WARNING, message, span: span,
+          useColors: options.useColors);
+
+      messages.add(msg);
+    }
+  }
+
+  /** Report and informational message about what the compiler is doing. */
+  void info(String message, Span span) {
+    var msg = new Message(Level.INFO, message, span: span,
+        useColors: options.useColors);
+
+    messages.add(msg);
+
+    if (options.verbose) printHandler(msg);
+  }
+
+  /** Merge [newMessages] to this message lsit. */
+  void mergeMessages(Messages newMessages) {
+    messages.addAll(newMessages.messages);
+    newMessages.messages.where((message) =>
+        message.level.value == Level.SEVERE || options.verbose)
+        .forEach((message) { printHandler(message); });
+  }
+}
diff --git a/lib/src/options.dart b/lib/src/options.dart
new file mode 100644
index 0000000..a62ef71
--- /dev/null
+++ b/lib/src/options.dart
@@ -0,0 +1,94 @@
+// 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.src.options;
+
+import 'package:args/args.dart';
+
+class PreprocessorOptions {
+  /** Report warnings as errors. */
+  final bool warningsAsErrors;
+
+  /** Throw an exception on warnings (not used by command line tool). */
+  final bool throwOnWarnings;
+
+  /** Throw an exception on errors (not used by command line tool). */
+  final bool throwOnErrors;
+
+  /** True to show informational messages. The `--verbose` flag. */
+  final bool verbose;
+
+  /** True to show warning messages for bad CSS.  The '--checked' flag. */
+  final bool checked;
+
+  // TODO(terry): Add mixin support and nested rules.
+  /**
+   * Subset of Less commands enabled; disable with '--no-less'.
+   * Less syntax supported:
+   * - @name at root level statically defines variables resolved at compilation
+   * time.  Essentially a directive e.g., @var-name.
+   */
+  final bool lessSupport;
+
+  /** Whether to use colors to print messages on the terminal. */
+  final bool useColors;
+
+  /** File to process by the compiler. */
+  String inputFile;
+
+  // We could make this faster, if it ever matters.
+  factory PreprocessorOptions() => parse(['']);
+
+  PreprocessorOptions.fromArgs(ArgResults args)
+    : warningsAsErrors = args['warnings_as_errors'],
+      throwOnWarnings = args['throw_on_warnings'],
+      throwOnErrors = args['throw_on_errors'],
+      verbose = args['verbose'],
+      checked = args['checked'],
+      lessSupport = args['less'],
+      useColors = args['colors'],
+      inputFile = args.rest.length > 0 ? args.rest[0] : null;
+
+  // tool.dart [options...] <css file>
+  static PreprocessorOptions parse(List<String> arguments) {
+    var parser = new ArgParser()
+      ..addFlag('verbose', abbr: 'v', defaultsTo: false, negatable: false,
+          help: 'Display detail info')
+      ..addFlag('checked', defaultsTo: false, negatable: false,
+          help: 'Validate CSS values invalid value display a warning message')
+      ..addFlag('less', defaultsTo: true, negatable: true,
+          help: 'Supports subset of Less syntax')
+      ..addFlag('suppress_warnings', defaultsTo: true,
+          help: 'Warnings not displayed')
+      ..addFlag('warnings_as_errors', defaultsTo: false,
+          help: 'Warning handled as errors')
+      ..addFlag('throw_on_errors', defaultsTo: false,
+          help: 'Throw on errors encountered')
+      ..addFlag('throw_on_warnings', defaultsTo: false,
+          help: 'Throw on warnings encountered')
+      ..addFlag('colors', defaultsTo: true,
+          help: 'Display errors/warnings in colored text')
+      ..addFlag('help', abbr: 'h', defaultsTo: false, negatable: false,
+          help: 'Displays this help message');
+
+    try {
+      var results = parser.parse(arguments);
+      if (results['help'] || results.rest.length == 0) {
+        showUsage(parser);
+        return null;
+      }
+      return new PreprocessorOptions.fromArgs(results);
+    } on FormatException catch (e) {
+      print(e.message);
+      showUsage(parser);
+      return null;
+    }
+  }
+
+  static showUsage(parser) {
+    print('Usage: css [options...] input.css');
+    print(parser.getUsage());
+  }
+
+}
diff --git a/lib/src/property.dart b/lib/src/property.dart
new file mode 100644
index 0000000..8dc4dcb
--- /dev/null
+++ b/lib/src/property.dart
@@ -0,0 +1,1250 @@
+// 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.
+
+/** Representations of CSS styles. */
+
+part of csslib.parser;
+
+// TODO(terry): Prune down this file we do need some of the code in this file
+//              for darker, lighter, how to represent a Font, etc but alot of
+//              the complexity can be removed.
+//              See https://github.com/dart-lang/csslib/issues/7
+
+/**
+ * Base for all style properties (e.g., Color, Font, Border, Margin, etc.)
+ */
+abstract class _StyleProperty {
+  /**
+   * Returns the expression part of a CSS declaration.  Declaration is:
+   *
+   *     property:expression;
+   *
+   * E.g., if property is color then expression could be rgba(255,255,0) the
+   *       CSS declaration would be 'color:rgba(255,255,0);'.
+   *
+   * then _cssExpression would return 'rgba(255,255,0)'.  See
+   * <http://www.w3.org/TR/CSS21/grammar.html>
+   */
+  String get cssExpression;
+}
+
+
+/**
+ * Base interface for Color, HSL and RGB.
+ */
+abstract class ColorBase {
+  /**
+   * Canonical form for color #rrggbb with alpha blending (0.0 == full
+   * transparency and 1.0 == fully opaque). If _argb length is 6 it's an
+   * rrggbb otherwise it's aarrggbb.
+   */
+  String toHexArgbString();
+
+  /**
+   * Return argb as a value (int).
+   */
+  int get argbValue;
+}
+
+
+/**
+ * General purpse Color class.  Represent a color as an ARGB value that can be
+ * converted to and from num, hex string, hsl, hsla, rgb, rgba and SVG pre-
+ * defined color constant.
+ */
+class Color implements _StyleProperty, ColorBase {
+  // If _argb length is 6 it's an rrggbb otherwise it's aarrggbb.
+  final String _argb;
+
+  // TODO(terry): Look at reducing Rgba and Hsla classes as factories for
+  //              converting from Color to an Rgba or Hsla for reading only.
+  //              Usefulness of creating an Rgba or Hsla is limited.
+
+  /**
+   * Create a color with an integer representing the rgb value of red, green,
+   * and blue.  The value 0xffffff is the color white #ffffff (CSS style).
+   * The [rgb] value of 0xffd700 would map to #ffd700 or the constant
+   * Color.gold, where ff is red intensity, d7 is green intensity, and 00 is
+   * blue intensity.
+   */
+  Color(int rgb, [num alpha]) :
+    this._argb = Color._rgbToArgbString(rgb, alpha);
+
+  /**
+   * RGB takes three values. The [red], [green], and [blue] parameters are
+   * the intensity of those components where '0' is the least and '256' is the
+   * greatest.
+   *
+   * If [alpha] is provided, it is the level of translucency which ranges from
+   * '0' (completely transparent) to '1.0' (completely opaque).  It will
+   * internally be mapped to an int between '0' and '255' like the other color
+   * components.
+   */
+  Color.createRgba(int red, int green, int blue, [num alpha]) :
+      this._argb = Color.convertToHexString(Color._clamp(red, 0, 255),
+          Color._clamp(green, 0, 255),
+          Color._clamp(blue, 0, 255),
+          alpha != null ? Color._clamp(alpha, 0, 1) : alpha);
+
+  /**
+   * Creates a new color from a CSS color string. For more information, see
+   * <https://developer.mozilla.org/en/CSS/color>.
+   */
+  Color.css(String color) :
+      this._argb = Color._convertCssToArgb(color);
+
+  // TODO(jmesserly): I found the use of percents a bit suprising.
+  /**
+   * HSL takes three values.  The [hueDegree] degree on the color wheel; '0' is
+   * the least and '100' is the greatest.  The value '0' or '360' is red, '120'
+   * is green, '240' is blue. Numbers in between reflect different shades.
+   * The [saturationPercent] percentage; where'0' is the least and '100' is the
+   * greatest (100 represents full color).  The [lightnessPercent] percentage;
+   * where'0' is the least and '100' is the greatest.  The value 0 is dark or
+   * black, 100 is light or white and 50 is a medium lightness.
+   *
+   * If [alpha] is provided, it is the level of translucency which ranges from
+   * '0' (completely transparent foreground) to '1.0' (completely opaque
+   * foreground).
+   */
+  Color.createHsla(num hueDegree, num saturationPercent, num lightnessPercent,
+      [num alpha]) :
+          this._argb = new Hsla(Color._clamp(hueDegree, 0, 360) / 360,
+          Color._clamp(saturationPercent, 0, 100) / 100,
+          Color._clamp(lightnessPercent, 0, 100) / 100,
+          alpha != null ? Color._clamp(alpha, 0, 1) : alpha).toHexArgbString();
+
+  /**
+   * The hslaRaw takes three values.  The [hue] degree on the color wheel; '0'
+   * is the least and '1' is the greatest.  The value '0' or '1' is red, the
+   * ratio of 120/360 is green, and the ratio of 240/360 is blue.  Numbers in
+   * between reflect different shades.  The [saturation] is a percentage; '0'
+   * is the least and '1' is the greatest.  The value of '1' is equivalent to
+   * 100% (full colour).  The [lightness] is a percentage; '0' is the least and
+   * '1' is the greatest.  The value of '0' is dark (black), the value of '1'
+   * is light (white), and the value of '.50' is a medium lightness.
+   *
+   * The fourth optional parameter is:
+   *   [alpha]      level of translucency range of values is 0..1, zero is a
+   *                completely transparent foreground and 1 is a completely
+   *                opaque foreground.
+   */
+  Color.hslaRaw(num hue, num saturation, num lightness, [num alpha]) :
+    this._argb = new Hsla(Color._clamp(hue, 0, 1),
+          Color._clamp(saturation, 0, 1),
+          Color._clamp(lightness, 0, 1),
+          alpha != null ? Color._clamp(alpha, 0, 1) : alpha).toHexArgbString();
+
+  /**
+   * Generate a real constant for pre-defined colors (no leading #).
+   */
+  const Color.hex(this._argb);
+
+  // TODO(jmesserly): this is needed by the example so leave it exposed for now.
+  String toString() => cssExpression;
+
+  // TODO(terry): Regardless of how color is set (rgb, num, css or hsl) we'll
+  //              always return a rgb or rgba loses fidelity when debugging in
+  //              CSS if user uses hsl and would like to edit as hsl, etc.  If
+  //              this is an issue we should keep the original value and not re-
+  //              create the CSS from the normalized value.
+  String get cssExpression {
+    if (_argb.length == 6) {
+      return "#$_argb";         // RGB only, no alpha blending.
+    } else {
+      num alpha = Color.hexToInt(_argb.substring(0, 2));
+      String a = (alpha / 255).toStringAsPrecision(2);
+      int r = Color.hexToInt(_argb.substring(2, 4));
+      int g = Color.hexToInt(_argb.substring(4, 6));
+      int b = Color.hexToInt(_argb.substring(6, 8));
+      return "rgba($r,$g,$b,$a)";
+    }
+  }
+
+  Rgba get rgba {
+    int nextIndex = 0;
+    num a;
+    if (_argb.length == 8) {
+      // Get alpha blending value 0..255
+      int alpha = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+      // Convert to value from 0..1
+      a = double.parse((alpha / 255).toStringAsPrecision(2));
+      nextIndex += 2;
+    }
+    int r = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+    nextIndex += 2;
+    int g = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+    nextIndex += 2;
+    int b = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+    return new Rgba(r, g, b, a);
+  }
+
+  Hsla get hsla => new Hsla.fromRgba(rgba);
+
+  int get argbValue => Color.hexToInt(_argb);
+
+  bool operator ==(Object other) => Color.equal(this, other);
+
+  String toHexArgbString() => _argb;
+
+  Color darker(num amount) {
+    Rgba newRgba = Color._createNewTintShadeFromRgba(rgba, -amount);
+    return new Color.hex("${newRgba.toHexArgbString()}");
+  }
+
+  Color lighter(num amount) {
+    Rgba newRgba = Color._createNewTintShadeFromRgba(rgba, amount);
+    return new Color.hex("${newRgba.toHexArgbString()}");
+  }
+
+  static bool equal(ColorBase curr, Object other) {
+    if (other is Color) {
+      Color o = other;
+      return o.toHexArgbString() == curr.toHexArgbString();
+    } else if (other is Rgba) {
+      Rgba rgb = other;
+      return rgb.toHexArgbString() == curr.toHexArgbString();
+    } else if (other is Hsla) {
+      Hsla hsla = other;
+      return hsla.toHexArgbString() == curr.toHexArgbString();
+    } else {
+      return false;
+    }
+  }
+
+  int get hashCode => _argb.hashCode;
+
+  // Conversion routines:
+
+  static String _rgbToArgbString(int rgba, num alpha) {
+    int a;
+    // If alpha is defined then adjust from 0..1 to 0..255 value, if not set
+    // then a is left as undefined and passed to convertToHexString.
+    if (alpha != null) {
+      a = (Color._clamp(alpha, 0, 1) * 255).round();
+    }
+
+    int r = (rgba & 0xff0000) >> 0x10;
+    int g = (rgba & 0xff00) >> 8;
+    int b = rgba & 0xff;
+
+    return Color.convertToHexString(r, g, b, a);
+  }
+
+  static const int _rgbCss = 1;
+  static const int _rgbaCss = 2;
+  static const int _hslCss = 3;
+  static const int _hslaCss = 4;
+  /**
+   * Parse CSS expressions of the from #rgb, rgb(r,g,b), rgba(r,g,b,a),
+   * hsl(h,s,l), hsla(h,s,l,a) and SVG colors (e.g., darkSlateblue, etc.) and
+   * convert to argb.
+   */
+  static String _convertCssToArgb(String value) {
+    // TODO(terry): Better parser/regex for converting CSS properties.
+    String color = value.trim().replaceAll("\\s", "");
+    if (color[0] == '#') {
+      String v = color.substring(1);
+      Color.hexToInt(v);              // Valid hexadecimal, throws if not.
+      return v;
+    } else if (color.length > 0 && color[color.length - 1] == ')') {
+      int type;
+      if (color.indexOf("rgb(") == 0 || color.indexOf("RGB(") == 0) {
+        color = color.substring(4);
+        type = _rgbCss;
+      } else if (color.indexOf("rgba(") == 0 || color.indexOf("RGBA(") == 0) {
+        type = _rgbaCss;
+        color = color.substring(5);
+      } else if (color.indexOf("hsl(") == 0 || color.indexOf("HSL(") == 0) {
+        type = _hslCss;
+        color = color.substring(4);
+      } else if (color.indexOf("hsla(") == 0 || color.indexOf("HSLA(") == 0) {
+        type = _hslaCss;
+        color = color.substring(5);
+      } else {
+        throw new UnsupportedError('CSS property not implemented');
+      }
+
+      color = color.substring(0, color.length - 1);     // Strip close paren.
+
+      var args = <num>[];
+      List<String> params = color.split(",");
+      for (String param in params) {
+        args.add(double.parse(param));
+      }
+      switch (type) {
+        case _rgbCss:
+          return Color.convertToHexString(args[0], args[1], args[2]);
+        case _rgbaCss:
+          return Color.convertToHexString(args[0], args[1], args[2], args[3]);
+        case _hslCss:
+          return new Hsla(args[0], args[1], args[2]).toHexArgbString();
+        case _hslaCss:
+          return new Hsla(args[0], args[1], args[2],
+              args[3]).toHexArgbString();
+        default:
+          // Type not defined UnsupportedOperationException should have thrown.
+          assert(true);
+          break;
+      }
+    }
+  }
+
+  /**
+   * [hex] hexadecimal string to convert to scalar.
+   * returns hexadecimal number as an integer.
+   * throws BadNumberFormatException if [hex] isn't a valid hexadecimal number.
+   */
+  // TODO(terry): Should be part of Dart standard library see bug
+  // <http://code.google.com/p/dart/issues/detail?id=2624>
+  static int hexToInt(String hex) {
+    int val = 0;
+
+    int len = hex.length;
+    for (int i = 0; i < len; i++) {
+      int hexDigit = hex.codeUnitAt(i);
+      if (hexDigit >= 48 && hexDigit <= 57) {
+        val += (hexDigit - 48) * (1 << (4 * (len - 1 - i)));
+      } else if (hexDigit >= 65 && hexDigit <= 70) {
+        // A..F
+        val += (hexDigit - 55) * (1 << (4 * (len - 1 - i)));
+      } else if (hexDigit >= 97 && hexDigit <= 102) {
+        // a..f
+        val += (hexDigit - 87) * (1 << (4 * (len - 1 - i)));
+      } else {
+        throw throw new FormatException("Bad hexadecimal value");
+      }
+    }
+
+    return val;
+  }
+
+  static String convertToHexString(int r, int g, int b, [num a]) {
+    String rHex = Color._numAs2DigitHex(Color._clamp(r, 0, 255));
+    String gHex = Color._numAs2DigitHex(Color._clamp(g, 0, 255));
+    String bHex = Color._numAs2DigitHex(Color._clamp(b, 0, 255));
+    String aHex = (a != null) ?
+        Color._numAs2DigitHex((Color._clamp(a, 0, 1) * 255).round()) : "";
+
+    // TODO(terry) 15.toRadixString(16) return 'F' on Dartium not f as in JS.
+    //             bug: <http://code.google.com/p/dart/issues/detail?id=2670>
+    return "$aHex$rHex$gHex$bHex".toLowerCase();
+  }
+
+  static String _numAs2DigitHex(num v) {
+    // TODO(terry): v.toInt().toRadixString instead of v.toRadixString
+    //              Bug <http://code.google.com/p/dart/issues/detail?id=2671>.
+    String hex = v.toInt().toRadixString(16);
+    if (hex.length == 1) {
+      hex = "0${hex}";
+    }
+    return hex;
+  }
+
+  static num _clamp(num value, num min, num max) =>
+      math.max(math.min(max, value), min);
+
+  /**
+   * Change the tint (make color lighter) or shade (make color darker) of all
+   * parts of [rgba] (r, g and b).  The [amount] is percentage darker between
+   * -1 to 0 for darker and 0 to 1 for lighter; '0' is no change.  The [amount]
+   * will darken or lighten the rgb values; it will not change the alpha value.
+   * If [amount] is outside of the value -1 to +1 then [amount] is changed to
+   * either the min or max direction -1 or 1.
+   *
+   * Darker will approach the color #000000 (black) and lighter will approach
+   * the color #ffffff (white).
+   */
+  static Rgba _createNewTintShadeFromRgba(Rgba rgba, num amount) {
+    int r, g, b;
+    num tintShade = Color._clamp(amount, -1, 1);
+    if (amount < 0 && rgba.r == 255 && rgba.g == 255 && rgba.b == 255) {
+      // TODO(terry): See TODO in _changeTintShadeColor; eliminate this test
+      //              by converting to HSL and adjust lightness although this
+      //              is fastest lighter/darker algorithm.
+      // Darkening white special handling.
+      r = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
+      g = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
+      b = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
+    } else {
+      // All other colors then darkening white go here.
+      r = Color._changeTintShadeColor(rgba.r, tintShade).round().toInt();
+      g = Color._changeTintShadeColor(rgba.g, tintShade).round().toInt();
+      b = Color._changeTintShadeColor(rgba.b, tintShade).round().toInt();
+    }
+    return new Rgba(r, g, b, rgba.a);
+  }
+
+  // TODO(terry): This does an okay lighter/darker; better would be convert to
+  //              HSL then change the lightness.
+  /**
+   * The parameter [v] is the color to change (r, g, or b) in the range '0' to
+   * '255'. The parameter [delta] is a number between '-1' and '1'.  A value
+   * between '-1' and '0' is darker and a value between '0' and '1' is lighter
+   * ('0' imples no change).
+   */
+  static num _changeTintShadeColor(num v, num delta) =>
+      Color._clamp(((1 - delta) * v + (delta * 255)).round(), 0, 255);
+
+  // Predefined CSS colors see <http://www.w3.org/TR/css3-color/>
+  static final Color transparent = const Color.hex("00ffffff");  // Alpha 0.0
+  static final Color aliceBlue = const Color.hex("0f08ff");
+  static final Color antiqueWhite = const Color.hex("0faebd7");
+  static final Color aqua = const Color.hex("00ffff");
+  static final Color aquaMarine = const Color.hex("7fffd4");
+  static final Color azure = const Color.hex("f0ffff");
+  static final Color beige = const Color.hex("f5f5dc");
+  static final Color bisque = const Color.hex("ffe4c4");
+  static final Color black = const Color.hex("000000");
+  static final Color blanchedAlmond = const Color.hex("ffebcd");
+  static final Color blue = const Color.hex("0000ff");
+  static final Color blueViolet = const Color.hex("8a2be2");
+  static final Color brown = const Color.hex("a52a2a");
+  static final Color burlyWood = const Color.hex("deb887");
+  static final Color cadetBlue = const Color.hex("5f9ea0");
+  static final Color chartreuse = const Color.hex("7fff00");
+  static final Color chocolate = const Color.hex("d2691e");
+  static final Color coral = const Color.hex("ff7f50");
+  static final Color cornFlowerBlue = const Color.hex("6495ed");
+  static final Color cornSilk = const Color.hex("fff8dc");
+  static final Color crimson = const Color.hex("dc143c");
+  static final Color cyan = const Color.hex("00ffff");
+  static final Color darkBlue = const Color.hex("00008b");
+  static final Color darkCyan = const Color.hex("008b8b");
+  static final Color darkGoldenRod = const Color.hex("b8860b");
+  static final Color darkGray = const Color.hex("a9a9a9");
+  static final Color darkGreen = const Color.hex("006400");
+  static final Color darkGrey = const Color.hex("a9a9a9");
+  static final Color darkKhaki = const Color.hex("bdb76b");
+  static final Color darkMagenta = const Color.hex("8b008b");
+  static final Color darkOliveGreen = const Color.hex("556b2f");
+  static final Color darkOrange = const Color.hex("ff8c00");
+  static final Color darkOrchid = const Color.hex("9932cc");
+  static final Color darkRed = const Color.hex("8b0000");
+  static final Color darkSalmon = const Color.hex("e9967a");
+  static final Color darkSeaGreen = const Color.hex("8fbc8f");
+  static final Color darkSlateBlue = const Color.hex("483d8b");
+  static final Color darkSlateGray = const Color.hex("2f4f4f");
+  static final Color darkSlateGrey = const Color.hex("2f4f4f");
+  static final Color darkTurquoise = const Color.hex("00ced1");
+  static final Color darkViolet = const Color.hex("9400d3");
+  static final Color deepPink = const Color.hex("ff1493");
+  static final Color deepSkyBlue = const Color.hex("00bfff");
+  static final Color dimGray = const Color.hex("696969");
+  static final Color dimGrey = const Color.hex("696969");
+  static final Color dodgerBlue = const Color.hex("1e90ff");
+  static final Color fireBrick = const Color.hex("b22222");
+  static final Color floralWhite = const Color.hex("fffaf0");
+  static final Color forestGreen = const Color.hex("228b22");
+  static final Color fuchsia = const Color.hex("ff00ff");
+  static final Color gainsboro = const Color.hex("dcdcdc");
+  static final Color ghostWhite = const Color.hex("f8f8ff");
+  static final Color gold = const Color.hex("ffd700");
+  static final Color goldenRod = const Color.hex("daa520");
+  static final Color gray = const Color.hex("808080");
+  static final Color green = const Color.hex("008000");
+  static final Color greenYellow = const Color.hex("adff2f");
+  static final Color grey = const Color.hex("808080");
+  static final Color honeydew = const Color.hex("f0fff0");
+  static final Color hotPink = const Color.hex("ff69b4");
+  static final Color indianRed = const Color.hex("cd5c5c");
+  static final Color indigo = const Color.hex("4b0082");
+  static final Color ivory = const Color.hex("fffff0");
+  static final Color khaki = const Color.hex("f0e68c");
+  static final Color lavender = const Color.hex("e6e6fa");
+  static final Color lavenderBlush = const Color.hex("fff0f5");
+  static final Color lawnGreen = const Color.hex("7cfc00");
+  static final Color lemonChiffon = const Color.hex("fffacd");
+  static final Color lightBlue = const Color.hex("add8e6");
+  static final Color lightCoral = const Color.hex("f08080");
+  static final Color lightCyan = const Color.hex("e0ffff");
+  static final Color lightGoldenRodYellow = const Color.hex("fafad2");
+  static final Color lightGray = const Color.hex("d3d3d3");
+  static final Color lightGreen = const Color.hex("90ee90");
+  static final Color lightGrey = const Color.hex("d3d3d3");
+  static final Color lightPink = const Color.hex("ffb6c1");
+  static final Color lightSalmon = const Color.hex("ffa07a");
+  static final Color lightSeaGreen = const Color.hex("20b2aa");
+  static final Color lightSkyBlue = const Color.hex("87cefa");
+  static final Color lightSlateGray = const Color.hex("778899");
+  static final Color lightSlateGrey = const Color.hex("778899");
+  static final Color lightSteelBlue = const Color.hex("b0c4de");
+  static final Color lightYellow = const Color.hex("ffffe0");
+  static final Color lime = const Color.hex("00ff00");
+  static final Color limeGreen = const Color.hex("32cd32");
+  static final Color linen = const Color.hex("faf0e6");
+  static final Color magenta = const Color.hex("ff00ff");
+  static final Color maroon = const Color.hex("800000");
+  static final Color mediumAquaMarine = const Color.hex("66cdaa");
+  static final Color mediumBlue = const Color.hex("0000cd");
+  static final Color mediumOrchid = const Color.hex("ba55d3");
+  static final Color mediumPurple = const Color.hex("9370db");
+  static final Color mediumSeaGreen = const Color.hex("3cb371");
+  static final Color mediumSlateBlue = const Color.hex("7b68ee");
+  static final Color mediumSpringGreen = const Color.hex("00fa9a");
+  static final Color mediumTurquoise = const Color.hex("48d1cc");
+  static final Color mediumVioletRed = const Color.hex("c71585");
+  static final Color midnightBlue = const Color.hex("191970");
+  static final Color mintCream = const Color.hex("f5fffa");
+  static final Color mistyRose = const Color.hex("ffe4e1");
+  static final Color moccasin = const Color.hex("ffe4b5");
+  static final Color navajoWhite = const Color.hex("ffdead");
+  static final Color navy = const Color.hex("000080");
+  static final Color oldLace = const Color.hex("fdf5e6");
+  static final Color olive = const Color.hex("808000");
+  static final Color oliveDrab = const Color.hex("6b8e23");
+  static final Color orange = const Color.hex("ffa500");
+  static final Color orangeRed = const Color.hex("ff4500");
+  static final Color orchid = const Color.hex("da70d6");
+  static final Color paleGoldenRod = const Color.hex("eee8aa");
+  static final Color paleGreen = const Color.hex("98fb98");
+  static final Color paleTurquoise = const Color.hex("afeeee");
+  static final Color paleVioletRed = const Color.hex("db7093");
+  static final Color papayaWhip = const Color.hex("ffefd5");
+  static final Color peachPuff = const Color.hex("ffdab9");
+  static final Color peru = const Color.hex("cd85ef");
+  static final Color pink = const Color.hex("ffc0cb");
+  static final Color plum = const Color.hex("dda0dd");
+  static final Color powderBlue = const Color.hex("b0e0e6");
+  static final Color purple = const Color.hex("800080");
+  static final Color red = const Color.hex("ff0000");
+  static final Color rosyBrown = const Color.hex("bc8f8f");
+  static final Color royalBlue = const Color.hex("4169e1");
+  static final Color saddleBrown = const Color.hex("8b4513");
+  static final Color salmon = const Color.hex("fa8072");
+  static final Color sandyBrown = const Color.hex("f4a460");
+  static final Color seaGreen = const Color.hex("2e8b57");
+  static final Color seashell = const Color.hex("fff5ee");
+  static final Color sienna = const Color.hex("a0522d");
+  static final Color silver = const Color.hex("c0c0c0");
+  static final Color skyBlue = const Color.hex("87ceeb");
+  static final Color slateBlue = const Color.hex("6a5acd");
+  static final Color slateGray = const Color.hex("708090");
+  static final Color slateGrey = const Color.hex("708090");
+  static final Color snow = const Color.hex("fffafa");
+  static final Color springGreen = const Color.hex("00ff7f");
+  static final Color steelBlue = const Color.hex("4682b4");
+  static final Color tan = const Color.hex("d2b48c");
+  static final Color teal = const Color.hex("008080");
+  static final Color thistle = const Color.hex("d8bfd8");
+  static final Color tomato = const Color.hex("ff6347");
+  static final Color turquoise = const Color.hex("40e0d0");
+  static final Color violet = const Color.hex("ee82ee");
+  static final Color wheat = const Color.hex("f5deb3");
+  static final Color white = const Color.hex("ffffff");
+  static final Color whiteSmoke = const Color.hex("f5f5f5");
+  static final Color yellow = const Color.hex("ffff00");
+  static final Color yellowGreen = const Color.hex("9acd32");
+}
+
+
+/**
+ * Rgba class for users that want to interact with a color as a RGBA value.
+ */
+class Rgba implements _StyleProperty, ColorBase {
+  // TODO(terry): Consider consolidating rgba to a single 32-bit int, make sure
+  //              it works under JS and Dart VM.
+  final int r;
+  final int g;
+  final int b;
+  final num a;
+
+  Rgba(int red, int green, int blue, [num alpha]) :
+    this.r = Color._clamp(red, 0, 255),
+    this.g = Color._clamp(green, 0, 255),
+    this.b = Color._clamp(blue, 0, 255),
+    this.a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha;
+
+  factory Rgba.fromString(String hexValue) =>
+    new Color.css("#${Color._convertCssToArgb(hexValue)}").rgba;
+
+  factory Rgba.fromColor(Color color) => color.rgba;
+
+  factory Rgba.fromArgbValue(num value) {
+    return new Rgba(((value.toInt() & 0xff000000) >> 0x18),  /* a */
+      ((value.toInt() & 0xff0000) >> 0x10),                  /* r */
+      ((value.toInt() & 0xff00) >> 8),                       /* g */
+      ((value.toInt() & 0xff)));                             /* b */
+  }
+
+  factory Rgba.fromHsla(Hsla hsla) {
+    // Convert to Rgba.
+    // See site <http://easyrgb.com/index.php?X=MATH> for good documentation
+    // and color conversion routines.
+
+    num h = hsla.hue;
+    num s = hsla.saturation;
+    num l = hsla.lightness;
+    num a = hsla.alpha;
+
+    int r;
+    int g;
+    int b;
+
+    if (s == 0) {
+      r = (l * 255).round().toInt();
+      g = r;
+      b = r;
+    } else {
+      num var2;
+
+      if (l < 0.5) {
+        var2 = l * (1 + s);
+      } else {
+        var2 = (l + s) - (s * l);
+      }
+      num var1 = 2 * l - var2;
+
+      r = (255 * Rgba._hueToRGB(var1, var2, h + (1/3))).round().toInt();
+      g = (255 * Rgba._hueToRGB(var1, var2, h)).round().toInt();
+      b = (255 * Rgba._hueToRGB(var1, var2, h - (1/3))).round().toInt();
+    }
+
+    return new Rgba(r, g, b, a);
+  }
+
+  static num _hueToRGB(num v1, num v2, num vH) {
+    if (vH < 0) {
+      vH += 1;
+    }
+
+    if (vH > 1) {
+      vH -= 1;
+    }
+
+    if ((6 * vH) < 1) {
+      return (v1 + (v2 - v1) * 6 * vH);
+    }
+
+    if ((2 * vH) < 1) {
+      return v2;
+    }
+
+    if ((3 * vH) < 2) {
+      return (v1 + (v2 - v1) * ((2 / 3 - vH) * 6));
+    }
+
+    return v1;
+  }
+
+  bool operator ==(Object other) => Color.equal(this, other);
+
+  String get cssExpression {
+    if (a == null) {
+      return "#${Color.convertToHexString(r, g, b)}";
+    } else {
+      return "rgba($r,$g,$b,$a)";
+    }
+  }
+
+  String toHexArgbString() => Color.convertToHexString(r, g, b, a);
+
+  int get argbValue {
+    int value = 0;
+    if (a != null) {
+      value = (a.toInt() << 0x18);
+    }
+    value += (r << 0x10);
+    value += (g << 0x08);
+    value += b;
+  }
+
+  Color get color => new Color.createRgba(r, g, b, a);
+  Hsla get hsla => new Hsla.fromRgba(this);
+
+  Rgba darker(num amount) => Color._createNewTintShadeFromRgba(this, -amount);
+  Rgba lighter(num amount) => Color._createNewTintShadeFromRgba(this, amount);
+
+  int get hashCode => toHexArgbString().hashCode;
+}
+
+
+/**
+ * Hsl class support to interact with a color as a hsl with hue, saturation, and
+ * lightness with optional alpha blending.  The hue is a ratio of 360 degrees
+ * 360° = 1 or 0, (1° == (1/360)), saturation and lightness is a 0..1 fraction
+ * (1 == 100%) and alpha is a 0..1 fraction.
+ */
+class Hsla implements _StyleProperty, ColorBase {
+  final num _h;             // Value from 0..1
+  final num _s;             // Value from 0..1
+  final num _l;             // Value from 0..1
+  final num _a;             // Value from 0..1
+
+  /**
+   * [hue] is a 0..1 fraction of 360 degrees (360 == 0).
+   * [saturation] is a 0..1 fraction (100% == 1).
+   * [lightness] is a 0..1 fraction (100% == 1).
+   * [alpha] is a 0..1 fraction, alpha blending between 0..1, 1 == 100% opaque.
+   */
+  Hsla(num hue, num saturation, num lightness, [num alpha]) :
+    this._h = (hue == 1) ? 0 : Color._clamp(hue, 0, 1),
+    this._s = Color._clamp(saturation, 0, 1),
+    this._l = Color._clamp(lightness, 0, 1),
+    this._a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha;
+
+  factory Hsla.fromString(String hexValue) {
+    Rgba rgba = new Color.css("#${Color._convertCssToArgb(hexValue)}").rgba;
+    return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
+  }
+
+  factory Hsla.fromColor(Color color) {
+    Rgba rgba = color.rgba;
+    return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
+  }
+
+  factory Hsla.fromArgbValue(num value) {
+    num a = (value.toInt() & 0xff000000) >> 0x18;
+    int r = (value.toInt() & 0xff0000) >> 0x10;
+    int g = (value.toInt() & 0xff00) >> 8;
+    int b = value.toInt() & 0xff;
+
+    // Convert alpha to 0..1 from (0..255).
+    if (a != null) {
+      a = double.parse((a / 255).toStringAsPrecision(2));
+    }
+
+    return _createFromRgba(r, g, b, a);
+  }
+
+  factory Hsla.fromRgba(Rgba rgba) =>
+    _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
+
+  static Hsla _createFromRgba(num r, num g, num b, num a) {
+    // Convert RGB to hsl.
+    // See site <http://easyrgb.com/index.php?X=MATH> for good documentation
+    // and color conversion routines.
+    r /= 255;
+    g /= 255;
+    b /= 255;
+
+    // Hue, saturation and lightness.
+    num h;
+    num s;
+    num l;
+
+    num minRgb = math.min(r, math.min(g, b));
+    num maxRgb = math.max(r, math.max(g, b));
+    l = (maxRgb + minRgb) / 2;
+    if (l <= 0) {
+      return new Hsla(0, 0, l);   // Black;
+    }
+
+    num vm = maxRgb - minRgb;
+    s = vm;
+    if (s > 0) {
+      s /= (l < 0.5) ? (maxRgb + minRgb) : (2 - maxRgb - minRgb);
+    } else {
+      return new Hsla(0, 0, l);   // White
+    }
+
+    num r2, g2, b2;
+    r2 = (maxRgb - r) / vm;
+    g2 = (maxRgb - g) / vm;
+    b2 = (maxRgb - b) / vm;
+    if (r == maxRgb) {
+      h = (g == minRgb) ? 5.0 + b2 : 1 - g2;
+    } else if (g == maxRgb) {
+      h = (b == minRgb) ? 1 + r2 : 3 - b2;
+    } else {
+      h = (r == minRgb) ? 3 + g2 : 5 - r2;
+    }
+    h /= 6;
+
+    return new Hsla(h, s, l, a);
+  }
+
+  /**
+   * Returns 0..1 fraction (ratio of 360°, e.g. 1° == 1/360).
+   */
+  num get hue => _h;
+
+  /**
+   * Returns 0..1 fraction (1 == 100%)
+   */
+  num get saturation => _s;
+
+  /**
+   * Returns 0..1 fraction (1 == 100%).
+   */
+  num get lightness => _l;
+
+  /**
+   * Returns number as degrees 0..360.
+   */
+  num get hueDegrees => (_h * 360).round();
+
+  /**
+   * Returns number as percentage 0..100
+   */
+  num get saturationPercentage => (_s * 100).round();
+
+  /**
+   * Returns number as percentage 0..100.
+   */
+  num get lightnessPercentage => (_l * 100).round();
+
+  /**
+   * Returns number as 0..1
+   */
+  num get alpha => _a;
+
+  bool operator ==(Object other) => Color.equal(this, other);
+
+  String get cssExpression => (_a == null) ?
+      "hsl($hueDegrees,$saturationPercentage,$lightnessPercentage)" :
+      "hsla($hueDegrees,$saturationPercentage,$lightnessPercentage,$_a)";
+
+  String toHexArgbString() => new Rgba.fromHsla(this).toHexArgbString();
+
+  int get argbValue => Color.hexToInt(this.toHexArgbString());
+
+  Color get color => new Color.createHsla(_h, _s, _l, _a);
+  Rgba get rgba => new Rgba.fromHsla(this);
+
+  Hsla darker(num amount) =>
+      new Hsla.fromRgba(new Rgba.fromHsla(this).darker(amount));
+
+  Hsla lighter(num amount) =>
+      new Hsla.fromRgba(new Rgba.fromHsla(this).lighter(amount));
+
+  int get hashCode => toHexArgbString().hashCode;
+}
+
+
+/** X,Y position. */
+class PointXY implements _StyleProperty {
+  final num x, y;
+  const PointXY(this.x, this.y);
+
+  String get cssExpression {
+    // TODO(terry): TBD
+  }
+}
+
+
+// TODO(terry): Implement style and color.
+/**
+ * Supports border for measuring with layout.
+ */
+class Border implements _StyleProperty {
+  final int top, left, bottom, right;
+
+  // TODO(terry): Just like CSS, 1-arg -> set all properties, 2-args -> top and
+  //               bottom are first arg, left and right are second, 3-args, and
+  //               4-args -> tlbr or trbl.
+  const Border([this.top, this.left, this.bottom, this.right]);
+
+  // TODO(terry): Consider using Size or width and height.
+  Border.uniform(num amount) :
+      top = amount, left = amount, bottom = amount, right = amount;
+
+  int get width => left + right;
+  int get height => top + bottom;
+
+  String get cssExpression {
+    return (top == left && bottom == right && top == right) ? "${left}px" :
+      "${top != null ? '$top' : '0'}px ${
+      right != null ? '$right' : '0'}px ${
+      bottom != null ? '$bottom' : '0'}px ${
+      left != null ? '$left' : '0'}px";
+  }
+}
+
+
+/** Font style constants. */
+class FontStyle {
+  /** Font style [normal] default. */
+  static const String normal = "normal";
+  /**
+   * Font style [italic] use explicity crafted italic font otherwise inclined
+   * on the fly like oblique.
+   */
+  static const String italic = "italic";
+  /**
+   * Font style [oblique] is rarely used. The normal style of a font is inclined
+   * on the fly to the right by 8-12 degrees.
+   */
+  static const String oblique = "oblique";
+}
+
+
+/** Font variant constants. */
+class FontVariant {
+  /** Font style [normal] default. */
+  static const String normal = "normal";
+  /** Font variant [smallCaps]. */
+  static const String smallCaps = "small-caps";
+}
+
+
+/** Font weight constants values 100, 200, 300, 400, 500, 600, 700, 800, 900. */
+class FontWeight {
+  /** Font weight normal [default] */
+  static const int normal = 400;
+  /** Font weight bold */
+  static const int bold = 700;
+
+  static const int wt100 = 100;
+  static const int wt200 = 200;
+  static const int wt300 = 300;
+  static const int wt400 = 400;
+  static const int wt500 = 500;
+  static const int wt600 = 600;
+  static const int wt700 = 700;
+  static const int wt800 = 800;
+  static const int wt900 = 900;
+}
+
+
+/** Generic font family names. */
+class FontGeneric {
+  /** Generic family sans-serif font (w/o serifs). */
+  static const String sansSerif = "sans-serif";
+  /** Generic family serif font. */
+  static const String serif = "serif";
+  /** Generic family fixed-width font. */
+  static const monospace = "monospace";
+  /** Generic family emulate handwriting font. */
+  static const String cursive = "cursive";
+  /** Generic family decorative font. */
+  static const String fantasy = "fantasy";
+}
+
+
+/**
+ * List of most common font families across different platforms.  Use the
+ * collection names in the Font class (e.g., Font.SANS_SERIF, Font.FONT_SERIF,
+ * Font.MONOSPACE, Font.CURSIVE or Font.FANTASY).  These work best on all
+ * platforms using the fonts that best match availability on each platform.
+ * See <http://www.angelfire.com/al4/rcollins/style/fonts.html> for a good
+ * description of fonts available between platforms and browsers.
+ */
+class FontFamily {
+  /** Sans-Serif font for Windows similar to Helvetica on Mac bold/italic. */
+  static const String arial = "arial";
+  /** Sans-Serif font for Windows less common already bolded. */
+  static const String arialBlack = "arial black";
+  /** Sans-Serif font for Mac since 1984, similar to Arial/Helvetica. */
+  static const String geneva = "geneva";
+  /** Sans-Serif font for Windows most readable sans-serif font for displays. */
+  static const String verdana = "verdana";
+  /** Sans-Serif font for Mac since 1984 is identical to Arial. */
+  static const String helvetica = "helvetica";
+
+  /** Serif font for Windows traditional font with “old-style” numerals. */
+  static const String georgia = "georgia";
+  /**
+   * Serif font for Mac. PCs may have the non-scalable Times use Times New
+   * Roman instead.  Times is more compact than Times New Roman.
+   */
+  static const String times = "times";
+  /**
+   * Serif font for Windows most common serif font and default serif font for
+   * most browsers.
+   */
+  static const String timesNewRoman = "times new roman";
+
+  /**
+   * Monospace font for Mac/Windows most common. Scalable on Mac not scalable
+   * on Windows.
+   */
+  static const String courier = "courier";
+  /** Monospace font for Mac/Windows scalable on both platforms. */
+  static const String courierNew = "courier new";
+
+  /** Cursive font for Windows and default cursive font for IE. */
+  static const String comicSansMs = "comic sans ms";
+  /** Cursive font for Mac on Macs 2000 and newer. */
+  static const String textile = "textile";
+  /** Cursive font for older Macs. */
+  static const String appleChancery = "apple chancery";
+  /** Cursive font for some PCs. */
+  static const String zaphChancery = "zaph chancery";
+
+  /** Fantasy font on most Mac/Windows/Linux platforms. */
+  static const String impact = "impact";
+  /** Fantasy font for Windows. */
+  static const String webdings = "webdings";
+}
+
+class LineHeight {
+  final num height;
+  final bool inPixels;
+  const LineHeight(this.height, {this.inPixels : true});
+}
+
+// TODO(terry): Support @font-face fule.
+/**
+ * Font style support for size, family, weight, style, variant, and lineheight.
+ */
+class Font implements _StyleProperty {
+  /** Collection of most common sans-serif fonts in order. */
+  static const List<String> sansSerif = const [FontFamily.arial,
+                                               FontFamily.verdana,
+                                               FontFamily.geneva,
+                                               FontFamily.helvetica,
+                                               FontGeneric.sansSerif];
+
+  /** Collection of most common serif fonts in order. */
+  static const List<String> serif = const [FontFamily.georgia,
+                                           FontFamily.timesNewRoman,
+                                           FontFamily.times,
+                                           FontGeneric.serif];
+  /** Collection of most common monospace fonts in order. */
+  static const List<String> monospace = const [FontFamily.courierNew,
+                                               FontFamily.courier,
+                                               FontGeneric.monospace];
+  /** Collection of most common cursive fonts in order. */
+  static const List<String> cursive = const [FontFamily.textile,
+                                             FontFamily.appleChancery,
+                                             FontFamily.zaphChancery,
+                                             FontGeneric.fantasy];
+  /** Collection of most common fantasy fonts in order. */
+  static const List<String> fantasy = const [FontFamily.comicSansMs,
+                                             FontFamily.impact,
+                                             FontFamily.webdings,
+                                             FontGeneric.fantasy];
+
+  // TODO(terry): Should support the values xx-small, small, large, xx-large,
+  //              etc. (mapped to a pixel sized font)?
+  /** Font size in pixels. */
+  final num size;
+
+  // TODO(terry): _family should be an immutable list, wrapper class to do this
+  //              should exist in Dart.
+  /**
+   * Family specifies a list of fonts, the browser will sequentially select the
+   * the first known/supported font.  There are two types of font families the
+   * family-name (e.g., arial, times, courier, etc) or the generic-family (e.g.,
+   * serif, sans-seric, etc.)
+   */
+  final List<String> family;
+
+  /** Font weight from 100, 200, 300, 400, 500, 600, 700, 800, 900 */
+  final int weight;
+
+  /** Style of a font normal, italic, oblique. */
+  final String style;
+
+  /**
+   * Font variant NORMAL (default) or SMALL_CAPS.  Different set of font glyph
+   * lower case letters designed to have to fit within the font-height and
+   * weight of the corresponding lowercase letters.
+   */
+  final String variant;
+
+  final LineHeight lineHeight;
+
+  // TODO(terry): Size and computedLineHeight are in pixels.  Need to figure out
+  //              how to handle in other units (specified in other units) like
+  //              points, inches, etc.  Do we have helpers like Units.Points(12)
+  //              where 12 is in points and that's converted to pixels?
+  // TODO(terry): lineHeight is computed as 1.2 although CSS_RESET is 1.0 we
+  //              need to be consistent some browsers use 1 others 1.2.
+  // TODO(terry): There is a school of thought "Golden Ratio Typography".
+  // Where width to display the text is also important in computing the line
+  // height.  Classic typography suggest the ratio be 1.5.  See
+  // <http://www.pearsonified.com/2011/12/golden-ratio-typography.php> and
+  // <http://meyerweb.com/eric/thoughts/2008/05/06/line-height-abnormal/>.
+  /**
+   * Create a font using [size] of font in pixels, [family] name of font(s)
+   * using [FontFamily], [style] of the font using [FontStyle], [variant] using
+   * [FontVariant], and [lineHeight] extra space (leading) around the font in
+   * pixels, if not specified it's 1.2 the font size.
+   */
+  const Font({this.size, this.family, this.weight, this.style, this.variant,
+              this.lineHeight});
+
+  /**
+   * Merge the two fonts and return the result. See [Style.merge] for
+   * more information.
+   */
+  factory Font.merge(Font a, Font b) {
+    if (a == null) return b;
+    if (b == null) return a;
+    return new Font._merge(a, b);
+  }
+
+  Font._merge(Font a, Font b)
+    : size = _mergeVal(a.size, b.size),
+      family = _mergeVal(a.family, b.family),
+      weight = _mergeVal(a.weight, b.weight),
+      style = _mergeVal(a.style, b.style),
+      variant = _mergeVal(a.variant, b.variant),
+      lineHeight = _mergeVal(a.lineHeight, b.lineHeight);
+
+  /**
+   * Shorthand CSS format for font is:
+   *
+   *    font-style font-variant font-weight font-size/line-height font-family
+   *
+   * The font-size and font-family values are required. If any of the other
+   * values are missing the default value is used.
+   */
+  String get cssExpression {
+    // TODO(jimhug): include variant, style, other options
+    if (weight != null) {
+      // TODO(jacobr): is this really correct for lineHeight?
+      if (lineHeight != null) {
+        return "$weight ${size}px/$lineHeightInPixels $_fontsAsString";
+      }
+      return '$weight ${size}px $_fontsAsString';
+    }
+
+    return '${size}px $_fontsAsString';
+  }
+
+  Font scale(num ratio) =>
+      new Font(size: size * ratio, family: family, weight: weight, style: style,
+          variant: variant);
+
+  /**
+   * The lineHeight, provides an indirect means to specify the leading. The
+   * leading is the difference between the font-size height and the (used)
+   * value of line height in pixels.  If lineHeight is not specified it's
+   * automatically computed as 1.2 of the font size.  Firefox is 1.2, Safari is
+   * ~1.2, and CSS suggest a ration from 1 to 1.2 of the font-size when
+   * computing line-height. The Font class constructor has the computation for
+   * _lineHeight.
+   */
+  num get lineHeightInPixels {
+    if (lineHeight != null) {
+      if (lineHeight.inPixels) {
+        return lineHeight.height;
+      } else {
+        return (size != null) ? lineHeight.height * size : null;
+      }
+    } else {
+      return (size != null) ? size * 1.2 : null;
+    }
+  }
+
+  int get hashCode {
+    // TODO(jimhug): Lot's of potential collisions here. List of fonts, etc.
+    return size.toInt() % family[0].hashCode;
+  }
+
+  bool operator ==(Object other) {
+    if (other is! Font) return false;
+    Font o = other;
+    return o.size == size && o.family == family && o.weight == weight &&
+        o.lineHeight == lineHeight && o.style == style && o.variant == variant;
+  }
+
+  // TODO(terry): This is fragile should probably just iterate through the list
+  //              of fonts construction the font-family string.
+  /** Return fonts as a comma seperated list sans the square brackets. */
+  String get _fontsAsString {
+    String fonts = family.toString();
+    return fonts.length > 2 ? fonts.substring(1, fonts.length - 1) : "";
+  }
+}
+
+/**
+ * This class stores the sizes of the box edges in the CSS [box model][]. Each
+ * edge area is placed around the sides of the content box. The innermost area
+ * is the [Style.padding] area which has a background and surrounds the content.
+ * The content and padding area is surrounded by the [Style.border], which
+ * itself is surrounded by the transparent [Style.margin]. This box represents
+ * the eges of padding, border, or margin depending on which accessor was used
+ * to retrieve it.
+ *
+ * [box model]: https://developer.mozilla.org/en/CSS/box_model
+ */
+class BoxEdge {
+  /** The size of the left edge, or null if the style has no edge. */
+  final num left;
+
+  /** The size of the top edge, or null if the style has no edge. */
+  final num top;
+
+  /** The size of the right edge, or null if the style has no edge. */
+  final num right;
+
+  /** The size of the bottom edge, or null if the style has no edge. */
+  final num bottom;
+
+  /**
+   * Creates a box edge with the specified [left], [top], [right], and
+   * [bottom] width.
+   */
+  const BoxEdge([this.left, this.top, this.right, this.bottom]);
+
+  /**
+   * Creates a box edge with the specified [top], [right], [bottom], and
+   * [left] width. This matches the typical CSS order:
+   * <https://developer.mozilla.org/en/CSS/margin>
+   * <https://developer.mozilla.org/en/CSS/border-width>
+   * <https://developer.mozilla.org/en/CSS/padding>.
+   */
+   const BoxEdge.clockwiseFromTop(this.top, this.right, this.bottom, this.left);
+
+  /**
+   * This is a helper to creates a box edge with the same [left], [top]
+   * [right], and [bottom] widths.
+   */
+  const BoxEdge.uniform(num size)
+    : top = size, left = size, bottom = size, right = size;
+
+  /**
+   * Takes a possibly null box edge, with possibly null metrics, and fills
+   * them in with 0 instead.
+   */
+  factory BoxEdge.nonNull(BoxEdge other) {
+    if (other == null) return const BoxEdge(0, 0, 0, 0);
+    num left = other.left;
+    num top = other.top;
+    num right = other.right;
+    num bottom = other.bottom;
+    bool make = false;
+    if (left == null) {
+      make = true;
+      left = 0;
+    }
+    if (top == null) {
+      make = true;
+      top = 0;
+    }
+    if (right == null) {
+      make = true;
+      right = 0;
+    }
+    if (bottom == null) {
+      make = true;
+      bottom = 0;
+    }
+    return make ? new BoxEdge(left, top, right, bottom) : other;
+  }
+
+  /**
+   * Merge the two box edge sizes and return the result. See [Style.merge] for
+   * more information.
+   */
+  factory BoxEdge.merge(BoxEdge x, BoxEdge y) {
+    if (x == null) return y;
+    if (y == null) return x;
+    return new BoxEdge._merge(x, y);
+  }
+
+  BoxEdge._merge(BoxEdge x, BoxEdge y)
+    : left = _mergeVal(x.left, y.left),
+      top = _mergeVal(x.top, y.top),
+      right = _mergeVal(x.right, y.right),
+      bottom = _mergeVal(x.bottom, y.bottom);
+
+  /**
+   * The total size of the horizontal edges. Equal to [left] + [right], where
+   * null is interpreted as 0px.
+   */
+  num get width => (left != null ? left : 0) + (right != null ? right : 0);
+
+  /**
+   * The total size of the vertical edges. Equal to [top] + [bottom], where
+   * null is interpreted as 0px.
+   */
+  num get height => (top != null ? top : 0) + (bottom != null ? bottom : 0);
+}
+
+_mergeVal(x, y) => y != null ? y : x;
diff --git a/lib/src/token.dart b/lib/src/token.dart
new file mode 100644
index 0000000..cf9e376
--- /dev/null
+++ b/lib/src/token.dart
@@ -0,0 +1,53 @@
+// 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.
+
+part of csslib.parser;
+
+/**
+ * A single token in the Dart language.
+ */
+class Token {
+  /** A member of [TokenKind] specifying what kind of token this is. */
+  final int kind;
+
+  /** The location where this token was parsed from. */
+  final Span span;
+
+  /** The start offset of this token. */
+  int get start => span.start.offset;
+
+  /** The end offset of this token. */
+  int get end => span.end.offset;
+
+  /** Returns the source text corresponding to this [Token]. */
+  String get text => span.text;
+
+  Token(this.kind, this.span);
+
+  /** Returns a pretty representation of this token for error messages. **/
+  String toString() {
+    var kindText = TokenKind.kindToString(kind);
+    var actualText = text;
+    if (kindText != actualText) {
+      if (actualText.length > 10) {
+        actualText = '${actualText.substring(0, 8)}...';
+      }
+      return '$kindText($actualText)';
+    } else {
+      return kindText;
+    }
+  }
+}
+
+/** A token containing a parsed literal value. */
+class LiteralToken extends Token {
+  var value;
+  LiteralToken(int kind, Span span, this.value) : super(kind, span);
+}
+
+/** A token containing error information. */
+class ErrorToken extends Token {
+  String message;
+  ErrorToken(int kind, Span span, this.message) : super(kind, span);
+}
diff --git a/lib/src/tokenizer.dart b/lib/src/tokenizer.dart
new file mode 100644
index 0000000..e3cdfdc
--- /dev/null
+++ b/lib/src/tokenizer.dart
@@ -0,0 +1,409 @@
+// 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.
+
+part of csslib.parser;
+
+class Tokenizer extends TokenizerBase {
+  /** U+ prefix for unicode characters. */
+  final UNICODE_U = 'U'.codeUnitAt(0);
+  final UNICODE_LOWER_U = 'u'.codeUnitAt(0);
+  final UNICODE_PLUS = '+'.codeUnitAt(0);
+
+  final QUESTION_MARK = '?'.codeUnitAt(0);
+
+  /** CDATA keyword. */
+  final List CDATA_NAME = 'CDATA'.codeUnits;
+
+  Tokenizer(SourceFile file, String text, bool skipWhitespace,
+      [int index = 0])
+      : super(file, text, skipWhitespace, index);
+
+  Token next({unicodeRange: false}) {
+    // keep track of our starting position
+    _startIndex = _index;
+
+    int ch;
+    ch = _nextChar();
+    switch (ch) {
+      case TokenChar.NEWLINE:
+      case TokenChar.RETURN:
+      case TokenChar.SPACE:
+      case TokenChar.TAB:
+        return finishWhitespace();
+      case TokenChar.END_OF_FILE:
+        return _finishToken(TokenKind.END_OF_FILE);
+      case TokenChar.AT:
+        int peekCh = _peekChar();
+        if (TokenizerHelpers.isIdentifierStart(peekCh)) {
+          var oldIndex = _index;
+          var oldStartIndex = _startIndex;
+
+          _startIndex = _index;
+          ch = _nextChar();
+          Token ident = this.finishIdentifier(ch);
+
+          // Is it a directive?
+          int tokId = TokenKind.matchDirectives(_text, _startIndex,
+              _index - _startIndex);
+          if (tokId == -1) {
+            // No, is it a margin directive?
+            tokId = TokenKind.matchMarginDirectives(_text, _startIndex,
+                _index - _startIndex);
+          }
+
+          if (tokId != -1) {
+            return _finishToken(tokId);
+          } else {
+            // Didn't find a CSS directive or margin directive so the @name is
+            // probably the Less definition '@name: value_variable_definition'.
+            _startIndex = oldStartIndex;
+            _index = oldIndex;
+          }
+        }
+        return _finishToken(TokenKind.AT);
+      case TokenChar.DOT:
+        int start = _startIndex;             // Start where the dot started.
+        if (maybeEatDigit()) {
+          // looks like a number dot followed by digit(s).
+          Token number = finishNumber();
+          if (number.kind == TokenKind.INTEGER) {
+            // It's a number but it's preceeded by a dot, so make it a double.
+            _startIndex = start;
+            return _finishToken(TokenKind.DOUBLE);
+          } else {
+            // Don't allow dot followed by a double (e.g,  '..1').
+            return _errorToken();
+          }
+        }
+        // It's really a dot.
+        return _finishToken(TokenKind.DOT);
+      case TokenChar.LPAREN:
+        return _finishToken(TokenKind.LPAREN);
+      case TokenChar.RPAREN:
+        return _finishToken(TokenKind.RPAREN);
+      case TokenChar.LBRACE:
+        return _finishToken(TokenKind.LBRACE);
+      case TokenChar.RBRACE:
+        return _finishToken(TokenKind.RBRACE);
+      case TokenChar.LBRACK:
+        return _finishToken(TokenKind.LBRACK);
+      case TokenChar.RBRACK:
+        if (_maybeEatChar(TokenChar.RBRACK) &&
+            _maybeEatChar(TokenChar.GREATER)) {
+          // ]]>
+          return next();
+        }
+        return _finishToken(TokenKind.RBRACK);
+      case TokenChar.HASH:
+        return _finishToken(TokenKind.HASH);
+      case TokenChar.PLUS:
+        if (maybeEatDigit()) return finishNumber();
+        return _finishToken(TokenKind.PLUS);
+      case TokenChar.MINUS:
+        if (selectorExpression || unicodeRange) {
+          // If parsing in pseudo function expression then minus is an operator
+          // not part of identifier e.g., interval value range (e.g. U+400-4ff)
+          // or minus operator in selector expression.
+          return _finishToken(TokenKind.MINUS);
+        } else if (maybeEatDigit()) {
+          return finishNumber();
+        } else if (TokenizerHelpers.isIdentifierStart(ch)) {
+          return this.finishIdentifier(ch);
+        }
+        return _finishToken(TokenKind.MINUS);
+      case TokenChar.GREATER:
+        return _finishToken(TokenKind.GREATER);
+      case TokenChar.TILDE:
+        if (_maybeEatChar(TokenChar.EQUALS)) {
+          return _finishToken(TokenKind.INCLUDES);          // ~=
+        }
+        return _finishToken(TokenKind.TILDE);
+      case TokenChar.ASTERISK:
+        if (_maybeEatChar(TokenChar.EQUALS)) {
+          return _finishToken(TokenKind.SUBSTRING_MATCH);   // *=
+        }
+        return _finishToken(TokenKind.ASTERISK);
+      case TokenChar.AMPERSAND:
+        return _finishToken(TokenKind.AMPERSAND);
+      case TokenChar.NAMESPACE:
+        return _finishToken(TokenKind.NAMESPACE);
+      case TokenChar.COLON:
+        return _finishToken(TokenKind.COLON);
+      case TokenChar.COMMA:
+        return _finishToken(TokenKind.COMMA);
+      case TokenChar.SEMICOLON:
+        return _finishToken(TokenKind.SEMICOLON);
+      case TokenChar.PERCENT:
+        return _finishToken(TokenKind.PERCENT);
+      case TokenChar.SINGLE_QUOTE:
+        return _finishToken(TokenKind.SINGLE_QUOTE);
+      case TokenChar.DOUBLE_QUOTE:
+        return _finishToken(TokenKind.DOUBLE_QUOTE);
+      case TokenChar.SLASH:
+        if (_maybeEatChar(TokenChar.ASTERISK)) return finishMultiLineComment();
+        return _finishToken(TokenKind.SLASH);
+      case  TokenChar.LESS:      // <!--
+        if (_maybeEatChar(TokenChar.BANG)) {
+          if (_maybeEatChar(TokenChar.MINUS) &&
+              _maybeEatChar(TokenChar.MINUS)) {
+            return finishMultiLineComment();
+          } else if (_maybeEatChar(TokenChar.LBRACK) &&
+              _maybeEatChar(CDATA_NAME[0]) &&
+              _maybeEatChar(CDATA_NAME[1]) &&
+              _maybeEatChar(CDATA_NAME[2]) &&
+              _maybeEatChar(CDATA_NAME[3]) &&
+              _maybeEatChar(CDATA_NAME[4]) &&
+              _maybeEatChar(TokenChar.LBRACK)) {
+            // <![CDATA[
+            return next();
+          }
+        }
+        return _finishToken(TokenKind.LESS);
+      case TokenChar.EQUALS:
+        return _finishToken(TokenKind.EQUALS);
+      case TokenChar.OR:
+        if (_maybeEatChar(TokenChar.EQUALS)) {
+          return _finishToken(TokenKind.DASH_MATCH);      // |=
+        }
+        return _finishToken(TokenKind.OR);
+      case TokenChar.CARET:
+        if (_maybeEatChar(TokenChar.EQUALS)) {
+          return _finishToken(TokenKind.PREFIX_MATCH);    // ^=
+        }
+        return _finishToken(TokenKind.CARET);
+      case TokenChar.DOLLAR:
+        if (_maybeEatChar(TokenChar.EQUALS)) {
+          return _finishToken(TokenKind.SUFFIX_MATCH);    // $=
+        }
+        return _finishToken(TokenKind.DOLLAR);
+      case TokenChar.BANG:
+        Token tok = finishIdentifier(ch);
+        return (tok == null) ? _finishToken(TokenKind.BANG) : tok;
+      case TokenChar.BACKSLASH:
+        return _finishToken(TokenKind.BACKSLASH);
+      default:
+        if (unicodeRange) {
+          // Three types of unicode ranges:
+          //   - single code point (e.g. U+416)
+          //   - interval value range (e.g. U+400-4ff)
+          //   - range where trailing ‘?’ characters imply ‘any digit value’
+          //   (e.g. U+4??)
+          if (maybeEatHexDigit()) {
+            var t = finishHexNumber();
+            // Any question marks then it's a HEX_RANGE not HEX_NUMBER.
+            if (maybeEatQuestionMark()) finishUnicodeRange();
+            return t;
+          } else if (maybeEatQuestionMark()) {
+            // HEX_RANGE U+N???
+            return finishUnicodeRange();
+          } else {
+            return _errorToken();
+          }
+        } else if ((ch == UNICODE_U || ch == UNICODE_LOWER_U) &&
+            (_peekChar() == UNICODE_PLUS)) {
+          // Unicode range: U+uNumber[-U+uNumber]
+          //   uNumber = 0..10FFFF
+          _nextChar();                                // Skip +
+          _startIndex = _index;                       // Starts at the number
+          return _finishToken(TokenKind.UNICODE_RANGE);
+        } else if (varDef(ch)) {
+          return _finishToken(TokenKind.VAR_DEFINITION);
+        } else if (varUsage(ch)) {
+          return _finishToken(TokenKind.VAR_USAGE);
+        } else if (TokenizerHelpers.isIdentifierStart(ch)) {
+          return finishIdentifier(ch);
+        } else if (TokenizerHelpers.isDigit(ch)) {
+          return finishNumber();
+        }
+        return _errorToken();
+    }
+  }
+
+  bool varDef(int ch) {
+    return ch == 'v'.codeUnitAt(0) && _maybeEatChar('a'.codeUnitAt(0)) &&
+        _maybeEatChar('r'.codeUnitAt(0)) && _maybeEatChar('-'.codeUnitAt(0));
+  }
+
+  bool varUsage(int ch) {
+    return ch == 'v'.codeUnitAt(0) && _maybeEatChar('a'.codeUnitAt(0)) &&
+        _maybeEatChar('r'.codeUnitAt(0)) && (_peekChar() == '-'.codeUnitAt(0));
+  }
+
+  Token _errorToken([String message = null]) {
+    return _finishToken(TokenKind.ERROR);
+  }
+
+  int getIdentifierKind() {
+    // Is the identifier a unit type?
+    int tokId = TokenKind.matchUnits(_text, _startIndex, _index - _startIndex);
+    if (tokId == -1) {
+      tokId = (_text.substring(_startIndex, _index) == '!important') ?
+          TokenKind.IMPORTANT : -1;
+    }
+
+    return tokId >= 0 ? tokId : TokenKind.IDENTIFIER;
+  }
+
+  // Need to override so CSS version of isIdentifierPart is used.
+  Token finishIdentifier(int ch) {
+    while (_index < _text.length) {
+      // If parsing in pseudo function expression then minus is an operator
+      // not part of identifier.
+      var isIdentifier = selectorExpression
+          ? TokenizerHelpers.isIdentifierPartExpr(_text.codeUnitAt(_index))
+          : TokenizerHelpers.isIdentifierPart(_text.codeUnitAt(_index));
+      if (!isIdentifier) {
+          break;
+      } else {
+        _index += 1;
+      }
+    }
+
+    int kind = getIdentifierKind();
+    if (kind == TokenKind.IDENTIFIER) {
+      return _finishToken(TokenKind.IDENTIFIER);
+    } else {
+      return _finishToken(kind);
+    }
+  }
+
+  Token finishImportant() {
+
+  }
+
+  Token finishNumber() {
+    eatDigits();
+
+    if (_peekChar() == 46/*.*/) {
+      // Handle the case of 1.toString().
+      _nextChar();
+      if (TokenizerHelpers.isDigit(_peekChar())) {
+        eatDigits();
+        return _finishToken(TokenKind.DOUBLE);
+      } else {
+        _index -= 1;
+      }
+    }
+
+    return _finishToken(TokenKind.INTEGER);
+  }
+
+  bool maybeEatDigit() {
+    if (_index < _text.length
+        && TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) {
+      _index += 1;
+      return true;
+    }
+    return false;
+  }
+
+  Token finishHexNumber() {
+    eatHexDigits();
+    return _finishToken(TokenKind.HEX_INTEGER);
+  }
+
+  void eatHexDigits() {
+    while (_index < _text.length) {
+     if (TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) {
+       _index += 1;
+     } else {
+       return;
+     }
+    }
+  }
+
+  bool maybeEatHexDigit() {
+    if (_index < _text.length
+        && TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) {
+      _index += 1;
+      return true;
+    }
+    return false;
+  }
+
+  bool maybeEatQuestionMark() {
+    if (_index < _text.length &&
+        _text.codeUnitAt(_index) == QUESTION_MARK) {
+      _index += 1;
+      return true;
+    }
+    return false;
+  }
+
+  void eatQuestionMarks() {
+    while (_index < _text.length) {
+     if (_text.codeUnitAt(_index) == QUESTION_MARK) {
+       _index += 1;
+     } else {
+       return;
+     }
+    }
+  }
+
+  Token finishUnicodeRange() {
+    eatQuestionMarks();
+    return _finishToken(TokenKind.HEX_RANGE);
+  }
+
+  Token finishMultiLineComment() {
+    while (true) {
+      int ch = _nextChar();
+      if (ch == 0) {
+        return _finishToken(TokenKind.INCOMPLETE_COMMENT);
+      } else if (ch == 42/*'*'*/) {
+        if (_maybeEatChar(47/*'/'*/)) {
+          if (_skipWhitespace) {
+            return next();
+          } else {
+            return _finishToken(TokenKind.COMMENT);
+          }
+        }
+      } else if (ch == TokenChar.MINUS) {
+        /* Check if close part of Comment Definition --> (CDC). */
+        if (_maybeEatChar(TokenChar.MINUS)) {
+          if (_maybeEatChar(TokenChar.GREATER)) {
+            if (_skipWhitespace) {
+              return next();
+            } else {
+              return _finishToken(TokenKind.HTML_COMMENT);
+            }
+          }
+        }
+      }
+    }
+    return _errorToken();
+  }
+
+}
+
+/** Static helper methods. */
+class TokenizerHelpers {
+  static bool isIdentifierStart(int c) {
+    return isIdentifierStartExpr(c) || c == 45 /*-*/;
+  }
+
+  static bool isDigit(int c) {
+    return (c >= 48/*0*/ && c <= 57/*9*/);
+  }
+
+  static bool isHexDigit(int c) {
+    return (isDigit(c) || (c >= 97/*a*/ && c <= 102/*f*/)
+        || (c >= 65/*A*/ && c <= 70/*F*/));
+  }
+
+  static bool isIdentifierPart(int c) {
+    return isIdentifierPartExpr(c) || c == 45 /*-*/;
+  }
+
+  /** Pseudo function expressions identifiers can't have a minus sign. */
+  static bool isIdentifierStartExpr(int c) {
+    return ((c >= 97/*a*/ && c <= 122/*z*/) || (c >= 65/*A*/ && c <= 90/*Z*/) ||
+        c == 95/*_*/);
+  }
+
+  /** Pseudo function expressions identifiers can't have a minus sign. */
+  static bool isIdentifierPartExpr(int c) {
+    return (isIdentifierStartExpr(c) || isDigit(c));
+  }
+}
diff --git a/lib/src/tokenizer_base.dart b/lib/src/tokenizer_base.dart
new file mode 100644
index 0000000..f33a362
--- /dev/null
+++ b/lib/src/tokenizer_base.dart
@@ -0,0 +1,445 @@
+// 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.
+// Generated by scripts/tokenizer_gen.py.
+
+part of csslib.parser;
+
+/** Tokenizer state to support look ahead for Less' nested selectors. */
+class TokenizerState {
+  final int index;
+  final int startIndex;
+  final bool selectorExpression;
+
+  TokenizerState(TokenizerBase base) :
+      this.index = base._index,
+      this.startIndex = base._startIndex,
+      this.selectorExpression = base.selectorExpression;
+}
+
+/**
+ * The base class for our tokenizer. The hand coded parts are in this file, with
+ * the generated parts in the subclass Tokenizer.
+ */
+abstract class TokenizerBase {
+  final SourceFile _file;
+  final bool _skipWhitespace;
+  final String _text;
+
+  /**
+   * Changes tokenization when in a pseudo function expression.  If true then
+   * minus signs are handled as operators instead of identifiers.
+   */
+  bool selectorExpression = false;
+
+  int _index;
+  int _startIndex;
+
+  static const String _CDATA_START = '<![CDATA[';
+  static const String _CDATA_END = ']]>';
+
+  TokenizerBase(this._file, this._text, this._skipWhitespace,
+      [this._index = 0]);
+
+  Token next();
+  int getIdentifierKind();
+
+  /** Snapshot of Tokenizer scanning state. */
+  TokenizerState get mark => new TokenizerState(this);
+
+  /** Restore Tokenizer scanning state. */
+  void restore(TokenizerState markedData) {
+    _index = markedData.index;
+    _startIndex = markedData.startIndex;
+    selectorExpression = markedData.selectorExpression;
+  }
+
+  int _nextChar() {
+    if (_index < _text.length) {
+      return _text.codeUnitAt(_index++);
+    } else {
+      return 0;
+    }
+  }
+
+  int _peekChar() {
+    if (_index < _text.length) {
+      return _text.codeUnitAt(_index);
+    } else {
+      return 0;
+    }
+  }
+
+  bool _maybeEatChar(int ch) {
+    if (_index < _text.length) {
+      if (_text.codeUnitAt(_index) == ch) {
+        _index++;
+        return true;
+      } else {
+        return false;
+      }
+    } else {
+      return false;
+    }
+  }
+
+  String _tokenText() {
+    if (_index < _text.length) {
+      return _text.substring(_startIndex, _index);
+    } else {
+      return _text.substring(_startIndex, _text.length);
+    }
+  }
+
+  Token _finishToken(int kind) {
+    return new Token(kind, _file.span(_startIndex, _index));
+  }
+
+  Token _errorToken([String message = null]) {
+    return new ErrorToken(
+        TokenKind.ERROR, _file.span(_startIndex, _index), message);
+  }
+
+  Token finishWhitespace() {
+    _index--;
+    while (_index < _text.length) {
+      final ch = _text.codeUnitAt(_index++);
+      if (ch == TokenChar.SPACE ||
+          ch == TokenChar.TAB ||
+          ch == TokenChar.RETURN) {
+        // do nothing
+      } else if (ch == TokenChar.NEWLINE) {
+        if (!_skipWhitespace) {
+          return _finishToken(TokenKind.WHITESPACE); // note the newline?
+        }
+      } else {
+        _index--;
+        if (_skipWhitespace) {
+          return next();
+        } else {
+          return _finishToken(TokenKind.WHITESPACE);
+        }
+      }
+
+    }
+    return _finishToken(TokenKind.END_OF_FILE);
+  }
+
+  Token finishSingleLineComment() {
+    while (true) {
+      int ch = _nextChar();
+      if (ch == 0 || ch == TokenChar.NEWLINE || ch == TokenChar.RETURN) {
+        if (_skipWhitespace) {
+          return next();
+        } else {
+          return _finishToken(TokenKind.COMMENT);
+        }
+      }
+    }
+  }
+
+  Token finishMultiLineComment() {
+    int nesting = 1;
+    do {
+      int ch = _nextChar();
+      if (ch == 0) {
+        return _errorToken();
+      } else if (ch == TokenChar.ASTERISK) {
+        if (_maybeEatChar(TokenChar.SLASH)) {
+          nesting--;
+        }
+      } else if (ch == TokenChar.SLASH) {
+        if (_maybeEatChar(TokenChar.ASTERISK)) {
+          nesting++;
+        }
+      }
+    } while (nesting > 0);
+
+    if (_skipWhitespace) {
+      return next();
+    } else {
+      return _finishToken(TokenKind.COMMENT);
+    }
+  }
+
+  void eatDigits() {
+    while (_index < _text.length) {
+      if (TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) {
+        _index++;
+      } else {
+        return;
+      }
+    }
+  }
+
+  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;
+    }
+  }
+
+  int readHex([int hexLength]) {
+    int maxIndex;
+    if (hexLength == null) {
+      maxIndex = _text.length - 1;
+    } else {
+      // TODO(jimhug): What if this is too long?
+      maxIndex = _index + hexLength;
+      if (maxIndex >= _text.length) return -1;
+    }
+    var result = 0;
+    while (_index < maxIndex) {
+      final digit = _hexDigit(_text.codeUnitAt(_index));
+      if (digit == -1) {
+        if (hexLength == null) {
+          return result;
+        } else {
+          return -1;
+        }
+      }
+      _hexDigit(_text.codeUnitAt(_index));
+      // Multiply by 16 rather than shift by 4 since that will result in a
+      // correct value for numbers that exceed the 32 bit precision of JS
+      // 'integers'.
+      // TODO: Figure out a better solution to integer truncation. Issue 638.
+      result = (result * 16) + digit;
+      _index++;
+    }
+
+    return result;
+  }
+
+  Token finishNumber() {
+    eatDigits();
+
+    if (_peekChar() == TokenChar.DOT) {
+      // Handle the case of 1.toString().
+      _nextChar();
+      if (TokenizerHelpers.isDigit(_peekChar())) {
+        eatDigits();
+        return finishNumberExtra(TokenKind.DOUBLE);
+      } else {
+        _index--;
+      }
+    }
+
+    return finishNumberExtra(TokenKind.INTEGER);
+  }
+
+  Token finishNumberExtra(int kind) {
+    if (_maybeEatChar(101/*e*/) || _maybeEatChar(69/*E*/)) {
+      kind = TokenKind.DOUBLE;
+      _maybeEatChar(TokenKind.MINUS);
+      _maybeEatChar(TokenKind.PLUS);
+      eatDigits();
+    }
+    if (_peekChar() != 0 && TokenizerHelpers.isIdentifierStart(_peekChar())) {
+      _nextChar();
+      return _errorToken("illegal character in number");
+    }
+
+    return _finishToken(kind);
+  }
+
+  Token _makeStringToken(List<int> buf, bool isPart) {
+    final s = new String.fromCharCodes(buf);
+    final kind = isPart ? TokenKind.STRING_PART : TokenKind.STRING;
+    return new LiteralToken(kind, _file.span(_startIndex, _index), s);
+  }
+
+  Token makeIEFilter(int start, int end) {
+    var filter = _text.substring(start, end);
+    return new LiteralToken(TokenKind.STRING, _file.span(start, end), filter);
+  }
+
+  Token _makeRawStringToken(bool isMultiline) {
+    var s;
+    if (isMultiline) {
+      // Skip initial newline in multiline strings
+      int start = _startIndex + 4;
+      if (_text[start] == '\n') start++;
+      s = _text.substring(start, _index - 3);
+    } else {
+      s = _text.substring(_startIndex + 2, _index - 1);
+    }
+    return new LiteralToken(TokenKind.STRING,
+        _file.span(_startIndex, _index), s);
+  }
+
+  Token finishMultilineString(int quote) {
+    var buf = <int>[];
+    while (true) {
+      int ch = _nextChar();
+      if (ch == 0) {
+        return _errorToken();
+      } else if (ch == quote) {
+        if (_maybeEatChar(quote)) {
+          if (_maybeEatChar(quote)) {
+            return _makeStringToken(buf, false);
+          }
+          buf.add(quote);
+        }
+        buf.add(quote);
+      } else if (ch == TokenChar.BACKSLASH) {
+        var escapeVal = readEscapeSequence();
+        if (escapeVal == -1) {
+          return _errorToken("invalid hex escape sequence");
+        } else {
+          buf.add(escapeVal);
+        }
+      } else {
+        buf.add(ch);
+      }
+    }
+  }
+
+  Token _finishOpenBrace() {
+    return _finishToken(TokenKind.LBRACE);
+  }
+
+  Token _finishCloseBrace() {
+    return _finishToken(TokenKind.RBRACE);
+  }
+
+  Token finishString(int quote) {
+    if (_maybeEatChar(quote)) {
+      if (_maybeEatChar(quote)) {
+        // skip an initial newline
+        _maybeEatChar(TokenChar.NEWLINE);
+        return finishMultilineString(quote);
+      } else {
+        return _makeStringToken(new List<int>(), false);
+      }
+    }
+    return finishStringBody(quote);
+  }
+
+  Token finishRawString(int quote) {
+    if (_maybeEatChar(quote)) {
+      if (_maybeEatChar(quote)) {
+        return finishMultilineRawString(quote);
+      } else {
+        return _makeStringToken(<int>[], false);
+      }
+    }
+    while (true) {
+      int ch = _nextChar();
+      if (ch == quote) {
+        return _makeRawStringToken(false);
+      } else if (ch == 0) {
+        return _errorToken();
+      }
+    }
+  }
+
+  Token finishMultilineRawString(int quote) {
+    while (true) {
+      int ch = _nextChar();
+      if (ch == 0) {
+        return _errorToken();
+      } else if (ch == quote && _maybeEatChar(quote) && _maybeEatChar(quote)) {
+        return _makeRawStringToken(true);
+      }
+    }
+  }
+
+  Token finishStringBody(int quote) {
+    var buf = new List<int>();
+    while (true) {
+      int ch = _nextChar();
+      if (ch == quote) {
+        return _makeStringToken(buf, false);
+      } else if (ch == 0) {
+        return _errorToken();
+      } else if (ch == TokenChar.BACKSLASH) {
+        var escapeVal = readEscapeSequence();
+        if (escapeVal == -1) {
+          return _errorToken("invalid hex escape sequence");
+        } else {
+          buf.add(escapeVal);
+        }
+      } else {
+        buf.add(ch);
+      }
+    }
+  }
+
+  int readEscapeSequence() {
+    final ch = _nextChar();
+    int hexValue;
+    switch (ch) {
+      case 110/*n*/:
+        return TokenChar.NEWLINE;
+      case 114/*r*/:
+        return TokenChar.RETURN;
+      case 102/*f*/:
+        return TokenChar.FF;
+      case 98/*b*/:
+        return TokenChar.BACKSPACE;
+      case 116/*t*/:
+        return TokenChar.TAB;
+      case 118/*v*/:
+        return TokenChar.FF;
+      case 120/*x*/:
+        hexValue = readHex(2);
+        break;
+      case 117/*u*/:
+        if (_maybeEatChar(TokenChar.LBRACE)) {
+          hexValue = readHex();
+          if (!_maybeEatChar(TokenChar.RBRACE)) {
+            return -1;
+          }
+        } else {
+          hexValue = readHex(4);
+        }
+        break;
+      default: return ch;
+    }
+
+    if (hexValue == -1) return -1;
+
+    // According to the Unicode standard the high and low surrogate halves
+    // used by UTF-16 (U+D800 through U+DFFF) and values above U+10FFFF
+    // are not legal Unicode values.
+    if (hexValue < 0xD800 || hexValue > 0xDFFF && hexValue <= 0xFFFF) {
+      return hexValue;
+    } else if (hexValue <= 0x10FFFF){
+      messages.error('unicode values greater than 2 bytes not implemented yet',
+          _file.span(_startIndex, _startIndex + 1));
+      return -1;
+    } else {
+      return -1;
+    }
+  }
+
+  Token finishDot() {
+    if (TokenizerHelpers.isDigit(_peekChar())) {
+      eatDigits();
+      return finishNumberExtra(TokenKind.DOUBLE);
+    } else {
+      return _finishToken(TokenKind.DOT);
+    }
+  }
+
+  Token finishIdentifier(int ch) {
+    while (_index < _text.length) {
+      if (!TokenizerHelpers.isIdentifierPart(_text.codeUnitAt(_index++))) {
+        _index--;
+        break;
+      }
+    }
+    int kind = getIdentifierKind();
+    if (kind == TokenKind.IDENTIFIER) {
+      return _finishToken(TokenKind.IDENTIFIER);
+    } else {
+      return _finishToken(kind);
+    }
+  }
+}
+
diff --git a/lib/src/tokenkind.dart b/lib/src/tokenkind.dart
new file mode 100644
index 0000000..ebe0615
--- /dev/null
+++ b/lib/src/tokenkind.dart
@@ -0,0 +1,756 @@
+// 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.
+
+part of csslib.parser;
+
+// TODO(terry): Need to be consistent with tokens either they're ASCII tokens
+//              e.g., ASTERISK or they're CSS e.g., PSEUDO, COMBINATOR_*.
+class TokenKind {
+  // Common shared tokens used in TokenizerBase.
+  static const int UNUSED = 0;                  // Unused place holder...
+  static const int END_OF_FILE = 1;             // EOF
+  static const int LPAREN = 2;                  // (
+  static const int RPAREN = 3;                  // )
+  static const int LBRACK = 4;                  // [
+  static const int RBRACK = 5;                  // ]
+  static const int LBRACE = 6;                  // {
+  static const int RBRACE = 7;                  // }
+  static const int DOT = 8;                     // .
+  static const int SEMICOLON = 9;               // ;
+
+  // Unique tokens for CSS.
+  static const int AT = 10;                     // @
+  static const int HASH = 11;                   // #
+  static const int PLUS = 12;                   // +
+  static const int GREATER = 13;                // >
+  static const int TILDE = 14;                  // ~
+  static const int ASTERISK = 15;               // *
+  static const int NAMESPACE = 16;              // |
+  static const int COLON = 17;                  // :
+  static const int PRIVATE_NAME = 18;           // _ prefix private class or id
+  static const int COMMA = 19;                  // ,
+  static const int SPACE = 20;
+  static const int TAB = 21;                    // /t
+  static const int NEWLINE = 22;                // /n
+  static const int RETURN = 23;                 // /r
+  static const int PERCENT = 24;                // %
+  static const int SINGLE_QUOTE = 25;           // '
+  static const int DOUBLE_QUOTE = 26;           // "
+  static const int SLASH = 27;                  // /
+  static const int EQUALS = 28;                 // =
+  static const int OR = 29;                     // |
+  static const int CARET = 30;                  // ^
+  static const int DOLLAR = 31;                 // $
+  static const int LESS = 32;                   // <
+  static const int BANG = 33;                   // !
+  static const int MINUS = 34;                  // -
+  static const int BACKSLASH = 35;              // \
+  static const int AMPERSAND = 36;              // &
+
+  // WARNING: Tokens from this point and above must have the corresponding ASCII
+  //          character in the TokenChar list at the bottom of this file.  The
+  //          order of the above tokens should be the same order as TokenChar.
+
+  /** [TokenKind] representing integer tokens. */
+  static const int INTEGER = 60;
+
+  /** [TokenKind] representing hex integer tokens. */
+  static const int HEX_INTEGER = 61;
+
+  /** [TokenKind] representing double tokens. */
+  static const int DOUBLE = 62;
+
+  /** [TokenKind] representing whitespace tokens. */
+  static const int WHITESPACE = 63;
+
+  /** [TokenKind] representing comment tokens. */
+  static const int COMMENT = 64;
+
+  /** [TokenKind] representing error tokens. */
+  static const int ERROR = 65;
+
+  /** [TokenKind] representing incomplete string tokens. */
+  static const int INCOMPLETE_STRING = 66;
+
+  /** [TokenKind] representing incomplete comment tokens. */
+  static const int INCOMPLETE_COMMENT = 67;
+
+  static const int VAR_DEFINITION = 400;        // var-NNN-NNN
+  static const int VAR_USAGE = 401;             // var(NNN-NNN [,default])
+
+  // Synthesized Tokens (no character associated with TOKEN).
+  static const int STRING = 500;
+  static const int STRING_PART = 501;
+  static const int NUMBER = 502;
+  static const int HEX_NUMBER = 503;
+  static const int HTML_COMMENT = 504;          // <!--
+  static const int IMPORTANT = 505;             // !important
+  static const int CDATA_START = 506;           // <![CDATA[
+  static const int CDATA_END = 507;             // ]]>
+  // U+uNumber[-U+uNumber]
+  // uNumber = 0..10FFFF | ?[?]*
+  static const int UNICODE_RANGE = 508;
+  static const int HEX_RANGE = 509;             // ? in the hex range
+  static const int IDENTIFIER = 511;
+
+  // Uniquely synthesized tokens for CSS.
+  static const int SELECTOR_EXPRESSION = 512;
+  static const int COMBINATOR_NONE = 513;
+  static const int COMBINATOR_DESCENDANT = 514; // Space combinator
+  static const int COMBINATOR_PLUS = 515;       // + combinator
+  static const int COMBINATOR_GREATER = 516;    // > combinator
+  static const int COMBINATOR_TILDE = 517;      // ~ combinator
+
+  static const int UNARY_OP_NONE = 518;         // No unary operator present.
+
+  // Attribute match types:
+  static const int INCLUDES = 530;              // '~='
+  static const int DASH_MATCH = 531;            // '|='
+  static const int PREFIX_MATCH = 532;          // '^='
+  static const int SUFFIX_MATCH = 533;          // '$='
+  static const int SUBSTRING_MATCH = 534;       // '*='
+  static const int NO_MATCH = 535;              // No operator.
+
+  // Unit types:
+  static const int UNIT_EM = 600;
+  static const int UNIT_EX = 601;
+  static const int UNIT_LENGTH_PX = 602;
+  static const int UNIT_LENGTH_CM = 603;
+  static const int UNIT_LENGTH_MM = 604;
+  static const int UNIT_LENGTH_IN = 605;
+  static const int UNIT_LENGTH_PT = 606;
+  static const int UNIT_LENGTH_PC = 607;
+  static const int UNIT_ANGLE_DEG = 608;
+  static const int UNIT_ANGLE_RAD = 609;
+  static const int UNIT_ANGLE_GRAD = 610;
+  static const int UNIT_ANGLE_TURN = 611;
+  static const int UNIT_TIME_MS = 612;
+  static const int UNIT_TIME_S = 613;
+  static const int UNIT_FREQ_HZ = 614;
+  static const int UNIT_FREQ_KHZ = 615;
+  static const int UNIT_PERCENT = 616;
+  static const int UNIT_FRACTION = 617;
+  static const int UNIT_RESOLUTION_DPI = 618;
+  static const int UNIT_RESOLUTION_DPCM = 619;
+  static const int UNIT_RESOLUTION_DPPX = 620;
+  static const int UNIT_CH = 621;   // Measure of "0" U+0030 glyph.
+  static const int UNIT_REM = 622;  // computed value ‘font-size’ on root elem.
+  static const int UNIT_VIEWPORT_VW = 623;
+  static const int UNIT_VIEWPORT_VH = 624;
+  static const int UNIT_VIEWPORT_VMIN = 625;
+  static const int UNIT_VIEWPORT_VMAX = 626;
+
+  // Directives (@nnnn)
+  static const int DIRECTIVE_NONE = 650;
+  static const int DIRECTIVE_IMPORT = 651;
+  static const int DIRECTIVE_MEDIA = 652;
+  static const int DIRECTIVE_PAGE = 653;
+  static const int DIRECTIVE_CHARSET = 654;
+  static const int DIRECTIVE_STYLET = 655;
+  static const int DIRECTIVE_KEYFRAMES = 656;
+  static const int DIRECTIVE_WEB_KIT_KEYFRAMES = 657;
+  static const int DIRECTIVE_MOZ_KEYFRAMES = 658;
+  static const int DIRECTIVE_MS_KEYFRAMES = 659;
+  static const int DIRECTIVE_O_KEYFRAMES = 660;
+  static const int DIRECTIVE_FONTFACE = 661;
+  static const int DIRECTIVE_NAMESPACE = 662;
+  static const int DIRECTIVE_HOST = 663;
+
+  // Media query operators
+  static const int MEDIA_OP_ONLY = 665;     // Unary.
+  static const int MEDIA_OP_NOT = 666;      // Unary.
+  static const int MEDIA_OP_AND = 667;      // Binary.
+
+  // Directives inside of a @page (margin sym).
+  static const int MARGIN_DIRECTIVE_TOPLEFTCORNER = 670;
+  static const int MARGIN_DIRECTIVE_TOPLEFT = 671;
+  static const int MARGIN_DIRECTIVE_TOPCENTER = 672;
+  static const int MARGIN_DIRECTIVE_TOPRIGHT = 673;
+  static const int MARGIN_DIRECTIVE_TOPRIGHTCORNER = 674;
+  static const int MARGIN_DIRECTIVE_BOTTOMLEFTCORNER = 675;
+  static const int MARGIN_DIRECTIVE_BOTTOMLEFT = 676;
+  static const int MARGIN_DIRECTIVE_BOTTOMCENTER = 677;
+  static const int MARGIN_DIRECTIVE_BOTTOMRIGHT = 678;
+  static const int MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER = 679;
+  static const int MARGIN_DIRECTIVE_LEFTTOP = 680;
+  static const int MARGIN_DIRECTIVE_LEFTMIDDLE = 681;
+  static const int MARGIN_DIRECTIVE_LEFTBOTTOM = 682;
+  static const int MARGIN_DIRECTIVE_RIGHTTOP = 683;
+  static const int MARGIN_DIRECTIVE_RIGHTMIDDLE = 684;
+  static const int MARGIN_DIRECTIVE_RIGHTBOTTOM = 685;
+
+  // Simple selector type.
+  static const int CLASS_NAME = 700;            // .class
+  static const int ELEMENT_NAME = 701;          // tagName
+  static const int HASH_NAME = 702;             // #elementId
+  static const int ATTRIBUTE_NAME = 703;        // [attrib]
+  static const int PSEUDO_ELEMENT_NAME = 704;   // ::pseudoElement
+  static const int PSEUDO_CLASS_NAME = 705;     // :pseudoClass
+  static const int NEGATION = 706;              // NOT
+
+  static const List<Map<int, String>> _DIRECTIVES = const [
+    const {'type': TokenKind.DIRECTIVE_IMPORT, 'value' : 'import'},
+    const {'type': TokenKind.DIRECTIVE_MEDIA, 'value' : 'media'},
+    const {'type': TokenKind.DIRECTIVE_PAGE, 'value' : 'page'},
+    const {'type': TokenKind.DIRECTIVE_CHARSET, 'value' : 'charset'},
+    const {'type': TokenKind.DIRECTIVE_STYLET, 'value' : 'stylet'},
+    const {'type': TokenKind.DIRECTIVE_KEYFRAMES, 'value' : 'keyframes'},
+    const {'type': TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES,
+        'value' : '-webkit-keyframes'},
+    const {'type': TokenKind.DIRECTIVE_MOZ_KEYFRAMES,
+          'value' : '-moz-keyframes'},
+    const {'type': TokenKind.DIRECTIVE_MS_KEYFRAMES, 'value' : '-ms-keyframes'},
+    const {'type': TokenKind.DIRECTIVE_O_KEYFRAMES, 'value' : '-o-keyframes'},
+    const {'type': TokenKind.DIRECTIVE_FONTFACE, 'value' : 'font-face'},
+    const {'type': TokenKind.DIRECTIVE_NAMESPACE, 'value' : 'namespace'},
+    const {'type': TokenKind.DIRECTIVE_HOST, 'value' : 'host'},
+  ];
+
+  static const List<Map<int, String>> MEDIA_OPERATORS = const [
+    const {'type': TokenKind.MEDIA_OP_ONLY, 'value' : 'only'},
+    const {'type': TokenKind.MEDIA_OP_NOT, 'value' : 'not'},
+    const {'type': TokenKind.MEDIA_OP_AND, 'value' : 'and'},
+];
+
+  static const List<Map<int, String>> MARGIN_DIRECTIVES = const [
+    const {'type': TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER,
+        'value' : 'top-left-corner'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_TOPLEFT,
+        'value' : 'top-left'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_TOPCENTER,
+        'value' : 'top-center'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_TOPRIGHT,
+        'value' : 'top-right'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER,
+        'value' : 'top-right-corner'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER,
+        'value' : 'bottom-left-corner'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT,
+        'value' : 'bottom-left'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMCENTER,
+        'value' : 'bottom-center'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHT,
+        'value' : 'bottom-right'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER,
+        'value' : 'bottom-right-corner'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_LEFTTOP,
+        'value' : 'left-top'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_LEFTMIDDLE,
+        'value' : 'left-middle'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_LEFTBOTTOM,
+        'value' : 'right-bottom'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTTOP,
+        'value' : 'right-top'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTMIDDLE,
+        'value' : 'right-middle'},
+    const {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM,
+        'value' : 'right-bottom'},
+  ];
+
+  static const List<Map> _UNITS = const [
+    const {'unit': TokenKind.UNIT_EM, 'value' : 'em'},
+    const {'unit': TokenKind.UNIT_EX, 'value' : 'ex'},
+    const {'unit': TokenKind.UNIT_LENGTH_PX, 'value' : 'px'},
+    const {'unit': TokenKind.UNIT_LENGTH_CM, 'value' : 'cm'},
+    const {'unit': TokenKind.UNIT_LENGTH_MM, 'value' : 'mm'},
+    const {'unit': TokenKind.UNIT_LENGTH_IN, 'value' : 'in'},
+    const {'unit': TokenKind.UNIT_LENGTH_PT, 'value' : 'pt'},
+    const {'unit': TokenKind.UNIT_LENGTH_PC, 'value' : 'pc'},
+    const {'unit': TokenKind.UNIT_ANGLE_DEG, 'value' : 'deg'},
+    const {'unit': TokenKind.UNIT_ANGLE_RAD, 'value' : 'rad'},
+    const {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value' : 'grad'},
+    const {'unit': TokenKind.UNIT_ANGLE_TURN, 'value' : 'turn'},
+    const {'unit': TokenKind.UNIT_TIME_MS, 'value' : 'ms'},
+    const {'unit': TokenKind.UNIT_TIME_S, 'value' : 's'},
+    const {'unit': TokenKind.UNIT_FREQ_HZ, 'value' : 'hz'},
+    const {'unit': TokenKind.UNIT_FREQ_KHZ, 'value' : 'khz'},
+    const {'unit': TokenKind.UNIT_FRACTION, 'value' : 'fr'},
+    const {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value' : 'dpi'},
+    const {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value' : 'dpcm'},
+    const {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value' : 'dppx'},
+    const {'unit': TokenKind.UNIT_CH, 'value' : 'ch'},
+    const {'unit': TokenKind.UNIT_REM, 'value' : 'rem'},
+    const {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value' : 'vw'},
+    const {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value' : 'vh'},
+    const {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value' : 'vmin'},
+    const {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value' : 'vmax'},
+    ];
+
+  // Some more constants:
+  static const int ASCII_UPPER_A = 65;    // ASCII value for uppercase A
+  static const int ASCII_UPPER_Z = 90;    // ASCII value for uppercase Z
+
+  // Extended color keywords:
+  static const List<Map> _EXTENDED_COLOR_NAMES = const [
+    const {'name' : 'aliceblue', 'value' : 0xF08FF},
+    const {'name' : 'antiquewhite', 'value' : 0xFAEBD7},
+    const {'name' : 'aqua', 'value' : 0x00FFFF},
+    const {'name' : 'aquamarine', 'value' : 0x7FFFD4},
+    const {'name' : 'azure', 'value' : 0xF0FFFF},
+    const {'name' : 'beige', 'value' : 0xF5F5DC},
+    const {'name' : 'bisque', 'value' : 0xFFE4C4},
+    const {'name' : 'black', 'value' : 0x000000},
+    const {'name' : 'blanchedalmond', 'value' : 0xFFEBCD},
+    const {'name' : 'blue', 'value' : 0x0000FF},
+    const {'name' : 'blueviolet', 'value' : 0x8A2BE2},
+    const {'name' : 'brown', 'value' : 0xA52A2A},
+    const {'name' : 'burlywood', 'value' : 0xDEB887},
+    const {'name' : 'cadetblue', 'value' : 0x5F9EA0},
+    const {'name' : 'chartreuse', 'value' : 0x7FFF00},
+    const {'name' : 'chocolate', 'value' : 0xD2691E},
+    const {'name' : 'coral', 'value' : 0xFF7F50},
+    const {'name' : 'cornflowerblue', 'value' : 0x6495ED},
+    const {'name' : 'cornsilk', 'value' : 0xFFF8DC},
+    const {'name' : 'crimson', 'value' : 0xDC143C},
+    const {'name' : 'cyan', 'value' : 0x00FFFF},
+    const {'name' : 'darkblue', 'value' : 0x00008B},
+    const {'name' : 'darkcyan', 'value' : 0x008B8B},
+    const {'name' : 'darkgoldenrod', 'value' : 0xB8860B},
+    const {'name' : 'darkgray', 'value' : 0xA9A9A9},
+    const {'name' : 'darkgreen', 'value' : 0x006400},
+    const {'name' : 'darkgrey', 'value' : 0xA9A9A9},
+    const {'name' : 'darkkhaki', 'value' : 0xBDB76B},
+    const {'name' : 'darkmagenta', 'value' : 0x8B008B},
+    const {'name' : 'darkolivegreen', 'value' : 0x556B2F},
+    const {'name' : 'darkorange', 'value' : 0xFF8C00},
+    const {'name' : 'darkorchid', 'value' : 0x9932CC},
+    const {'name' : 'darkred', 'value' : 0x8B0000},
+    const {'name' : 'darksalmon', 'value' : 0xE9967A},
+    const {'name' : 'darkseagreen', 'value' : 0x8FBC8F},
+    const {'name' : 'darkslateblue', 'value' : 0x483D8B},
+    const {'name' : 'darkslategray', 'value' : 0x2F4F4F},
+    const {'name' : 'darkslategrey', 'value' : 0x2F4F4F},
+    const {'name' : 'darkturquoise', 'value' : 0x00CED1},
+    const {'name' : 'darkviolet', 'value' : 0x9400D3},
+    const {'name' : 'deeppink', 'value' : 0xFF1493},
+    const {'name' : 'deepskyblue', 'value' : 0x00BFFF},
+    const {'name' : 'dimgray', 'value' : 0x696969},
+    const {'name' : 'dimgrey', 'value' : 0x696969},
+    const {'name' : 'dodgerblue', 'value' : 0x1E90FF},
+    const {'name' : 'firebrick', 'value' : 0xB22222},
+    const {'name' : 'floralwhite', 'value' : 0xFFFAF0},
+    const {'name' : 'forestgreen', 'value' : 0x228B22},
+    const {'name' : 'fuchsia', 'value' : 0xFF00FF},
+    const {'name' : 'gainsboro', 'value' : 0xDCDCDC},
+    const {'name' : 'ghostwhite', 'value' : 0xF8F8FF},
+    const {'name' : 'gold', 'value' : 0xFFD700},
+    const {'name' : 'goldenrod', 'value' : 0xDAA520},
+    const {'name' : 'gray', 'value' : 0x808080},
+    const {'name' : 'green', 'value' : 0x008000},
+    const {'name' : 'greenyellow', 'value' : 0xADFF2F},
+    const {'name' : 'grey', 'value' : 0x808080},
+    const {'name' : 'honeydew', 'value' : 0xF0FFF0},
+    const {'name' : 'hotpink', 'value' : 0xFF69B4},
+    const {'name' : 'indianred', 'value' : 0xCD5C5C},
+    const {'name' : 'indigo', 'value' : 0x4B0082},
+    const {'name' : 'ivory', 'value' : 0xFFFFF0},
+    const {'name' : 'khaki', 'value' : 0xF0E68C},
+    const {'name' : 'lavender', 'value' : 0xE6E6FA},
+    const {'name' : 'lavenderblush', 'value' : 0xFFF0F5},
+    const {'name' : 'lawngreen', 'value' : 0x7CFC00},
+    const {'name' : 'lemonchiffon', 'value' : 0xFFFACD},
+    const {'name' : 'lightblue', 'value' : 0xADD8E6},
+    const {'name' : 'lightcoral', 'value' : 0xF08080},
+    const {'name' : 'lightcyan', 'value' : 0xE0FFFF},
+    const {'name' : 'lightgoldenrodyellow', 'value' : 0xFAFAD2},
+    const {'name' : 'lightgray', 'value' : 0xD3D3D3},
+    const {'name' : 'lightgreen', 'value' : 0x90EE90},
+    const {'name' : 'lightgrey', 'value' : 0xD3D3D3},
+    const {'name' : 'lightpink', 'value' : 0xFFB6C1},
+    const {'name' : 'lightsalmon', 'value' : 0xFFA07A},
+    const {'name' : 'lightseagreen', 'value' : 0x20B2AA},
+    const {'name' : 'lightskyblue', 'value' : 0x87CEFA},
+    const {'name' : 'lightslategray', 'value' : 0x778899},
+    const {'name' : 'lightslategrey', 'value' : 0x778899},
+    const {'name' : 'lightsteelblue', 'value' : 0xB0C4DE},
+    const {'name' : 'lightyellow', 'value' : 0xFFFFE0},
+    const {'name' : 'lime', 'value' : 0x00FF00},
+    const {'name' : 'limegreen', 'value' : 0x32CD32},
+    const {'name' : 'linen', 'value' : 0xFAF0E6},
+    const {'name' : 'magenta', 'value' : 0xFF00FF},
+    const {'name' : 'maroon', 'value' : 0x800000},
+    const {'name' : 'mediumaquamarine', 'value' : 0x66CDAA},
+    const {'name' : 'mediumblue', 'value' : 0x0000CD},
+    const {'name' : 'mediumorchid', 'value' : 0xBA55D3},
+    const {'name' : 'mediumpurple', 'value' : 0x9370DB},
+    const {'name' : 'mediumseagreen', 'value' : 0x3CB371},
+    const {'name' : 'mediumslateblue', 'value' : 0x7B68EE},
+    const {'name' : 'mediumspringgreen', 'value' : 0x00FA9A},
+    const {'name' : 'mediumturquoise', 'value' : 0x48D1CC},
+    const {'name' : 'mediumvioletred', 'value' : 0xC71585},
+    const {'name' : 'midnightblue', 'value' : 0x191970},
+    const {'name' : 'mintcream', 'value' : 0xF5FFFA},
+    const {'name' : 'mistyrose', 'value' : 0xFFE4E1},
+    const {'name' : 'moccasin', 'value' : 0xFFE4B5},
+    const {'name' : 'navajowhite', 'value' : 0xFFDEAD},
+    const {'name' : 'navy', 'value' : 0x000080},
+    const {'name' : 'oldlace', 'value' : 0xFDF5E6},
+    const {'name' : 'olive', 'value' : 0x808000},
+    const {'name' : 'olivedrab', 'value' : 0x6B8E23},
+    const {'name' : 'orange', 'value' : 0xFFA500},
+    const {'name' : 'orangered', 'value' : 0xFF4500},
+    const {'name' : 'orchid', 'value' : 0xDA70D6},
+    const {'name' : 'palegoldenrod', 'value' : 0xEEE8AA},
+    const {'name' : 'palegreen', 'value' : 0x98FB98},
+    const {'name' : 'paleturquoise', 'value' : 0xAFEEEE},
+    const {'name' : 'palevioletred', 'value' : 0xDB7093},
+    const {'name' : 'papayawhip', 'value' : 0xFFEFD5},
+    const {'name' : 'peachpuff', 'value' : 0xFFDAB9},
+    const {'name' : 'peru', 'value' : 0xCD853F},
+    const {'name' : 'pink', 'value' : 0xFFC0CB},
+    const {'name' : 'plum', 'value' : 0xDDA0DD},
+    const {'name' : 'powderblue', 'value' : 0xB0E0E6},
+    const {'name' : 'purple', 'value' : 0x800080},
+    const {'name' : 'red', 'value' : 0xFF0000},
+    const {'name' : 'rosybrown', 'value' : 0xBC8F8F},
+    const {'name' : 'royalblue', 'value' : 0x4169E1},
+    const {'name' : 'saddlebrown', 'value' : 0x8B4513},
+    const {'name' : 'salmon', 'value' : 0xFA8072},
+    const {'name' : 'sandybrown', 'value' : 0xF4A460},
+    const {'name' : 'seagreen', 'value' : 0x2E8B57},
+    const {'name' : 'seashell', 'value' : 0xFFF5EE},
+    const {'name' : 'sienna', 'value' : 0xA0522D},
+    const {'name' : 'silver', 'value' : 0xC0C0C0},
+    const {'name' : 'skyblue', 'value' : 0x87CEEB},
+    const {'name' : 'slateblue', 'value' : 0x6A5ACD},
+    const {'name' : 'slategray', 'value' : 0x708090},
+    const {'name' : 'slategrey', 'value' : 0x708090},
+    const {'name' : 'snow', 'value' : 0xFFFAFA},
+    const {'name' : 'springgreen', 'value' : 0x00FF7F},
+    const {'name' : 'steelblue', 'value' : 0x4682B4},
+    const {'name' : 'tan', 'value' : 0xD2B48C},
+    const {'name' : 'teal', 'value' : 0x008080},
+    const {'name' : 'thistle', 'value' : 0xD8BFD8},
+    const {'name' : 'tomato', 'value' : 0xFF6347},
+    const {'name' : 'turquoise', 'value' : 0x40E0D0},
+    const {'name' : 'violet', 'value' : 0xEE82EE},
+    const {'name' : 'wheat', 'value' : 0xF5DEB3},
+    const {'name' : 'white', 'value' : 0xFFFFFF},
+    const {'name' : 'whitesmoke', 'value' : 0xF5F5F5},
+    const {'name' : 'yellow', 'value' : 0xFFFF00},
+    const {'name' : 'yellowgreen', 'value' : 0x9ACD32},
+  ];
+
+  // TODO(terry): Should used Dart mirroring for parameter values and types
+  //              especially for enumeration (e.g., counter's second parameter
+  //              is list-style-type which is an enumerated list for ordering
+  //              of a list 'circle', 'decimal', 'lower-roman', 'square', etc.
+  //              see http://www.w3schools.com/cssref/pr_list-style-type.asp
+  //              for list of possible values.
+
+  // List of valid CSS functions:
+  static const List<Map<String, Object>> _FUNCTIONS = const [
+    const {'name' : 'counter', 'info' : const {'params' : 2, 'expr' : false}},
+    const {'name' : 'attr', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'calc', 'info' : const {'params' : 1, 'expr' : true}},
+    const {'name' : 'min', 'info' : const {'params' : 2, 'expr' : true}},
+    const {'name' : 'max', 'info' : const {'params' : 2, 'expr' : true}},
+
+    // 2D functions:
+    const {'name' : 'translateX',
+        'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'translateY',
+        'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'translate', 'info' : const {'params' : 2, 'expr' : false}},
+    const {'name' : 'rotate', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'scaleX', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'scaleY', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'scale', 'info' : const {'params' : 2, 'expr' : false}},
+    const {'name' : 'skewX', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'skewY', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'skew', 'info' : const {'params' : 2, 'expr' : false}},
+    const {'name' : 'matrix', 'info' : const {'params' : 6, 'expr' : false}},
+
+    // 3D functions:
+    const {'name' : 'matrix3d', 'info' : const {'params' : 16, 'expr' : false}},
+    const {'name' : 'translate3d',
+        'info' : const {'params' : 3, 'expr' : false}},
+    const {'name' : 'translateZ',
+        'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'scale3d', 'info' : const {'params' : 3, 'expr' : false}},
+    const {'name' : 'scaleZ', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'rotate3d', 'info' : const {'params' : 3, 'expr' : false}},
+    const {'name' : 'rotateX', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'rotateY', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'rotateZ', 'info' : const {'params' : 1, 'expr' : false}},
+    const {'name' : 'perspective',
+        'info' : const {'params' : 1, 'expr' : false}},
+  ];
+
+  /**
+   * Check if name is a pre-defined CSS name.  Used by error handler to report
+   * if name is unknown or used improperly.
+   */
+  static bool isPredefinedName(String name) {
+    var nameLen = name.length;
+    // TODO(terry): Add more pre-defined names (hidden, bolder, inherit, etc.).
+    if (matchUnits(name, 0, nameLen) == -1 ||
+        matchDirectives(name, 0, nameLen) == -1 ||
+        matchMarginDirectives(name, 0, nameLen) == -1 ||
+        matchColorName(name) == null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /** Return the token that matches the unit ident found. */
+  static int matchList(var identList, String tokenField, String text,
+                       int offset, int length) {
+    for (final entry in identList) {
+      String ident = entry['value'];
+
+      if (length == ident.length) {
+        int idx = offset;
+        bool match = true;
+        for (int i = 0; i < ident.length; i++) {
+          int identChar = ident.codeUnitAt(i);
+          int char = text.codeUnitAt(idx++);
+          // Compare lowercase to lowercase then check if char is uppercase.
+          match = match && (char == identChar ||
+              ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) &&
+               (char + 32) == identChar));
+          if (!match) {
+            break;
+          }
+        }
+
+        if (match) {
+          // Completely matched; return the token for this unit.
+          return entry[tokenField];
+        }
+      }
+    }
+
+    return -1;  // Not a unit token.
+  }
+
+  /** Return the token that matches the unit ident found. */
+  static int matchUnits(String text, int offset, int length) {
+    return matchList(_UNITS, 'unit', text, offset, length);
+  }
+
+  /** Return the token that matches the directive name found. */
+  static int matchDirectives(String text, int offset, int length) {
+    return matchList(_DIRECTIVES, 'type', text, offset, length);
+  }
+
+  /** Return the token that matches the margin directive name found. */
+  static int matchMarginDirectives(String text, int offset, int length) {
+    return matchList(MARGIN_DIRECTIVES, 'type', text, offset, length);
+  }
+
+  /** Return the token that matches the media operator found. */
+  static int matchMediaOperator(String text, int offset, int length) {
+    return matchList(MEDIA_OPERATORS, 'type', text, offset, length);
+  }
+
+  static String idToValue(var identList, int tokenId) {
+    for (var entry in identList) {
+      if (tokenId == entry['type']) {
+        return entry['value'];
+      }
+    }
+
+    return null;
+  }
+
+
+  /** Return the unit token as its pretty name. */
+  static String unitToString(int unitTokenToFind) {
+    if (unitTokenToFind == TokenKind.PERCENT) {
+      return '%';
+    } else {
+      for (final entry in _UNITS) {
+        int unit = entry['unit'];
+        if (unit == unitTokenToFind) {
+          return entry['value'];
+        }
+      }
+    }
+
+    return '<BAD UNIT>';  // Not a unit token.
+  }
+
+  /**
+   * Match color name, case insensitive match and return the associated color
+   * entry from _EXTENDED_COLOR_NAMES list, return [null] if not found.
+   */
+  static Map matchColorName(String text) {
+    var name = text.toLowerCase();
+    return _EXTENDED_COLOR_NAMES.
+        firstWhere((e) => e['name'] == name, orElse: () => null);
+  }
+
+  /** Return RGB value as [int] from a color entry in _EXTENDED_COLOR_NAMES. */
+  static int colorValue(Map entry) {
+    assert(entry != null);
+    return entry['value'];
+  }
+
+  static String hexToColorName(hexValue) {
+    for (final entry in _EXTENDED_COLOR_NAMES) {
+      if (entry['value'] == hexValue) {
+        return entry['name'];
+      }
+    }
+
+    return null;
+  }
+
+  static String decimalToHex(int number, [int minDigits = 1]) {
+    final String _HEX_DIGITS = '0123456789abcdef';
+
+    List<String> result = new List<String>();
+
+    int dividend = number >> 4;
+    int remain = number % 16;
+    result.add(_HEX_DIGITS[remain]);
+    while (dividend != 0) {
+      remain = dividend % 16;
+      dividend >>= 4;
+      result.add(_HEX_DIGITS[remain]);
+    }
+
+    StringBuffer invertResult = new StringBuffer();
+    int paddings = minDigits - result.length;
+    while (paddings-- > 0) {
+      invertResult.write('0');
+    }
+    for (int i = result.length - 1; i >= 0; i--) {
+      invertResult.write(result[i]);
+    }
+
+    return invertResult.toString();
+  }
+
+  static String kindToString(int kind) {
+    switch(kind) {
+      case TokenKind.UNUSED: return "ERROR";
+      case TokenKind.END_OF_FILE: return "end of file";
+      case TokenKind.LPAREN: return "(";
+      case TokenKind.RPAREN: return ")";
+      case TokenKind.LBRACK: return "[";
+      case TokenKind.RBRACK: return "]";
+      case TokenKind.LBRACE: return "{";
+      case TokenKind.RBRACE: return "}";
+      case TokenKind.DOT: return ".";
+      case TokenKind.SEMICOLON: return ";";
+      case TokenKind.AT: return "@";
+      case TokenKind.HASH: return "#";
+      case TokenKind.PLUS: return "+";
+      case TokenKind.GREATER: return ">";
+      case TokenKind.TILDE: return "~";
+      case TokenKind.ASTERISK: return "*";
+      case TokenKind.NAMESPACE: return "|";
+      case TokenKind.COLON: return ":";
+      case TokenKind.PRIVATE_NAME: return "_";
+      case TokenKind.COMMA: return ",";
+      case TokenKind.SPACE: return " ";
+      case TokenKind.TAB: return "\t";
+      case TokenKind.NEWLINE: return "\n";
+      case TokenKind.RETURN: return "\r";
+      case TokenKind.PERCENT: return "%";
+      case TokenKind.SINGLE_QUOTE: return "'";
+      case TokenKind.DOUBLE_QUOTE: return "\"";
+      case TokenKind.SLASH: return "/";
+      case TokenKind.EQUALS: return '=';
+      case TokenKind.OR: return '|';
+      case TokenKind.CARET: return '^';
+      case TokenKind.DOLLAR: return '\$';
+      case TokenKind.LESS: return '<';
+      case TokenKind.BANG: return '!';
+      case TokenKind.MINUS: return '-';
+      case TokenKind.BACKSLASH: return '\\';
+      default:
+        throw "Unknown TOKEN";
+    }
+  }
+
+  static bool isKindIdentifier(int kind) {
+    switch(kind) {
+      // Synthesized tokens.
+      case TokenKind.DIRECTIVE_IMPORT:
+      case TokenKind.DIRECTIVE_MEDIA:
+      case TokenKind.DIRECTIVE_PAGE:
+      case TokenKind.DIRECTIVE_CHARSET:
+      case TokenKind.DIRECTIVE_STYLET:
+      case TokenKind.DIRECTIVE_KEYFRAMES:
+      case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES:
+      case TokenKind.DIRECTIVE_MOZ_KEYFRAMES:
+      case TokenKind.DIRECTIVE_MS_KEYFRAMES:
+      case TokenKind.DIRECTIVE_O_KEYFRAMES:
+      case TokenKind.DIRECTIVE_FONTFACE:
+      case TokenKind.DIRECTIVE_NAMESPACE:
+      case TokenKind.DIRECTIVE_HOST:
+      case TokenKind.UNIT_EM:
+      case TokenKind.UNIT_EX:
+      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:
+      case TokenKind.UNIT_ANGLE_DEG:
+      case TokenKind.UNIT_ANGLE_RAD:
+      case TokenKind.UNIT_ANGLE_GRAD:
+      case TokenKind.UNIT_TIME_MS:
+      case TokenKind.UNIT_TIME_S:
+      case TokenKind.UNIT_FREQ_HZ:
+      case TokenKind.UNIT_FREQ_KHZ:
+      case TokenKind.UNIT_FRACTION:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  static bool isIdentifier(int kind) {
+    return kind == IDENTIFIER ;
+  }
+}
+
+// Note: these names should match TokenKind names
+class TokenChar {
+  static const int UNUSED = -1;
+  static const int END_OF_FILE = 0;
+  static const int LPAREN = 0x28; // "(".codeUnitAt(0)
+  static const int RPAREN = 0x29; // ")".codeUnitAt(0)
+  static const int LBRACK = 0x5b; // "[".codeUnitAt(0)
+  static const int RBRACK = 0x5d; // "]".codeUnitAt(0)
+  static const int LBRACE = 0x7b; // "{".codeUnitAt(0)
+  static const int RBRACE = 0x7d; // "}".codeUnitAt(0)
+  static const int DOT = 0x2e; // ".".codeUnitAt(0)
+  static const int SEMICOLON = 0x3b; // ";".codeUnitAt(0)
+  static const int AT = 0x40; // "@".codeUnitAt(0)
+  static const int HASH = 0x23; // "#".codeUnitAt(0)
+  static const int PLUS = 0x2b; // "+".codeUnitAt(0)
+  static const int GREATER = 0x3e; // ">".codeUnitAt(0)
+  static const int TILDE = 0x7e; // "~".codeUnitAt(0)
+  static const int ASTERISK = 0x2a; // "*".codeUnitAt(0)
+  static const int NAMESPACE = 0x7c; // "|".codeUnitAt(0)
+  static const int COLON = 0x3a; // ":".codeUnitAt(0)
+  static const int PRIVATE_NAME = 0x5f; // "_".codeUnitAt(0)
+  static const int COMMA = 0x2c; // ",".codeUnitAt(0)
+  static const int SPACE = 0x20; // " ".codeUnitAt(0)
+  static const int TAB = 0x9; // "\t".codeUnitAt(0)
+  static const int NEWLINE = 0xa; // "\n".codeUnitAt(0)
+  static const int RETURN = 0xd; // "\r".codeUnitAt(0)
+  static const int BACKSPACE = 0x8; // "/b".codeUnitAt(0)
+  static const int FF = 0xc; // "/f".codeUnitAt(0)
+  static const int VT = 0xb; // "/v".codeUnitAt(0)
+  static const int PERCENT = 0x25; // "%".codeUnitAt(0)
+  static const int SINGLE_QUOTE = 0x27; // "'".codeUnitAt(0)
+  static const int DOUBLE_QUOTE = 0x22; // '"'.codeUnitAt(0)
+  static const int SLASH = 0x2f; // "/".codeUnitAt(0)
+  static const int EQUALS = 0x3d; // "=".codeUnitAt(0)
+  static const int OR = 0x7c; // "|".codeUnitAt(0)
+  static const int CARET = 0x5e; // "^".codeUnitAt(0)
+  static const int DOLLAR = 0x24; // "\$".codeUnitAt(0)
+  static const int LESS = 0x3c; // "<".codeUnitAt(0)
+  static const int BANG = 0x21; // "!".codeUnitAt(0)
+  static const int MINUS = 0x2d; // "-".codeUnitAt(0)
+  static const int BACKSLASH = 0x5c; // "\".codeUnitAt(0)
+  static const int AMPERSAND = 0x26; // "&".codeUnitAt(0)
+}
diff --git a/lib/src/tree.dart b/lib/src/tree.dart
new file mode 100644
index 0000000..e8411d3
--- /dev/null
+++ b/lib/src/tree.dart
@@ -0,0 +1,1054 @@
+// 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.
+
+part of csslib.visitor;
+
+/////////////////////////////////////////////////////////////////////////
+// CSS specific types:
+/////////////////////////////////////////////////////////////////////////
+
+class Identifier extends TreeNode {
+  String name;
+
+  Identifier(this.name, Span span): super(span);
+
+  visit(VisitorBase visitor) => visitor.visitIdentifier(this);
+
+  String toString() => name;
+}
+
+class Wildcard extends TreeNode {
+  Wildcard(Span span): super(span);
+  visit(VisitorBase visitor) => visitor.visitWildcard(this);
+}
+
+class ThisOperator extends TreeNode {
+  ThisOperator(Span span): super(span);
+  visit(VisitorBase visitor) => visitor.visitThisOperator(this);
+}
+
+class Negation extends TreeNode {
+  Negation(Span span): super(span);
+  visit(VisitorBase visitor) => visitor.visitNegation(this);
+}
+
+// /*  ....   */
+class CssComment extends TreeNode {
+  final String comment;
+
+  CssComment(this.comment, Span span): super(span);
+  visit(VisitorBase visitor) => visitor.visitCssComment(this);
+}
+
+// CDO/CDC (Comment Definition Open <!-- and Comment Definition Close -->).
+class CommentDefinition extends CssComment {
+  CommentDefinition(String comment, Span span): super(comment, span);
+  visit(VisitorBase visitor) => visitor.visitCommentDefinition(this);
+}
+
+class SelectorGroup extends TreeNode {
+  List<Selector> _selectors;
+
+  SelectorGroup(this._selectors, Span span): super(span);
+
+  List<Selector> get selectors => _selectors;
+
+  visit(VisitorBase visitor) => visitor.visitSelectorGroup(this);
+}
+
+class Selector extends TreeNode {
+  final List<SimpleSelectorSequence> _simpleSelectorSequences;
+
+  Selector(this._simpleSelectorSequences, Span span) : super(span);
+
+  List<SimpleSelectorSequence> get simpleSelectorSequences =>
+      _simpleSelectorSequences;
+
+  add(SimpleSelectorSequence seq) => _simpleSelectorSequences.add(seq);
+
+  int get length => _simpleSelectorSequences.length;
+
+  visit(VisitorBase visitor) => visitor.visitSelector(this);
+}
+
+class SimpleSelectorSequence extends TreeNode {
+  /** +, >, ~, NONE */
+  final int _combinator;
+  final SimpleSelector _selector;
+
+  SimpleSelectorSequence(this._selector, Span span,
+      [int combinator = TokenKind.COMBINATOR_NONE])
+      : _combinator = combinator, super(span);
+
+  get simpleSelector => _selector;
+
+  bool get isCombinatorNone => _combinator == TokenKind.COMBINATOR_NONE;
+  bool get isCombinatorPlus => _combinator == TokenKind.COMBINATOR_PLUS;
+  bool get isCombinatorGreater => _combinator == TokenKind.COMBINATOR_GREATER;
+  bool get isCombinatorTilde => _combinator == TokenKind.COMBINATOR_TILDE;
+  bool get isCombinatorDescendant =>
+      _combinator == TokenKind.COMBINATOR_DESCENDANT;
+
+  String get _combinatorToString =>
+      isCombinatorDescendant ? ' ' :
+          isCombinatorPlus ? ' + ' :
+              isCombinatorGreater ? ' > ' :
+                  isCombinatorTilde ? ' ~ ' : '';
+
+  visit(VisitorBase visitor) => visitor.visitSimpleSelectorSequence(this);
+}
+
+/* All other selectors (element, #id, .class, attribute, pseudo, negation,
+ * namespace, *) are derived from this selector.
+ */
+class SimpleSelector extends TreeNode {
+  final _name;
+
+  SimpleSelector(this._name, Span span) : super(span);
+
+  // Name can be an Identifier or WildCard we'll return either the name or '*'.
+  String get name => isWildcard ? '*' : isThis ? '&' : _name.name;
+
+  bool get isWildcard => _name is Wildcard;
+
+  bool get isThis => _name is ThisOperator;
+
+  visit(VisitorBase visitor) => visitor.visitSimpleSelector(this);
+}
+
+// element name
+class ElementSelector extends SimpleSelector {
+  ElementSelector(name, Span span) : super(name, span);
+  visit(VisitorBase visitor) => visitor.visitElementSelector(this);
+}
+
+// namespace|element
+class NamespaceSelector extends SimpleSelector {
+  final _namespace;           // null, Wildcard or Identifier
+
+  NamespaceSelector(this._namespace, var name, Span span) : super(name, span);
+
+  String get namespace =>
+      _namespace is Wildcard ? '*' : _namespace == null ? '' : _namespace.name;
+
+  bool get isNamespaceWildcard => _namespace is Wildcard;
+
+  SimpleSelector get nameAsSimpleSelector => _name;
+
+  visit(VisitorBase visitor) => visitor.visitNamespaceSelector(this);
+}
+
+// [attr op value]
+class AttributeSelector extends SimpleSelector {
+  final int _op;
+  final _value;
+
+  AttributeSelector(Identifier name, this._op, this._value,
+      Span span) : super(name, span);
+
+  String matchOperator() {
+    switch (_op) {
+    case TokenKind.EQUALS:
+      return '=';
+    case TokenKind.INCLUDES:
+      return '~=';
+    case TokenKind.DASH_MATCH:
+      return '|=';
+    case TokenKind.PREFIX_MATCH:
+      return '^=';
+    case TokenKind.SUFFIX_MATCH:
+      return '\$=';
+    case TokenKind.SUBSTRING_MATCH:
+      return '*=';
+    case TokenKind.NO_MATCH:
+      return '';
+    }
+  }
+
+  // Return the TokenKind for operator used by visitAttributeSelector.
+  String matchOperatorAsTokenString() {
+    switch (_op) {
+    case TokenKind.EQUALS:
+      return 'EQUALS';
+    case TokenKind.INCLUDES:
+      return 'INCLUDES';
+    case TokenKind.DASH_MATCH:
+      return 'DASH_MATCH';
+    case TokenKind.PREFIX_MATCH:
+      return 'PREFIX_MATCH';
+    case TokenKind.SUFFIX_MATCH:
+      return 'SUFFIX_MATCH';
+    case TokenKind.SUBSTRING_MATCH:
+      return 'SUBSTRING_MATCH';
+    }
+  }
+
+  String valueToString() {
+    if (_value != null) {
+      if (_value is Identifier) {
+        return _value.name;
+      } else {
+        return '"${_value}"';
+      }
+    } else {
+      return '';
+    }
+  }
+
+  visit(VisitorBase visitor) => visitor.visitAttributeSelector(this);
+}
+
+// #id
+class IdSelector extends SimpleSelector {
+  IdSelector(Identifier name, Span span) : super(name, span);
+  visit(VisitorBase visitor) => visitor.visitIdSelector(this);
+}
+
+// .class
+class ClassSelector extends SimpleSelector {
+  ClassSelector(Identifier name, Span span) : super(name, span);
+  visit(VisitorBase visitor) => visitor.visitClassSelector(this);
+}
+
+// :pseudoClass
+class PseudoClassSelector extends SimpleSelector {
+  PseudoClassSelector(Identifier name, Span span) : super(name, span);
+  visit(VisitorBase visitor) => visitor.visitPseudoClassSelector(this);
+}
+
+// ::pseudoElement
+class PseudoElementSelector extends SimpleSelector {
+  PseudoElementSelector(Identifier name, Span span) : super(name, span);
+  visit(VisitorBase visitor) => visitor.visitPseudoElementSelector(this);
+}
+
+// :pseudoClassFunction(expression)
+class PseudoClassFunctionSelector extends PseudoClassSelector {
+  SelectorExpression expression;
+
+  PseudoClassFunctionSelector(Identifier name, this.expression, Span span)
+      : super(name, span);
+  visit(VisitorBase visitor) => visitor.visitPseudoClassFunctionSelector(this);
+}
+
+// ::pseudoElementFunction(expression)
+class PseudoElementFunctionSelector extends PseudoElementSelector {
+  SelectorExpression expression;
+
+  PseudoElementFunctionSelector(Identifier name, this.expression, Span span)
+      : super(name, span);
+  visit(VisitorBase visitor) =>
+      visitor.visitPseudoElementFunctionSelector(this);
+}
+
+class SelectorExpression extends TreeNode {
+  final List<Expression> _expressions = [];
+
+  SelectorExpression(Span span): super(span);
+
+  add(Expression expression) {
+    _expressions.add(expression);
+  }
+
+  List<Expression> get expressions => _expressions;
+
+  visit(VisitorBase visitor) => visitor.visitSelectorExpression(this);
+}
+
+// :NOT(negation_arg)
+class NegationSelector extends SimpleSelector {
+  SimpleSelector negationArg;
+
+  NegationSelector(this.negationArg, Span span)
+      : super(new Negation(span), span);
+
+  visit(VisitorBase visitor) => visitor.visitNegationSelector(this);
+}
+
+class StyleSheet extends TreeNode {
+  /**
+   * Contains charset, ruleset, directives (media, page, etc.), and selectors.
+   */
+  final topLevels;
+
+  StyleSheet(this.topLevels, Span span) : super(span) {
+    for (final node in topLevels) {
+      assert(node is TopLevelProduction || node is Directive);
+    }
+  }
+
+  /** Selectors only in this tree. */
+  StyleSheet.selector(this.topLevels, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitStyleSheet(this);
+}
+
+class TopLevelProduction extends TreeNode {
+  TopLevelProduction(Span span) : super(span);
+  visit(VisitorBase visitor) => visitor.visitTopLevelProduction(this);
+}
+
+class RuleSet extends TopLevelProduction {
+  final SelectorGroup _selectorGroup;
+  final DeclarationGroup _declarationGroup;
+
+  RuleSet(this._selectorGroup, this._declarationGroup, Span span) : super(span);
+
+  SelectorGroup get selectorGroup => _selectorGroup;
+  DeclarationGroup get declarationGroup => _declarationGroup;
+
+  visit(VisitorBase visitor) => visitor.visitRuleSet(this);
+}
+
+class Directive extends TreeNode {
+  Directive(Span span) : super(span);
+
+  bool get isBuiltIn => true;       // Known CSS directive?
+  bool get isExtension => false;    // SCSS extension?
+
+  visit(VisitorBase visitor) => visitor.visitDirective(this);
+}
+
+class ImportDirective extends Directive {
+  /** import name specified. */
+  final String import;
+
+  /** Any media queries for this import. */
+  final List<MediaQuery> mediaQueries;
+
+  ImportDirective(this.import, this.mediaQueries, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitImportDirective(this);
+}
+
+/**
+ *  MediaExpression grammar:
+ *    '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+ */
+class MediaExpression extends TreeNode {
+  final bool andOperator;
+  final Identifier _mediaFeature;
+  final Expressions exprs;
+
+  MediaExpression(this.andOperator, this._mediaFeature, this.exprs, Span span)
+      : super(span);
+
+  String get mediaFeature => _mediaFeature.name;
+
+  visit(VisitorBase visitor) => visitor.visitMediaExpression(this);
+}
+
+/**
+ * MediaQuery grammar:
+ *    : [ONLY | NOT]? S* media_type S* [ AND S* media_expression ]*
+ *    | media_expression [ AND S* media_expression ]*
+ *   media_type
+ *    : IDENT
+ *   media_expression
+ *    : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+ *   media_feature
+ *    : IDENT
+ */
+class MediaQuery extends TreeNode {
+  /** not, only or no operator. */
+  final int _mediaUnary;
+  final Identifier _mediaType;
+  final List<MediaExpression> expressions;
+
+  MediaQuery(this._mediaUnary, this._mediaType, this.expressions, Span span)
+      : super(span);
+
+  bool get hasMediaType => _mediaType != null;
+  String get mediaType => _mediaType.name;
+
+  bool get hasUnary => _mediaUnary != -1;
+  String get unary =>
+      TokenKind.idToValue(TokenKind.MEDIA_OPERATORS, _mediaUnary).toUpperCase();
+
+  visit(VisitorBase visitor) => visitor.visitMediaQuery(this);
+}
+
+class MediaDirective extends Directive {
+  List<MediaQuery> mediaQueries;
+  List<RuleSet> rulesets;
+
+  MediaDirective(this.mediaQueries, this.rulesets, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitMediaDirective(this);
+}
+
+class HostDirective extends Directive {
+  List<RuleSet> rulesets;
+
+  HostDirective(this.rulesets, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitHostDirective(this);
+}
+
+class PageDirective extends Directive {
+  final String _ident;
+  final String _pseudoPage;
+  List<DeclarationGroup> _declsMargin;
+
+  PageDirective(this._ident, this._pseudoPage, this._declsMargin,
+      Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitPageDirective(this);
+
+  bool get hasIdent => _ident != null && _ident.length > 0;
+  bool get hasPseudoPage => _pseudoPage != null && _pseudoPage.length > 0;
+}
+
+class CharsetDirective extends Directive {
+  final String charEncoding;
+
+  CharsetDirective(this.charEncoding, Span span) : super(span);
+  visit(VisitorBase visitor) => visitor.visitCharsetDirective(this);
+}
+
+class KeyFrameDirective extends Directive {
+  /*
+   * Either @keyframe or keyframe prefixed with @-webkit-, @-moz-, @-ms-, @-o-.
+   */
+  final int _keyframeName;
+  final _name;
+  final List<KeyFrameBlock> _blocks;
+
+  KeyFrameDirective(this._keyframeName, this._name, Span span)
+      : _blocks = [], super(span);
+
+  add(KeyFrameBlock block) {
+    _blocks.add(block);
+  }
+
+  String get keyFrameName {
+    switch (_keyframeName) {
+      case TokenKind.DIRECTIVE_KEYFRAMES:
+      case TokenKind.DIRECTIVE_MS_KEYFRAMES:
+        return '@keyframes';
+      case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: return '@-webkit-keyframes';
+      case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: return '@-moz-keyframes';
+      case TokenKind.DIRECTIVE_O_KEYFRAMES: return '@-o-keyframes';
+    }
+  }
+
+  String get name => _name;
+
+  visit(VisitorBase visitor) => visitor.visitKeyFrameDirective(this);
+}
+
+class KeyFrameBlock extends Expression {
+  final Expressions _blockSelectors;
+  final DeclarationGroup _declarations;
+
+  KeyFrameBlock(this._blockSelectors, this._declarations, Span span)
+      : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitKeyFrameBlock(this);
+}
+
+class FontFaceDirective extends Directive {
+  final DeclarationGroup _declarations;
+
+  FontFaceDirective(this._declarations, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitFontFaceDirective(this);
+}
+
+class StyletDirective extends Directive {
+  final String _dartClassName;
+  final List<RuleSet> _rulesets;
+
+  StyletDirective(this._dartClassName, this._rulesets, Span span) : super(span);
+
+  bool get isBuiltIn => false;
+  bool get isExtension => true;
+
+  String get dartClassName => _dartClassName;
+  List<RuleSet> get rulesets => _rulesets;
+
+  visit(VisitorBase visitor) => visitor.visitStyletDirective(this);
+}
+
+class NamespaceDirective extends Directive {
+  /** Namespace prefix. */
+  final String _prefix;
+
+  /** URI associated with this namespace. */
+  final String _uri;
+
+  NamespaceDirective(this._prefix, this._uri, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitNamespaceDirective(this);
+
+  String get prefix => _prefix.length > 0 ? '$_prefix ' : '';
+}
+
+/** To support Less syntax @name: expression */
+class VarDefinitionDirective extends Directive {
+  final VarDefinition def;
+
+  VarDefinitionDirective(this.def, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitVarDefinitionDirective(this);
+}
+
+class Declaration extends TreeNode {
+  final Identifier _property;
+  final Expression _expression;
+  /** Style exposed to Dart. */
+  var _dart;
+  final bool important;
+
+  /**
+   * IE CSS hacks that can only be read by a particular IE version.
+   *   7 implies IE 7 or older property (e.g., *background: blue;)
+   *   Note:  IE 8 or older property (e.g., background: green\9;) is handled
+   *          by IE8Term in declaration expression handling.
+   *   Note:  IE 6 only property with a leading underscore is a valid IDENT
+   *          since an ident can start with underscore (e.g., _background: red;)
+   */
+  final bool isIE7;
+
+  Declaration(this._property, this._expression, this._dart, Span span,
+              {important: false, ie7: false})
+      : this.important = important, this.isIE7 = ie7, super(span);
+
+  String get property => isIE7 ? '*${_property.name}' : _property.name;
+  Expression get expression => _expression;
+
+  bool get hasDartStyle => _dart != null;
+  get dartStyle => _dart;
+  set dartStyle(dStyle) {
+    _dart = dStyle;
+  }
+
+  visit(VisitorBase visitor) => visitor.visitDeclaration(this);
+}
+
+// TODO(terry): Consider 2 kinds of VarDefinitions static at top-level and
+//              dynamic when in a declaration.  Currently, Less syntax
+//              '@foo: expression' and 'var-foo: expression' in a declaration
+//              are statically resolved. Better solution, if @foo or var-foo
+//              are top-level are then statically resolved and var-foo in a
+//              declaration group (surrounded by a selector) would be dynamic.
+class VarDefinition extends Declaration {
+  VarDefinition(Identifier definedName, Expression expr, Span span)
+      : super(definedName, expr, null, span);
+
+  String get definedName => _property.name;
+
+  set dartStyle(dStyle) { }
+
+  visit(VisitorBase visitor) => visitor.visitVarDefinition(this);
+}
+
+class DeclarationGroup extends TreeNode {
+  /** Can be either Declaration or RuleSet (if nested selector). */
+  final List _declarations;
+
+  DeclarationGroup(this._declarations, Span span) : super(span);
+
+  List get declarations => _declarations;
+
+  visit(VisitorBase visitor) => visitor.visitDeclarationGroup(this);
+}
+
+class MarginGroup extends DeclarationGroup {
+  final int margin_sym;       // TokenType for for @margin sym.
+
+  MarginGroup(this.margin_sym, List<Declaration> decls, Span span)
+      : super(decls, span);
+  visit(VisitorBase visitor) => visitor.visitMarginGroup(this);
+}
+
+class VarUsage extends Expression {
+  final String name;
+  final List<Expression> defaultValues;
+
+  VarUsage(this.name, this.defaultValues, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitVarUsage(this);
+}
+
+class OperatorSlash extends Expression {
+  OperatorSlash(Span span) : super(span);
+  visit(VisitorBase visitor) => visitor.visitOperatorSlash(this);
+}
+
+class OperatorComma extends Expression {
+  OperatorComma(Span span) : super(span);
+  visit(VisitorBase visitor) => visitor.visitOperatorComma(this);
+}
+
+class OperatorPlus extends Expression {
+  OperatorPlus(Span span) : super(span);
+  visit(VisitorBase visitor) => visitor.visitOperatorPlus(this);
+}
+
+class OperatorMinus extends Expression {
+  OperatorMinus(Span span) : super(span);
+  visit(VisitorBase visitor) => visitor.visitOperatorMinus(this);
+}
+
+class UnicodeRangeTerm extends Expression {
+  final String first;
+  final String second;
+
+  UnicodeRangeTerm(this.first, this.second, Span span) : super(span);
+
+  bool get hasSecond => second != null;
+
+  visit(VisitorBase visitor) => visitor.visitUnicodeRangeTerm(this);
+}
+
+class LiteralTerm extends Expression {
+  // TODO(terry): value and text fields can be made final once all CSS resources
+  //              are copied/symlink'd in the build tool and UriVisitor in
+  //              web_ui is removed.
+  var value;
+  String text;
+
+  LiteralTerm(this.value, this.text, Span span) : super(span);
+
+  visit(VisitorBase visitor) => visitor.visitLiteralTerm(this);
+}
+
+class NumberTerm extends LiteralTerm {
+  NumberTerm(value, String t, Span span) : super(value, t, span);
+  visit(VisitorBase visitor) => visitor.visitNumberTerm(this);
+}
+
+class UnitTerm extends LiteralTerm {
+  final int _unit;
+
+  UnitTerm(value, String t, Span span, this._unit) : super(value, t, span);
+
+  int get unit => _unit;
+
+  visit(VisitorBase visitor) => visitor.visitUnitTerm(this);
+
+  String unitToString() => TokenKind.unitToString(_unit);
+
+  String toString() => '$text${unitToString()}';
+}
+
+class LengthTerm extends UnitTerm {
+  LengthTerm(value, String t, Span span,
+      [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(this._unit == TokenKind.UNIT_LENGTH_PX ||
+        this._unit == TokenKind.UNIT_LENGTH_CM ||
+        this._unit == TokenKind.UNIT_LENGTH_MM ||
+        this._unit == TokenKind.UNIT_LENGTH_IN ||
+        this._unit == TokenKind.UNIT_LENGTH_PT ||
+        this._unit == TokenKind.UNIT_LENGTH_PC);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitLengthTerm(this);
+}
+
+class PercentageTerm extends LiteralTerm {
+  PercentageTerm(value, String t, Span span) : super(value, t, span);
+  visit(VisitorBase visitor) => visitor.visitPercentageTerm(this);
+}
+
+class EmTerm extends LiteralTerm {
+  EmTerm(value, String t, Span span) : super(value, t, span);
+  visit(VisitorBase visitor) => visitor.visitEmTerm(this);
+}
+
+class ExTerm extends LiteralTerm {
+  ExTerm(value, String t, Span span) : super(value, t, span);
+  visit(VisitorBase visitor) => visitor.visitExTerm(this);
+}
+
+class AngleTerm extends UnitTerm {
+  AngleTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(this._unit == TokenKind.UNIT_ANGLE_DEG ||
+        this._unit == TokenKind.UNIT_ANGLE_RAD ||
+        this._unit == TokenKind.UNIT_ANGLE_GRAD ||
+        this._unit == TokenKind.UNIT_ANGLE_TURN);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitAngleTerm(this);
+}
+
+class TimeTerm extends UnitTerm {
+  TimeTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(this._unit == TokenKind.UNIT_ANGLE_DEG ||
+        this._unit == TokenKind.UNIT_TIME_MS ||
+        this._unit == TokenKind.UNIT_TIME_S);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitTimeTerm(this);
+}
+
+class FreqTerm extends UnitTerm {
+  FreqTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(_unit == TokenKind.UNIT_FREQ_HZ || _unit == TokenKind.UNIT_FREQ_KHZ);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitFreqTerm(this);
+}
+
+class FractionTerm extends LiteralTerm {
+  FractionTerm(var value, String t, Span span) : super(value, t, span);
+
+  visit(VisitorBase visitor) => visitor.visitFractionTerm(this);
+}
+
+class UriTerm extends LiteralTerm {
+  UriTerm(String value, Span span) : super(value, value, span);
+
+  visit(VisitorBase visitor) => visitor.visitUriTerm(this);
+}
+
+class ResolutionTerm extends UnitTerm {
+  ResolutionTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(_unit == TokenKind.UNIT_RESOLUTION_DPI ||
+        _unit == TokenKind.UNIT_RESOLUTION_DPCM ||
+        _unit == TokenKind.UNIT_RESOLUTION_DPPX);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitResolutionTerm(this);
+}
+
+class ChTerm extends UnitTerm {
+  ChTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(_unit == TokenKind.UNIT_CH);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitChTerm(this);
+}
+
+class RemTerm extends UnitTerm {
+  RemTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(_unit == TokenKind.UNIT_REM);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitRemTerm(this);
+}
+
+class ViewportTerm extends UnitTerm {
+  ViewportTerm(var value, String t, Span span,
+    [int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
+    assert(_unit == TokenKind.UNIT_VIEWPORT_VW ||
+        _unit == TokenKind.UNIT_VIEWPORT_VH ||
+        _unit == TokenKind.UNIT_VIEWPORT_VMIN ||
+        _unit == TokenKind.UNIT_VIEWPORT_VMAX);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitViewportTerm(this);
+}
+
+/** Type to signal a bad hex value for HexColorTerm.value. */
+class BAD_HEX_VALUE { }
+
+class HexColorTerm extends LiteralTerm {
+  HexColorTerm(var value, String t, Span span) : super(value, t, span);
+
+  visit(VisitorBase visitor) => visitor.visitHexColorTerm(this);
+}
+
+class FunctionTerm extends LiteralTerm {
+  final Expressions _params;
+
+  FunctionTerm(var value, String t, this._params, Span span)
+      : super(value, t, span);
+
+  visit(VisitorBase visitor) => visitor.visitFunctionTerm(this);
+}
+
+/**
+ * A "\9" was encountered at the end of the expression and before a semi-colon.
+ * This is an IE trick to ignore a property or value except by IE 8 and older
+ * browsers.
+ */
+class IE8Term extends LiteralTerm {
+  IE8Term(Span span) : super('\\9', '\\9', span);
+  visit(VisitorBase visitor) => visitor.visitIE8Term(this);
+}
+
+class GroupTerm extends Expression {
+  final List<LiteralTerm> _terms;
+
+  GroupTerm(Span span) : _terms =  [], super(span);
+
+  add(LiteralTerm term) {
+    _terms.add(term);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitGroupTerm(this);
+}
+
+class ItemTerm extends NumberTerm {
+  ItemTerm(var value, String t, Span span) : super(value, t, span);
+
+  visit(VisitorBase visitor) => visitor.visitItemTerm(this);
+}
+
+class Expressions extends Expression {
+  final List<Expression> expressions = [];
+
+  Expressions(Span span): super(span);
+
+  add(Expression expression) {
+    expressions.add(expression);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitExpressions(this);
+}
+
+class BinaryExpression extends Expression {
+  final Token op;
+  final Expression x;
+  final Expression y;
+
+  BinaryExpression(this.op, this.x, this.y, Span span): super(span);
+
+  visit(VisitorBase visitor) => visitor.visitBinaryExpression(this);
+}
+
+class UnaryExpression extends Expression {
+  final Token op;
+  final Expression self;
+
+  UnaryExpression(this.op, this.self, Span span): super(span);
+
+  visit(VisitorBase visitor) => visitor.visitUnaryExpression(this);
+}
+
+abstract class DartStyleExpression extends TreeNode {
+  static final int unknownType = 0;
+  static final int fontStyle = 1;
+  static final int marginStyle = 2;
+  static final int borderStyle = 3;
+  static final int paddingStyle = 4;
+  static final int heightStyle = 5;
+  static final int widthStyle = 6;
+
+  final int _styleType;
+  int priority;
+
+  DartStyleExpression(this._styleType, Span span) : super(span);
+
+  /*
+   * Merges give 2 DartStyleExpression (or derived from DartStyleExpression,
+   * e.g., FontExpression, etc.) will merge if the two expressions are of the
+   * same property name (implies same exact type e.g, FontExpression).
+   */
+  merged(DartStyleExpression newDartExpr);
+
+  bool get isUnknown => _styleType == 0 || _styleType == null;
+  bool get isFont => _styleType == fontStyle;
+  bool get isMargin => _styleType == marginStyle;
+  bool get isBorder => _styleType == borderStyle;
+  bool get isPadding => _styleType == paddingStyle;
+  bool get isHeight => _styleType == heightStyle;
+  bool get isWidth => _styleType == widthStyle;
+  bool get isBoxExpression => isMargin || isBorder || isPadding;
+
+  bool isSame(DartStyleExpression other) => this._styleType == other._styleType;
+
+  visit(VisitorBase visitor) => visitor.visitDartStyleExpression(this);
+}
+
+class FontExpression extends DartStyleExpression {
+  Font font;
+
+  //   font-style font-variant font-weight font-size/line-height font-family
+  FontExpression(Span span, {var size, List<String>family,
+      int weight, String style, String variant, LineHeight lineHeight})
+      : super(DartStyleExpression.fontStyle, span) {
+    // TODO(terry): Only px/pt for now need to handle all possible units to
+    //              support calc expressions on units.
+    font = new Font(size : size is LengthTerm ? size.value : size,
+        family: family, weight: weight, style: style, variant: variant,
+        lineHeight: lineHeight);
+  }
+
+  merged(FontExpression newFontExpr) {
+    if (this.isFont && newFontExpr.isFont) {
+      return new FontExpression.merge(this, newFontExpr);
+    }
+
+    return null;
+  }
+
+  /**
+   * Merge the two FontExpression and return the result.
+   */
+  factory FontExpression.merge(FontExpression x, FontExpression y) {
+    return new FontExpression._merge(x, y, y.span);
+  }
+
+  FontExpression._merge(FontExpression x, FontExpression y, Span span)
+      : super(DartStyleExpression.fontStyle, span),
+        font = new Font.merge(x.font, y.font);
+
+  visit(VisitorBase visitor) => visitor.visitFontExpression(this);
+}
+
+abstract class BoxExpression extends DartStyleExpression {
+  final BoxEdge box;
+
+  BoxExpression(int styleType, Span span, this.box)
+      : super(styleType, span);
+
+  /*
+   * Merges give 2 DartStyleExpression (or derived from DartStyleExpression,
+   * e.g., FontExpression, etc.) will merge if the two expressions are of the
+   * same property name (implies same exact type e.g, FontExpression).
+   */
+  merged(BoxExpression newDartExpr);
+
+  visit(VisitorBase visitor) => visitor.visitBoxExpression(this);
+
+  String get formattedBoxEdge {
+    if (box.top == box.left && box.top == box.bottom &&
+        box.top== box.right) {
+      return '.uniform(${box.top})';
+    } else {
+      var left = box.left == null ? 0 : box.left;
+      var top = box.top == null ? 0 : box.top;
+      var right = box.right == null ? 0 : box.right;
+      var bottom = box.bottom == null ? 0 : box.bottom;
+      return '.clockwiseFromTop($top,$right,$bottom,$left)';
+    }
+  }
+}
+
+class MarginExpression extends BoxExpression {
+  // TODO(terry): Does auto for margin need to be exposed to Dart UI framework?
+  /** Margin expression ripped apart. */
+  MarginExpression(Span span, {num top, num right, num bottom, num left})
+      : super(DartStyleExpression.marginStyle, span,
+              new BoxEdge(left, top, right, bottom));
+
+  MarginExpression.boxEdge(Span span, BoxEdge box)
+      : super(DartStyleExpression.marginStyle, span, box);
+
+  merged(MarginExpression newMarginExpr) {
+    if (this.isMargin && newMarginExpr.isMargin) {
+      return new MarginExpression.merge(this, newMarginExpr);
+    }
+
+    return null;
+  }
+
+  /**
+   * Merge the two MarginExpressions and return the result.
+   */
+  factory MarginExpression.merge(MarginExpression x, MarginExpression y) {
+    return new MarginExpression._merge(x, y, y.span);
+  }
+
+  MarginExpression._merge(MarginExpression x, MarginExpression y, Span span)
+      : super(x._styleType, span, new BoxEdge.merge(x.box, y.box));
+
+  visit(VisitorBase visitor) => visitor.visitMarginExpression(this);
+}
+
+class BorderExpression extends BoxExpression {
+  /** Border expression ripped apart. */
+  BorderExpression(Span span, {num top, num right, num bottom, num left})
+      : super(DartStyleExpression.borderStyle, span,
+              new BoxEdge(left, top, right, bottom));
+
+  BorderExpression.boxEdge(Span span, BoxEdge box)
+      : super(DartStyleExpression.borderStyle, span, box);
+
+  merged(BorderExpression newBorderExpr) {
+    if (this.isBorder && newBorderExpr.isBorder) {
+      return new BorderExpression.merge(this, newBorderExpr);
+    }
+
+    return null;
+  }
+
+  /**
+   * Merge the two BorderExpression and return the result.
+   */
+  factory BorderExpression.merge(BorderExpression x, BorderExpression y) {
+    return new BorderExpression._merge(x, y, y.span);
+  }
+
+  BorderExpression._merge(BorderExpression x, BorderExpression y,
+      Span span)
+      : super(DartStyleExpression.borderStyle, span,
+              new BoxEdge.merge(x.box, y.box));
+
+  visit(VisitorBase visitor) => visitor.visitBorderExpression(this);
+}
+
+class HeightExpression extends DartStyleExpression {
+  final height;
+
+  HeightExpression(Span span, this.height)
+      : super(DartStyleExpression.heightStyle, span);
+
+  merged(HeightExpression newHeightExpr) {
+    if (this.isHeight && newHeightExpr.isHeight) {
+      return newHeightExpr;
+    }
+
+    return null;
+  }
+
+  visit(VisitorBase visitor) => visitor.visitHeightExpression(this);
+}
+
+class WidthExpression extends DartStyleExpression {
+  final width;
+
+  WidthExpression(Span span, this.width)
+      : super(DartStyleExpression.widthStyle, span);
+
+  merged(WidthExpression newWidthExpr) {
+    if (this.isWidth && newWidthExpr.isWidth) {
+      return newWidthExpr;
+    }
+
+    return null;
+  }
+
+  visit(VisitorBase visitor) => visitor.visitWidthExpression(this);
+}
+
+class PaddingExpression extends BoxExpression {
+  /** Padding expression ripped apart. */
+  PaddingExpression(Span span, {num top, num right, num bottom, num left})
+      : super(DartStyleExpression.paddingStyle, span,
+              new BoxEdge(left, top, right, bottom));
+
+  PaddingExpression.boxEdge(Span span, BoxEdge box)
+      : super(DartStyleExpression.paddingStyle, span, box);
+
+  merged(PaddingExpression newPaddingExpr) {
+    if (this.isPadding && newPaddingExpr.isPadding) {
+      return new PaddingExpression.merge(this, newPaddingExpr);
+    }
+
+    return null;
+  }
+
+  /**
+   * Merge the two PaddingExpression and return the result.
+   */
+  factory PaddingExpression.merge(PaddingExpression x, PaddingExpression y) {
+    return new PaddingExpression._merge(x, y, y.span);
+  }
+
+  PaddingExpression._merge(PaddingExpression x, PaddingExpression y, Span span)
+      : super(DartStyleExpression.paddingStyle, span,
+            new BoxEdge.merge(x.box, y.box));
+
+  visit(VisitorBase visitor) => visitor.visitPaddingExpression(this);
+}
diff --git a/lib/src/tree_base.dart b/lib/src/tree_base.dart
new file mode 100644
index 0000000..31411d6
--- /dev/null
+++ b/lib/src/tree_base.dart
@@ -0,0 +1,109 @@
+// 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.
+
+part of csslib.visitor;
+
+/**
+ * The base type for all nodes in a CSS abstract syntax tree.
+ */
+abstract class TreeNode {
+  /** The source code this [TreeNode] represents. */
+  Span span;
+
+  TreeNode(this.span) {}
+
+  /** Classic double-dispatch visitor for implementing passes. */
+  visit(VisitorBase visitor);
+
+  /** A multiline string showing the node and its children. */
+  String toDebugString() {
+    var to = new TreeOutput();
+    var tp = new _TreePrinter(to, true);
+    this.visit(tp);
+    return to.buf.toString();
+  }
+}
+
+/** The base type for expressions. */
+abstract class Expression extends TreeNode {
+  Expression(Span span): super(span);
+}
+
+/** Simple class to provide a textual dump of trees for debugging. */
+class TreeOutput {
+  int depth = 0;
+  final StringBuffer buf = new StringBuffer();
+  var printer;
+
+  void write(String s) {
+    for (int i=0; i < depth; i++) {
+      buf.write(' ');
+    }
+    buf.write(s);
+  }
+
+  void writeln(String s) {
+    write(s);
+    buf.write('\n');
+  }
+
+  void heading(String name, [span]) {
+    write(name);
+    if (span != null) {
+      buf.write('  (${span.getLocationMessage('')})');
+    }
+    buf.write('\n');
+  }
+
+  String toValue(value) {
+    if (value == null) return 'null';
+    else if (value is Identifier) return value.name;
+    else return value.toString();
+  }
+
+  void writeNode(String label, TreeNode node) {
+    write('${label}: ');
+    depth += 1;
+    if (node != null) node.visit(printer);
+    else writeln('null');
+    depth -= 1;
+  }
+
+  void writeValue(String label, value) {
+    var v = toValue(value);
+    writeln('${label}: ${v}');
+  }
+
+  void writeList(String label, List list) {
+    write('${label}: ');
+    if (list == null) {
+      buf.write('null');
+      buf.write('\n');
+    } else {
+      for (var item in list) {
+        buf.write(item.toString());
+        buf.write(', ');
+      }
+      buf.write('\n');
+    }
+  }
+
+  void writeNodeList(String label, List list) {
+    writeln('${label} [');
+    if (list != null) {
+      depth += 1;
+      for (var node in list) {
+        if (node != null) {
+          node.visit(printer);
+        } else {
+          writeln('null');
+        }
+      }
+      depth -= 1;
+      writeln(']');
+    }
+  }
+
+  String toString() => buf.toString();
+}
diff --git a/lib/src/tree_printer.dart b/lib/src/tree_printer.dart
new file mode 100644
index 0000000..24cca64
--- /dev/null
+++ b/lib/src/tree_printer.dart
@@ -0,0 +1,512 @@
+// Copyright (c) 2013, 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.
+
+part of csslib.visitor;
+
+// TODO(terry): Enable class for debug only; when conditional imports enabled.
+
+/** Helper function to dump the CSS AST. */
+String treeToDebugString(styleSheet, [bool useSpan = false]) {
+  var to = new TreeOutput();
+  new _TreePrinter(to, useSpan)..visitTree(styleSheet);
+  return to.toString();
+}
+
+/** Tree dump for debug output of the CSS AST. */
+class _TreePrinter extends Visitor {
+  var output;
+  final bool useSpan;
+  _TreePrinter(this.output, this.useSpan) { output.printer = this; }
+
+  void visitTree(tree) => visitStylesheet(tree);
+
+  void heading(String heading, node) {
+    if (useSpan) {
+      output.heading(heading, node.span);
+    } else {
+      output.heading(heading);
+    }
+  }
+
+  void visitStylesheet(StyleSheet node) {
+    heading('Stylesheet', node);
+    output.depth++;
+    super.visitStyleSheet(node);
+    output.depth--;
+  }
+
+  void visitTopLevelProduction(TopLevelProduction node) {
+    heading('TopLevelProduction', node);
+  }
+
+  void visitDirective(Directive node) {
+    heading('Directive', node);
+  }
+
+  void visitCssComment(CssComment node) {
+    heading('Comment', node);
+    output.depth++;
+    output.writeValue('comment value', node.comment);
+    output.depth--;
+  }
+
+  void visitCommentDefinition(CommentDefinition node) {
+    heading('CommentDefinition (CDO/CDC)', node);
+    output.depth++;
+    output.writeValue('comment value', node.comment);
+    output.depth--;
+  }
+
+  void visitMediaExpression(MediaExpression node) {
+    heading('MediaExpression', node);
+    output.writeValue('feature', node.mediaFeature);
+    if (node.andOperator) output.writeValue('AND operator', '');
+    visitExpressions(node.exprs);
+  }
+
+  void visitMediaQueries(MediaQuery query) {
+    output.headeing('MediaQueries');
+    output.writeValue('unary', query.unary);
+    output.writeValue('media type', query.mediaType);
+    output.writeNodeList('media expressions', query.expressions);
+  }
+
+  void visitMediaDirective(MediaDirective node) {
+    heading('MediaDirective', node);
+    output.depth++;
+    output.writeNodeList('media queries', node.mediaQueries);
+    output.writeNodeList('rule sets', node.rulesets);
+    super.visitMediaDirective(node);
+    output.depth--;
+  }
+
+  void visitPageDirective(PageDirective node) {
+    heading('PageDirective', node);
+    output.depth++;
+    output.writeValue('pseudo page', node._pseudoPage);
+    super.visitPageDirective(node);
+    output.depth;
+  }
+
+  void visitCharsetDirective(CharsetDirective node) {
+    heading('Charset Directive', node);
+    output.writeValue('charset encoding', node.charEncoding);
+  }
+
+  void visitImportDirective(ImportDirective node) {
+    heading('ImportDirective', node);
+    output.depth++;
+    output.writeValue('import', node.import);
+    super.visitImportDirective(node);
+    output.writeNodeList('media', node.mediaQueries);
+    output.depth--;
+  }
+
+  void visitKeyFrameDirective(KeyFrameDirective node) {
+    heading('KeyFrameDirective', node);
+    output.depth++;
+    output.writeValue('keyframe', node.keyFrameName);
+    output.writeValue('name', node._name);
+    output.writeNodeList('blocks', node._blocks);
+    output.depth--;
+  }
+
+  void visitKeyFrameBlock(KeyFrameBlock node) {
+    heading('KeyFrameBlock', node);
+    output.depth++;
+    super.visitKeyFrameBlock(node);
+    output.depth--;
+  }
+
+  void visitFontFaceDirective(FontFaceDirective node) {
+    // TODO(terry): To Be Implemented
+  }
+
+  void visitStyletDirective(StyletDirective node) {
+    heading('StyletDirective', node);
+    output.writeValue('dartClassName', node._dartClassName);
+    output.depth++;
+    output.writeNodeList('rulesets', node._rulesets);
+    output.depth--;
+  }
+
+  void visitNamespaceDirective(NamespaceDirective node) {
+    heading('NamespaceDirective', node);
+    output.depth++;
+    output.writeValue('prefix', node._prefix);
+    output.writeValue('uri', node._uri);
+    output.depth--;
+  }
+
+  void visitVarDefinitionDirective(VarDefinitionDirective node) {
+    heading('Less variable definition', node);
+    visitVarDefinition(node.def);
+  }
+
+  void visitRuleSet(RuleSet node) {
+    heading('Ruleset', node);
+    output.depth++;
+    super.visitRuleSet(node);
+    output.depth--;
+  }
+
+  void visitDeclarationGroup(DeclarationGroup node) {
+    heading('DeclarationGroup', node);
+    output.depth++;
+    output.writeNodeList('declarations', node._declarations);
+    output.depth--;
+  }
+
+  void visitMarginGroup(MarginGroup node) {
+    heading('MarginGroup', node);
+    output.depth++;
+    output.writeValue('@directive', node.margin_sym);
+    output.writeNodeList('declarations', node._declarations);
+    output.depth--;
+  }
+
+  void visitDeclaration(Declaration node) {
+    heading('Declaration', node);
+    output.depth++;
+    if (node.isIE7) output.write('IE7 property');
+    output.write('property');
+    super.visitDeclaration(node);
+    output.writeNode('expression', node._expression);
+    if (node.important) {
+      output.writeValue('!important', 'true');
+    }
+    output.depth--;
+  }
+
+  void visitVarDefinition(VarDefinition node) {
+    heading('Var', node);
+    output.depth++;
+    output.write('defintion');
+    super.visitVarDefinition(node);
+    output.writeNode('expression', node._expression);
+    output.depth--;
+  }
+
+  void visitSelectorGroup(SelectorGroup node) {
+    heading('Selector Group', node);
+    output.depth++;
+    output.writeNodeList('selectors', node.selectors);
+    output.depth--;
+  }
+
+  void visitSelector(Selector node) {
+    heading('Selector', node);
+    output.depth++;
+    output.writeNodeList('simpleSelectorsSequences',
+        node._simpleSelectorSequences);
+    output.depth--;
+  }
+
+  void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+    heading('SimpleSelectorSequence', node);
+    output.depth++;
+    if (node.isCombinatorNone) {
+      output.writeValue('combinator', "NONE");
+    } else if (node.isCombinatorDescendant) {
+      output.writeValue('combinator', "descendant");
+    } else if (node.isCombinatorPlus) {
+      output.writeValue('combinator', "+");
+    } else if (node.isCombinatorGreater) {
+      output.writeValue('combinator', ">");
+    } else if (node.isCombinatorTilde) {
+      output.writeValue('combinator', "~");
+    } else {
+      output.writeValue('combinator', "ERROR UNKNOWN");
+    }
+
+    super.visitSimpleSelectorSequence(node);
+
+    output.depth--;
+  }
+
+  void visitNamespaceSelector(NamespaceSelector node) {
+    heading('Namespace Selector', node);
+    output.depth++;
+
+    super.visitNamespaceSelector(node);
+
+    visitSimpleSelector(node.nameAsSimpleSelector);
+    output.depth--;
+  }
+
+  void visitElementSelector(ElementSelector node) {
+    heading('Element Selector', node);
+    output.depth++;
+    super.visitElementSelector(node);
+    output.depth--;
+  }
+
+  void visitAttributeSelector(AttributeSelector node) {
+    heading('AttributeSelector', node);
+    output.depth++;
+    super.visitAttributeSelector(node);
+    String tokenStr = node.matchOperatorAsTokenString();
+    output.writeValue('operator', '${node.matchOperator()} (${tokenStr})');
+    output.writeValue('value', node.valueToString());
+    output.depth--;
+  }
+
+  void visitIdSelector(IdSelector node) {
+    heading('Id Selector', node);
+    output.depth++;
+    super.visitIdSelector(node);
+    output.depth--;
+  }
+
+  void visitClassSelector(ClassSelector node) {
+    heading('Class Selector', node);
+    output.depth++;
+    super.visitClassSelector(node);
+    output.depth--;
+  }
+
+  void visitPseudoClassSelector(PseudoClassSelector node) {
+    heading('Pseudo Class Selector', node);
+    output.depth++;
+    super.visitPseudoClassSelector(node);
+    output.depth--;
+  }
+
+  void visitPseudoElementSelector(PseudoElementSelector node) {
+    heading('Pseudo Element Selector', node);
+    output.depth++;
+    super.visitPseudoElementSelector(node);
+    output.depth--;
+  }
+
+  void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) {
+    heading('Pseudo Class Function Selector', node);
+    output.depth++;
+    visitSelectorExpression(node.expression);
+    super.visitPseudoClassFunctionSelector(node);
+    output.depth--;
+  }
+
+  void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) {
+    heading('Pseudo Element Function Selector', node);
+    output.depth++;
+    visitSelectorExpression(node.expression);
+    super.visitPseudoElementFunctionSelector(node);
+    output.depth--;
+  }
+
+  void visitSelectorExpression(SelectorExpression node) {
+    heading('Selector Expression', node);
+    output.depth++;
+    output.writeNodeList('expressions', node.expressions);
+    output.depth--;
+  }
+
+  void visitNegationSelector(NegationSelector node) {
+    super.visitNegationSelector(node);
+    output.depth++;
+    heading('Negation Selector', node);
+    output.writeNode('Negation arg', node.negationArg);
+    output.depth--;
+  }
+
+  void visitUnicodeRangeTerm(UnicodeRangeTerm node) {
+    heading('UnicodeRangeTerm', node);
+    output.depth++;
+    output.writeValue('1st value', node.first);
+    output.writeValue('2nd value', node.second);
+    output.depth--;
+  }
+
+  void visitLiteralTerm(LiteralTerm node) {
+    heading('LiteralTerm', node);
+    output.depth++;
+    output.writeValue('value', node.text);
+    output.depth--;
+ }
+
+  void visitHexColorTerm(HexColorTerm node) {
+    heading('HexColorTerm', node);
+    output.depth++;
+    output.writeValue('hex value', node.text);
+    output.writeValue('decimal value', node.value);
+    output.depth--;
+  }
+
+  void visitNumberTerm(NumberTerm node) {
+    heading('NumberTerm', node);
+    output.depth++;
+    output.writeValue('value', node.text);
+    output.depth--;
+  }
+
+  void visitUnitTerm(UnitTerm node) {
+    String unitValue;
+
+    output.depth++;
+    output.writeValue('value', node.text);
+    output.writeValue('unit', node.unitToString());
+    output.depth--;
+  }
+
+  void visitLengthTerm(LengthTerm node) {
+    heading('LengthTerm', node);
+    super.visitLengthTerm(node);
+  }
+
+  void visitPercentageTerm(PercentageTerm node) {
+    heading('PercentageTerm', node);
+    output.depth++;
+    super.visitPercentageTerm(node);
+    output.depth--;
+  }
+
+  void visitEmTerm(EmTerm node) {
+    heading('EmTerm', node);
+    output.depth++;
+    super.visitEmTerm(node);
+    output.depth--;
+  }
+
+  void visitExTerm(ExTerm node) {
+    heading('ExTerm', node);
+    output.depth++;
+    super.visitExTerm(node);
+    output.depth--;
+  }
+
+  void visitAngleTerm(AngleTerm node) {
+    heading('AngleTerm', node);
+    super.visitAngleTerm(node);
+  }
+
+  void visitTimeTerm(TimeTerm node) {
+    heading('TimeTerm', node);
+    super.visitTimeTerm(node);
+  }
+
+  void visitFreqTerm(FreqTerm node) {
+    heading('FreqTerm', node);
+    super.visitFreqTerm(node);
+  }
+
+  void visitFractionTerm(FractionTerm node) {
+    heading('FractionTerm', node);
+    output.depth++;
+    super.visitFractionTerm(node);
+    output.depth--;
+  }
+
+  void visitUriTerm(UriTerm node) {
+    heading('UriTerm', node);
+    output.depth++;
+    super.visitUriTerm(node);
+    output.depth--;
+  }
+
+  void visitFunctionTerm(FunctionTerm node) {
+    heading('FunctionTerm', node);
+    output.depth++;
+    super.visitFunctionTerm(node);
+    output.depth--;
+  }
+
+  void visitGroupTerm(GroupTerm node) {
+    heading('GroupTerm', node);
+    output.depth++;
+    output.writeNodeList('grouped terms', node._terms);
+    output.depth--;
+  }
+
+  void visitItemTerm(ItemTerm node) {
+    heading('ItemTerm', node);
+    super.visitItemTerm(node);
+  }
+
+  void visitIE8Term(IE8Term node) {
+    heading('IE8Term', node);
+    visitLiteralTerm(node);
+  }
+
+  void visitOperatorSlash(OperatorSlash node) {
+    heading('OperatorSlash', node);
+  }
+
+  void visitOperatorComma(OperatorComma node) {
+    heading('OperatorComma', node);
+  }
+
+  void visitOperatorPlus(OperatorPlus node) {
+    heading('OperatorPlus', node);
+  }
+
+  void visitOperatorMinus(OperatorMinus node) {
+    heading('OperatorMinus', node);
+  }
+
+  void visitVarUsage(VarUsage node) {
+    heading('Var', node);
+    output.depth++;
+    output.write('usage ${node.name}');
+    output.writeNodeList('default values', node.defaultValues);
+    output.depth--;
+  }
+
+  void visitExpressions(Expressions node) {
+    heading('Expressions', node);
+    output.depth++;
+    output.writeNodeList('expressions', node.expressions);
+    output.depth--;
+  }
+
+  void visitBinaryExpression(BinaryExpression node) {
+    heading('BinaryExpression', node);
+    // TODO(terry): TBD
+  }
+
+  void visitUnaryExpression(UnaryExpression node) {
+    heading('UnaryExpression', node);
+    // TODO(terry): TBD
+  }
+
+  void visitIdentifier(Identifier node) {
+    heading('Identifier(${output.toValue(node.name)})', node);
+  }
+
+  void visitWildcard(Wildcard node) {
+    heading('Wildcard(*)', node);
+  }
+
+  void visitDartStyleExpression(DartStyleExpression node) {
+    heading('DartStyleExpression', node);
+  }
+
+  void visitFontExpression(FontExpression node) {
+    heading('Dart Style FontExpression', node);
+  }
+
+  void visitBoxExpression(BoxExpression node) {
+    heading('Dart Style BoxExpression', node);
+  }
+
+  void visitMarginExpression(MarginExpression node) {
+    heading('Dart Style MarginExpression', node);
+  }
+
+  void visitBorderExpression(BorderExpression node) {
+    heading('Dart Style BorderExpression', node);
+  }
+
+  void visitHeightExpression(HeightExpression node) {
+    heading('Dart Style HeightExpression', node);
+  }
+
+  void visitPaddingExpression(PaddingExpression node) {
+    heading('Dart Style PaddingExpression', node);
+  }
+
+  void visitWidthExpression(WidthExpression node) {
+    heading('Dart Style WidthExpression', node);
+  }
+}
diff --git a/lib/src/validate.dart b/lib/src/validate.dart
new file mode 100644
index 0000000..d2301d7
--- /dev/null
+++ b/lib/src/validate.dart
@@ -0,0 +1,135 @@
+// 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.src.validate;
+
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'package:source_maps/span.dart' show Span;
+
+/** Can be thrown on any Css runtime problem includes source location. */
+class CssSelectorException implements Exception {
+  final String _message;
+  final Span _span;
+
+  CssSelectorException(this._message, [this._span]);
+
+  String toString() {
+    var msg = _span == null ? _message : _span.getLocationMessage(_message);
+    return 'CssSelectorException: $msg';
+  }
+}
+
+List<String> classes = [];
+List<String> ids = [];
+
+class Validate {
+  static int _classNameCheck(var selector, int matches) {
+    if (selector.isCombinatorDescendant() ||
+        (selector.isCombinatorNone() && matches == 0)) {
+      if (matches < 0) {
+        String tooMany = selector.simpleSelector.toString();
+        throw new CssSelectorException(
+            'Can not mix Id selector with class selector(s). Id '
+            'selector must be singleton too many starting at $tooMany');
+      }
+
+      return matches + 1;
+    } else {
+      String error = selector.toString();
+      throw new CssSelectorException(
+          'Selectors can not have combinators (>, +, or ~) before $error');
+    }
+  }
+
+  static int _elementIdCheck(var selector, int matches) {
+    if (selector.isCombinatorNone() && matches == 0) {
+      // Perfect just one element id returns matches of -1.
+      return -1;
+    } else if (selector.isCombinatorDescendant()) {
+        String tooMany = selector.simpleSelector.toString();
+        throw new CssSelectorException(
+            'Use of Id selector must be singleton starting at $tooMany');
+    } else {
+      String error = selector.simpleSelector.toString();
+      throw new CssSelectorException(
+          'Selectors can not have combinators (>, +, or ~) before $error');
+    }
+  }
+
+  // Validate the @{css expression} only .class and #elementId are valid inside
+  // of @{...}.
+  static template(List<Selector> selectors) {
+    var errorSelector;                  // signal which selector didn't match.
+    bool found = false;                 // signal if a selector is matched.
+    int matches = 0;                    // < 0 IdSelectors, > 0 ClassSelector
+
+    // At most one selector group (any number of simple selector sequences).
+    assert(selectors.length <= 1);
+
+    for (final sels in selectors) {
+      for (final selector in sels.simpleSelectorSequences) {
+        found = false;
+        var simpleSelector = selector.simpleSelector;
+        if (simpleSelector is ClassSelector) {
+          // Any class name starting with an underscore is a private class name
+          // that doesn't have to match the world of known classes.
+          if (!simpleSelector.name.startsWith('_')) {
+            // TODO(terry): For now iterate through all classes look for faster
+            //              mechanism hash map, etc.
+            for (final className in classes) {
+              if (selector.simpleSelector.name == className) {
+                matches = _classNameCheck(selector, matches);
+                found = true;              // .class found.
+                break;
+              }
+              for (final className2 in classes) {
+                print(className2);
+              }
+            }
+
+          } else {
+            // Don't check any class name that is prefixed with an underscore.
+            // However, signal as found and bump up matches; it's a valid class
+            // name.
+            matches = _classNameCheck(selector, matches);
+            found = true;                 // ._class are always okay.
+          }
+        } else if (simpleSelector is IdSelector) {
+          // Any element id starting with an underscore is a private element id
+          // that doesn't have to match the world of known elemtn ids.
+          if (!simpleSelector.name.startsWith('_')) {
+            for (final id in ids) {
+              if (simpleSelector.name == id) {
+                matches = _elementIdCheck(selector, matches);
+                found = true;             // #id found.
+                break;
+              }
+            }
+          } else {
+            // Don't check any element ID that is prefixed with an underscore.
+            // Signal as found and bump up matches; it's a valid element ID.
+            matches = _elementIdCheck(selector, matches);
+            found = true;                 // #_id are always okay
+          }
+        } else {
+          String badSelector = simpleSelector.toString();
+          throw new CssSelectorException(
+              'Invalid template selector $badSelector');
+        }
+
+        if (!found) {
+          String unknownName = simpleSelector.toString();
+          throw new CssSelectorException('Unknown selector name $unknownName');
+        }
+      }
+    }
+
+    // Every selector must match.
+    Selector selector = selectors[0];
+    assert((matches >= 0 ? matches : -matches) ==
+        selector.simpleSelectorSequences.length);
+  }
+}
+
diff --git a/lib/visitor.dart b/lib/visitor.dart
new file mode 100644
index 0000000..d2f24c9
--- /dev/null
+++ b/lib/visitor.dart
@@ -0,0 +1,445 @@
+// 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.visitor;
+
+import 'package:source_maps/span.dart' show Span;
+import 'parser.dart';
+
+part 'src/css_printer.dart';
+part 'src/tree.dart';
+part 'src/tree_base.dart';
+part 'src/tree_printer.dart';
+
+abstract class VisitorBase {
+  void visitCssComment(CssComment node);
+  void visitCommentDefinition(CommentDefinition node);
+  void visitStyleSheet(StyleSheet node);
+  void visitTopLevelProduction(TopLevelProduction node);
+  void visitDirective(Directive node);
+  void visitMediaExpression(MediaExpression node);
+  void visitMediaQuery(MediaQuery node);
+  void visitMediaDirective(MediaDirective node);
+  void visitHostDirective(HostDirective node);
+  void visitPageDirective(PageDirective node);
+  void visitCharsetDirective(CharsetDirective node);
+  void visitImportDirective(ImportDirective node);
+  void visitKeyFrameDirective(KeyFrameDirective node);
+  void visitKeyFrameBlock(KeyFrameBlock node);
+  void visitFontFaceDirective(FontFaceDirective node);
+  void visitStyletDirective(StyletDirective node);
+  void visitNamespaceDirective(NamespaceDirective node);
+  void visitVarDefinitionDirective(VarDefinitionDirective node);
+
+  void visitRuleSet(RuleSet node);
+  void visitDeclarationGroup(DeclarationGroup node);
+  void visitMarginGroup(DeclarationGroup node);
+  void visitDeclaration(Declaration node);
+  void visitVarDefinition(VarDefinition node);
+  void visitSelectorGroup(SelectorGroup node);
+  void visitSelector(Selector node);
+  void visitSimpleSelectorSequence(SimpleSelectorSequence node);
+  void visitSimpleSelector(SimpleSelector node);
+  void visitElementSelector(ElementSelector node);
+  void visitNamespaceSelector(NamespaceSelector node);
+  void visitAttributeSelector(AttributeSelector node);
+  void visitIdSelector(IdSelector node);
+  void visitClassSelector(ClassSelector node);
+  void visitPseudoClassSelector(PseudoClassSelector node);
+  void visitPseudoElementSelector(PseudoElementSelector node);
+  void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node);
+  void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node);
+  void visitNegationSelector(NegationSelector node);
+  void visitSelectorExpression(SelectorExpression node);
+
+  void visitUnicodeRangeTerm(UnicodeRangeTerm node);
+  void visitLiteralTerm(LiteralTerm node);
+  void visitHexColorTerm(HexColorTerm node);
+  void visitNumberTerm(NumberTerm node);
+  void visitUnitTerm(UnitTerm node);
+  void visitLengthTerm(LengthTerm node);
+  void visitPercentageTerm(PercentageTerm node);
+  void visitEmTerm(EmTerm node);
+  void visitExTerm(ExTerm node);
+  void visitAngleTerm(AngleTerm node);
+  void visitTimeTerm(TimeTerm node);
+  void visitFreqTerm(FreqTerm node);
+  void visitFractionTerm(FractionTerm node);
+  void visitUriTerm(UriTerm node);
+  void visitResolutionTerm(ResolutionTerm node);
+  void visitChTerm(ChTerm node);
+  void visitRemTerm(RemTerm node);
+  void visitViewportTerm(ViewportTerm node);
+  void visitFunctionTerm(FunctionTerm node);
+  void visitGroupTerm(GroupTerm node);
+  void visitItemTerm(ItemTerm node);
+  void visitIE8Term(IE8Term node);
+  void visitOperatorSlash(OperatorSlash node);
+  void visitOperatorComma(OperatorComma node);
+  void visitOperatorPlus(OperatorPlus node);
+  void visitOperatorMinus(OperatorMinus node);
+  void visitVarUsage(VarUsage node);
+
+  void visitExpressions(Expressions node);
+  void visitBinaryExpression(BinaryExpression node);
+  void visitUnaryExpression(UnaryExpression node);
+
+  void visitIdentifier(Identifier node);
+  void visitWildcard(Wildcard node);
+  void visitThisOperator(ThisOperator node);
+  void visitNegation(Negation node);
+
+  void visitDartStyleExpression(DartStyleExpression node);
+  void visitFontExpression(FontExpression node);
+  void visitBoxExpression(BoxExpression node);
+  void visitMarginExpression(MarginExpression node);
+  void visitBorderExpression(BorderExpression node);
+  void visitHeightExpression(HeightExpression node);
+  void visitPaddingExpression(PaddingExpression node);
+  void visitWidthExpression(WidthExpression node);
+}
+
+/** Base vistor class for the style sheet AST. */
+class Visitor implements VisitorBase {
+  /** Helper function to walk a list of nodes. */
+  void _visitNodeList(list) {
+    // Don't use iterable otherwise the list can't grow while using Visitor.
+    // It certainly can't have items deleted before the index being iterated
+    // but items could be added after the index.
+    for (var index = 0; index < list.length; index++) {
+      list[index].visit(this);
+    }
+  }
+
+  void visitTree(StyleSheet tree) => visitStyleSheet(tree);
+
+  void visitStyleSheet(StyleSheet ss) {
+    _visitNodeList(ss.topLevels);
+  }
+
+  void visitTopLevelProduction(TopLevelProduction node) { }
+
+  void visitDirective(Directive node) { }
+
+  void visitCssComment(CssComment node) { }
+
+  void visitCommentDefinition(CommentDefinition node) { }
+
+  void visitMediaExpression(MediaExpression node) {
+    visitExpressions(node.exprs);
+  }
+
+  void visitMediaQuery(MediaQuery node) {
+    for (var mediaExpr in node.expressions) {
+      visitMediaExpression(mediaExpr);
+    }
+  }
+
+  void visitMediaDirective(MediaDirective node) {
+    for (var mediaQuery in node.mediaQueries) {
+      visitMediaQuery(mediaQuery);
+    }
+    for (var ruleset in node.rulesets) {
+      visitRuleSet(ruleset);
+    }
+  }
+
+  void visitHostDirective(HostDirective node) {
+    for (var ruleset in node.rulesets) {
+      visitRuleSet(ruleset);
+    }
+  }
+
+  void visitPageDirective(PageDirective node) {
+    for (var declGroup in node._declsMargin) {
+      if (declGroup is MarginGroup) {
+        visitMarginGroup(declGroup);
+      } else {
+        visitDeclarationGroup(declGroup);
+      }
+    }
+  }
+
+  void visitCharsetDirective(CharsetDirective node) { }
+
+  void visitImportDirective(ImportDirective node) {
+    for (var mediaQuery in node.mediaQueries) {
+      visitMediaQuery(mediaQuery);
+    }
+  }
+
+  void visitKeyFrameDirective(KeyFrameDirective node) {
+    visitIdentifier(node._name);
+    _visitNodeList(node._blocks);
+  }
+
+  void visitKeyFrameBlock(KeyFrameBlock node) {
+    visitExpressions(node._blockSelectors);
+    visitDeclarationGroup(node._declarations);
+  }
+
+  void visitFontFaceDirective(FontFaceDirective node) {
+    visitDeclarationGroup(node._declarations);
+  }
+
+  void visitStyletDirective(StyletDirective node) {
+    _visitNodeList(node._rulesets);
+  }
+
+  void visitNamespaceDirective(NamespaceDirective node) { }
+
+  void visitVarDefinitionDirective(VarDefinitionDirective node) {
+    visitVarDefinition(node.def);
+  }
+
+  void visitRuleSet(RuleSet node) {
+    visitSelectorGroup(node._selectorGroup);
+    visitDeclarationGroup(node._declarationGroup);
+  }
+
+  void visitDeclarationGroup(DeclarationGroup node) {
+    _visitNodeList(node._declarations);
+  }
+
+  void visitMarginGroup(MarginGroup node) => visitDeclarationGroup(node);
+
+  void visitDeclaration(Declaration node) {
+    visitIdentifier(node._property);
+    if (node._expression != null) node._expression.visit(this);
+  }
+
+  void visitVarDefinition(VarDefinition node) {
+    visitIdentifier(node._property);
+    if (node._expression != null) node._expression.visit(this);
+  }
+
+  void visitSelectorGroup(SelectorGroup node) {
+    _visitNodeList(node.selectors);
+  }
+
+  void visitSelector(Selector node) {
+    _visitNodeList(node._simpleSelectorSequences);
+  }
+
+  void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+    var selector = node._selector;
+    if (selector is NamespaceSelector) {
+      visitNamespaceSelector(selector);
+    } else if (selector is ElementSelector) {
+      visitElementSelector(selector);
+    } else if (selector is IdSelector) {
+      visitIdSelector(selector);
+    } else if (selector is ClassSelector) {
+      visitClassSelector(selector);
+    } else if (selector is PseudoClassFunctionSelector) {
+      visitPseudoClassFunctionSelector(selector);
+    } else if (selector is PseudoElementFunctionSelector) {
+      visitPseudoElementFunctionSelector(selector);
+    } else if (selector is PseudoClassSelector) {
+      visitPseudoClassSelector(selector);
+    } else if (selector is PseudoElementSelector) {
+      visitPseudoElementSelector(selector);
+    } else if (selector is NegationSelector) {
+      visitNegationSelector(selector);
+    } else if (selector is SelectorExpression) {
+      visitSelectorExpression(selector);
+    } else if (selector is AttributeSelector) {
+      visitAttributeSelector(selector);
+    } else {
+      visitSimpleSelector(selector);
+    }
+  }
+
+  void visitSimpleSelector(SimpleSelector node) => node._name.visit(this);
+
+  void visitNamespaceSelector(NamespaceSelector node) {
+    var namespace = node._namespace;
+    if (namespace is Identifier) {
+      visitIdentifier(namespace);
+    } else if (namespace is Wildcard) {
+      visitWildcard(namespace);
+    }
+
+    visitSimpleSelector(node.nameAsSimpleSelector);
+  }
+
+  void visitElementSelector(ElementSelector node) => visitSimpleSelector(node);
+
+  void visitAttributeSelector(AttributeSelector node) {
+    visitSimpleSelector(node);
+  }
+
+  void visitIdSelector(IdSelector node) => visitSimpleSelector(node);
+
+  void visitClassSelector(ClassSelector node) => visitSimpleSelector(node);
+
+  void visitPseudoClassSelector(PseudoClassSelector node) =>
+      visitSimpleSelector(node);
+
+  void visitPseudoElementSelector(PseudoElementSelector node) =>
+      visitSimpleSelector(node);
+
+  void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) =>
+      visitSimpleSelector(node);
+
+  void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) =>
+      visitSimpleSelector(node);
+
+  void visitNegationSelector(NegationSelector node) =>
+      visitSimpleSelector(node);
+
+  void visitSelectorExpression(SelectorExpression node) {
+    _visitNodeList(node._expressions);
+  }
+
+  void visitUnicodeRangeTerm(UnicodeRangeTerm node) { }
+
+  void visitLiteralTerm(LiteralTerm node) { }
+
+  void visitHexColorTerm(HexColorTerm node) { }
+
+  void visitNumberTerm(NumberTerm node) { }
+
+  void visitUnitTerm(UnitTerm node) { }
+
+  void visitLengthTerm(LengthTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitPercentageTerm(PercentageTerm node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitEmTerm(EmTerm node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitExTerm(ExTerm node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitAngleTerm(AngleTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitTimeTerm(TimeTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitFreqTerm(FreqTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitFractionTerm(FractionTerm node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitUriTerm(UriTerm node) {
+    visitLiteralTerm(node);
+  }
+
+  void visitResolutionTerm(ResolutionTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitChTerm(ChTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitRemTerm(RemTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitViewportTerm(ViewportTerm node) {
+    visitUnitTerm(node);
+  }
+
+  void visitFunctionTerm(FunctionTerm node) {
+    visitLiteralTerm(node);
+    visitExpressions(node._params);
+  }
+
+  void visitGroupTerm(GroupTerm node) {
+    for (var term in node._terms) {
+      term.visit(this);
+    }
+  }
+
+  void visitItemTerm(ItemTerm node) {
+    visitNumberTerm(node);
+  }
+
+  void visitIE8Term(IE8Term node) { }
+
+  void visitOperatorSlash(OperatorSlash node) { }
+
+  void visitOperatorComma(OperatorComma node) { }
+
+  void visitOperatorPlus(OperatorPlus node) { }
+
+  void visitOperatorMinus(OperatorMinus node) { }
+
+  void visitVarUsage(VarUsage node) {
+    _visitNodeList(node.defaultValues);
+  }
+
+  void visitExpressions(Expressions node) {
+    _visitNodeList(node.expressions);
+  }
+
+  void visitBinaryExpression(BinaryExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitUnaryExpression(UnaryExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitIdentifier(Identifier node) { }
+
+  void visitWildcard(Wildcard node) { }
+
+  void visitThisOperator(ThisOperator node) { }
+
+  void visitNegation(Negation node) { }
+
+  void visitDartStyleExpression(DartStyleExpression node) { }
+
+  void visitFontExpression(FontExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitBoxExpression(BoxExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitMarginExpression(MarginExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitBorderExpression(BorderExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitHeightExpression(HeightExpression node) {
+    // TODO(terry): TB
+    throw UnimplementedError;
+  }
+
+  void visitPaddingExpression(PaddingExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+
+  void visitWidthExpression(WidthExpression node) {
+    // TODO(terry): TBD
+    throw UnimplementedError;
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..b821941
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,12 @@
+name: csslib
+author: "Web UI Team <web-ui-dev@dartlang.org>"
+description: A library for parsing CSS.
+homepage: https://www.dartlang.org
+dependencies:
+  args: any
+  logging: any
+  path: any
+  source_maps: any
+dev_dependencies:
+  browser: any
+  unittest: any
diff --git a/test/compiler_test.dart b/test/compiler_test.dart
new file mode 100644
index 0000000..ee37f29
--- /dev/null
+++ b/test/compiler_test.dart
@@ -0,0 +1,728 @@
+// 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 compiler_test;
+
+import 'dart:utf';
+import 'package:unittest/unittest.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'testing.dart';
+
+void testClass() {
+  var errors = [];
+  var input = ".foobar {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var selectorSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+  expect(selectorSeqs.length, 1);
+  final simpSelector = selectorSeqs[0].simpleSelector;
+  expect(simpSelector is ClassSelector, true);
+  expect(selectorSeqs[0].isCombinatorNone, true);
+  expect(simpSelector.name, "foobar");
+}
+
+void testClass2() {
+  var errors = [];
+  var input = ".foobar .bar .no-story {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+  expect(simpleSeqs.length, 3);
+
+  var simpSelector0 = simpleSeqs[0].simpleSelector;
+  expect(simpSelector0 is ClassSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector0.name, "foobar");
+
+  var simpSelector1 = simpleSeqs[1].simpleSelector;
+  expect(simpSelector1 is ClassSelector, true);
+  expect(simpleSeqs[1].isCombinatorDescendant, true);
+  expect(simpSelector1.name, "bar");
+
+  var simpSelector2 = simpleSeqs[2].simpleSelector;
+  expect(simpSelector2 is ClassSelector, true);
+  expect(simpleSeqs[2].isCombinatorDescendant, true);
+  expect(simpSelector2.name, "no-story");
+}
+
+void testId() {
+  var errors = [];
+  var input = "#elemId {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 1);
+  var simpSelector = simpleSeqs[0].simpleSelector;
+  expect(simpSelector is IdSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector.name, "elemId");
+}
+
+void testElement() {
+  var errors = [];
+  var input = "div {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 1);
+
+  final simpSelector = simpleSeqs[0].simpleSelector;
+  expect(simpSelector is ElementSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector.name, "div");
+
+  input = "div div span {}";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 3);
+
+  var simpSelector0 = simpleSeqs[0].simpleSelector;
+  expect(simpSelector0 is ElementSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector0.name, "div");
+
+  var simpSelector1 = simpleSeqs[1].simpleSelector;
+  expect(simpSelector1 is ElementSelector, true);
+  expect(simpleSeqs[1].isCombinatorDescendant, true);
+  expect(simpSelector1.name, "div");
+
+  var simpSelector2 = simpleSeqs[2].simpleSelector;
+  expect(simpSelector2 is ElementSelector, true);
+  expect(simpleSeqs[2].isCombinatorDescendant, true);
+  expect(simpSelector2.name, "span");
+}
+
+void testNamespace() {
+  var errors = [];
+  var input = "ns1|div {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 1);
+  var simpSelector = simpleSeqs[0].simpleSelector;
+  expect(simpSelector is NamespaceSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector.isNamespaceWildcard, false);
+  expect(simpSelector.namespace, "ns1");
+  var elementSelector = simpSelector.nameAsSimpleSelector;
+  expect(elementSelector is ElementSelector, true);
+  expect(elementSelector.isWildcard, false);
+  expect(elementSelector.name, "div");
+}
+
+void testNamespace2() {
+  var errors = [];
+  var input = "ns1|div div ns2|span .foobar {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 4);
+
+  var simpSelector0 = simpleSeqs[0].simpleSelector;
+  expect(simpSelector0 is NamespaceSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector0.namespace, "ns1");
+  var elementSelector0 = simpSelector0.nameAsSimpleSelector;
+  expect(elementSelector0 is ElementSelector, true);
+  expect(elementSelector0.isWildcard, false);
+  expect(elementSelector0.name, "div");
+
+  var simpSelector1 = simpleSeqs[1].simpleSelector;
+  expect(simpSelector1 is ElementSelector, true);
+  expect(simpleSeqs[1].isCombinatorDescendant, true);
+  expect(simpSelector1.name, "div");
+
+  var simpSelector2 = simpleSeqs[2].simpleSelector;
+  expect(simpSelector2 is NamespaceSelector, true);
+  expect(simpleSeqs[2].isCombinatorDescendant, true);
+  expect(simpSelector2.namespace, "ns2");
+  var elementSelector2 = simpSelector2.nameAsSimpleSelector;
+  expect(elementSelector2 is ElementSelector, true);
+  expect(elementSelector2.isWildcard, false);
+  expect(elementSelector2.name, "span");
+
+  var simpSelector3 = simpleSeqs[3].simpleSelector;
+  expect(simpSelector3 is ClassSelector, true);
+  expect(simpleSeqs[3].isCombinatorDescendant, true);
+  expect(simpSelector3.name, "foobar");
+}
+
+void testSelectorGroups() {
+  var errors = [];
+  var input =
+      "div, .foobar ,#elemId, .xyzzy .test, ns1|div div #elemId .foobar {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 5);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var groupSelector0 = ruleset.selectorGroup.selectors[0];
+  expect(groupSelector0.simpleSelectorSequences.length, 1);
+  var selector0 = groupSelector0.simpleSelectorSequences[0];
+  var simpleSelector0 = selector0.simpleSelector;
+  expect(simpleSelector0 is ElementSelector, true);
+  expect(selector0.isCombinatorNone, true);
+  expect(simpleSelector0.name, "div");
+
+  var groupSelector1 = ruleset.selectorGroup.selectors[1];
+  expect(groupSelector1.simpleSelectorSequences.length, 1);
+  var selector1 = groupSelector1.simpleSelectorSequences[0];
+  var simpleSelector1 = selector1.simpleSelector;
+  expect(simpleSelector1 is ClassSelector, true);
+  expect(selector1.isCombinatorNone, true);
+  expect(simpleSelector1.name, "foobar");
+
+  var groupSelector2 = ruleset.selectorGroup.selectors[2];
+  expect(groupSelector2.simpleSelectorSequences.length, 1);
+  var selector2 = groupSelector2.simpleSelectorSequences[0];
+  var simpleSelector2 = selector2.simpleSelector;
+  expect(simpleSelector2 is IdSelector, true);
+  expect(selector2.isCombinatorNone, true);
+  expect(simpleSelector2.name, "elemId");
+
+  var groupSelector3 = ruleset.selectorGroup.selectors[3];
+  expect(groupSelector3.simpleSelectorSequences.length, 2);
+
+  var selector30 = groupSelector3.simpleSelectorSequences[0];
+  var simpleSelector30 = selector30.simpleSelector;
+  expect(simpleSelector30 is ClassSelector, true);
+  expect(selector30.isCombinatorNone, true);
+  expect(simpleSelector30.name, "xyzzy");
+
+  var selector31 = groupSelector3.simpleSelectorSequences[1];
+  var simpleSelector31 = selector31.simpleSelector;
+  expect(simpleSelector31 is ClassSelector, true);
+  expect(selector31.isCombinatorDescendant, true);
+  expect(simpleSelector31.name, "test");
+
+  var groupSelector4 = ruleset.selectorGroup.selectors[4];
+  expect(groupSelector4.simpleSelectorSequences.length, 4);
+
+  var selector40 = groupSelector4.simpleSelectorSequences[0];
+  var simpleSelector40 = selector40.simpleSelector;
+  expect(simpleSelector40 is NamespaceSelector, true);
+  expect(selector40.isCombinatorNone, true);
+  expect(simpleSelector40.namespace, "ns1");
+  var elementSelector = simpleSelector40.nameAsSimpleSelector;
+  expect(elementSelector is ElementSelector, true);
+  expect(elementSelector.isWildcard, false);
+  expect(elementSelector.name, "div");
+
+  var selector41 = groupSelector4.simpleSelectorSequences[1];
+  var simpleSelector41 = selector41.simpleSelector;
+  expect(simpleSelector41 is ElementSelector, true);
+  expect(selector41.isCombinatorDescendant, true);
+  expect(simpleSelector41.name, "div");
+
+  var selector42 = groupSelector4.simpleSelectorSequences[2];
+  var simpleSelector42 = selector42.simpleSelector;
+  expect(simpleSelector42 is IdSelector, true);
+  expect(selector42.isCombinatorDescendant, true);
+  expect(simpleSelector42.name, "elemId");
+
+  var selector43 = groupSelector4.simpleSelectorSequences[3];
+  var simpleSelector43 = selector43.simpleSelector;
+  expect(selector43.isCombinatorDescendant, true);
+  expect(simpleSelector43.name, "foobar");
+}
+
+void testCombinator() {
+  var errors = [];
+  var input = ".foobar > .bar + .no-story ~ myNs|div #elemId {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 5);
+
+  var selector0 = simpleSeqs[0];
+  var simpleSelector0 = selector0.simpleSelector;
+  expect(simpleSelector0 is ClassSelector, true);
+  expect(selector0.isCombinatorNone, true);
+  expect(simpleSelector0.name, "foobar");
+
+  var selector1 = simpleSeqs[1];
+  var simpleSelector1 = selector1.simpleSelector;
+  expect(simpleSelector1 is ClassSelector, true);
+  expect(selector1.isCombinatorGreater, true);
+  expect(simpleSelector1.name, "bar");
+
+  var selector2 = simpleSeqs[2];
+  var simpleSelector2 = selector2.simpleSelector;
+  expect(simpleSelector2 is ClassSelector, true);
+  expect(selector2.isCombinatorPlus, true);
+  expect(simpleSelector2.name, "no-story");
+
+  var selector3 = simpleSeqs[3];
+  var simpleSelector3 = selector3.simpleSelector;
+  expect(simpleSelector3 is NamespaceSelector, true);
+  expect(selector3.isCombinatorTilde, true);
+  expect(simpleSelector3.namespace, "myNs");
+  var elementSelector = simpleSelector3.nameAsSimpleSelector;
+  expect(elementSelector is ElementSelector, true);
+  expect(elementSelector.isWildcard, false);
+  expect(elementSelector.name, "div");
+
+  var selector4 = simpleSeqs[4];
+  var simpleSelector4 = selector4.simpleSelector;
+  expect(simpleSelector4 is IdSelector, true);
+  expect(selector4.isCombinatorDescendant, true);
+  expect(simpleSelector4.name, "elemId");
+}
+
+void testWildcard() {
+  var errors = [];
+  var input = "* {}";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  var ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  var simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 1);
+  var simpSelector = simpleSeqs[0].simpleSelector;
+  expect(simpSelector is ElementSelector, true);
+  expect(simpleSeqs[0].isCombinatorNone, true);
+  expect(simpSelector.isWildcard, true);
+  expect(simpSelector.name, "*");
+
+  input = "*.foobar {}";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 2);
+
+  var selector0 = simpleSeqs[0];
+  var simpleSelector0 = selector0.simpleSelector;
+  expect(simpleSelector0 is ElementSelector, true);
+  expect(selector0.isCombinatorNone, true);
+  expect(simpleSelector0.isWildcard, true);
+  expect(simpleSelector0.name, "*");
+
+  var selector1 = simpleSeqs[1];
+  var simpleSelector1 = selector1.simpleSelector;
+  expect(simpleSelector1 is ClassSelector, true);
+  expect(selector1.isCombinatorNone, true);
+  expect(simpleSelector1.name, "foobar");
+
+  input = "myNs|*.foobar {}";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels.length, 1);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 2);
+
+  selector0 = simpleSeqs[0];
+  simpleSelector0 = selector0.simpleSelector;
+  expect(simpleSelector0 is NamespaceSelector, true);
+  expect(selector0.isCombinatorNone, true);
+  expect(simpleSelector0.isNamespaceWildcard, false);
+  var elementSelector = simpleSelector0.nameAsSimpleSelector;
+  expect("myNs", simpleSelector0.namespace);
+  expect(elementSelector.isWildcard, true);
+  expect("*", elementSelector.name);
+
+  selector1 = simpleSeqs[1];
+  simpleSelector1 = selector1.simpleSelector;
+  expect(simpleSelector1 is ClassSelector, true);
+  expect(selector1.isCombinatorNone, true);
+  expect("foobar", simpleSelector1.name);
+
+  input = "*|*.foobar {}";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(stylesheet.topLevels[0] is RuleSet, true);
+  ruleset = stylesheet.topLevels[0];
+  expect(ruleset.selectorGroup.selectors.length, 1);
+  expect(ruleset.declarationGroup.declarations.length, 0);
+
+  simpleSeqs = ruleset.selectorGroup.selectors[0].simpleSelectorSequences;
+
+  expect(simpleSeqs.length, 2);
+
+  selector0 = simpleSeqs[0];
+  simpleSelector0 = selector0.simpleSelector;
+  expect(simpleSelector0 is NamespaceSelector, true);
+  expect(selector0.isCombinatorNone, true);
+  expect(simpleSelector0.isNamespaceWildcard, true);
+  expect("*", simpleSelector0.namespace);
+  elementSelector = simpleSelector0.nameAsSimpleSelector;
+  expect(elementSelector.isWildcard, true);
+  expect("*", elementSelector.name);
+
+  selector1 = simpleSeqs[1];
+  simpleSelector1 = selector1.simpleSelector;
+  expect(simpleSelector1 is ClassSelector, true);
+  expect(selector1.isCombinatorNone, true);
+  expect("foobar", simpleSelector1.name);
+}
+
+/** Test List<int> as input to parser. */
+void testArrayOfChars() {
+  var errors = [];
+  var input = '<![CDATA[.foo { '
+      'color: red; left: 20px; top: 20px; width: 100px; height:200px'
+    '}'
+    '#div {'
+      'color : #00F578; border-color: #878787;'
+    '}]]>';
+
+  var stylesheet = parse(encodeUtf8(input), errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  expect(prettyPrint(stylesheet), r'''
+.foo {
+  color: #f00;
+  left: 20px;
+  top: 20px;
+  width: 100px;
+  height: 200px;
+}
+#div {
+  color: #00F578;
+  border-color: #878787;
+}''');
+}
+
+void testPseudo() {
+  var errors = [];
+
+  final input = r'''
+html:lang(fr-ca) { quotes: '" ' ' "' }
+zoom: { }
+
+a:link { color: red }
+:link  { color: blue }
+
+a:focus { background: yellow }
+a:focus:hover { background: white }
+
+p.special:first-letter {color: #ffd800}
+
+p:not(#example){
+  background-color: yellow;
+}
+
+input:not([DISABLED]){
+  background-color: yellow;
+}
+
+html|*:not(:link):not(:visited) {
+  border: 1px solid black;
+}
+
+*:not(FOO) {
+  height: 20px;
+}
+
+*|*:not(*) {
+  color: orange;
+}
+
+*|*:not(:hover) {
+  color: magenta;
+}
+
+p:nth-child(3n-3) { }
+
+div:nth-child(2n) { color : red; }
+''';
+
+  var stylesheet = parseCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), r'''
+html:lang(fr-ca) {
+  quotes: "\" " " \"";
+}
+zoom {
+}
+a:link {
+  color: #f00;
+}
+:link {
+  color: #00f;
+}
+a:focus {
+  background: #ff0;
+}
+a:focus:hover {
+  background: #fff;
+}
+p.special:first-letter {
+  color: #ffd800;
+}
+p:not(#example) {
+  background-color: #ff0;
+}
+input:not([DISABLED]) {
+  background-color: #ff0;
+}
+html|*:not(:link):not(:visited) {
+  border: 1px solid #000;
+}
+*:not(FOO) {
+  height: 20px;
+}
+*|*:not(*) {
+  color: #ffa500;
+}
+*|*:not(:hover) {
+  color: #f0f;
+}
+p:nth-child(3n-3) {
+}
+div:nth-child(2n) {
+  color: #f00;
+}''');
+}
+
+void testAttribute() {
+  // TODO(terry): Implement
+}
+
+void testNegation() {
+  // TODO(terry): Implement
+}
+
+void testHost() {
+  var errors = [];
+  var input = '@host { '
+      ':scope {'
+        'white-space: nowrap;'
+        'overflow-style: marquee-line;'
+        'overflow-x: marquee;'
+      '}'
+      '* { color: red; }'
+      '*:hover { font-weight: bold; }'
+      ':nth-child(odd) { color: blue; }'
+    '}';
+  var stylesheet = parseCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), r'''
+@host {
+:scope {
+  white-space: nowrap;
+  overflow-style: marquee-line;
+  overflow-x: marquee;
+}
+* {
+  color: #f00;
+}
+*:hover {
+  font-weight: bold;
+}
+:nth-child(odd) {
+  color: #00f;
+}
+}''');
+}
+
+// TODO(terry): Move to emitter_test.dart when real emitter exist.
+void testEmitter() {
+  var errors = [];
+  var input = '.foo { '
+      'color: red; left: 20px; top: 20px; width: 100px; height:200px'
+    '}'
+    '#div {'
+      'color : #00F578; border-color: #878787;'
+    '}';
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  walkTree(stylesheet);
+
+  expect(prettyPrint(stylesheet), r'''
+.foo {
+  color: #f00;
+  left: 20px;
+  top: 20px;
+  width: 100px;
+  height: 200px;
+}
+#div {
+  color: #00F578;
+  border-color: #878787;
+}''');
+}
+
+main() {
+  test('Classes', testClass);
+  test('Classes 2', testClass2);
+  test('Ids', testId);
+  test('Elements', testElement);
+  test('Namespace', testNamespace);
+  test('Namespace 2', testNamespace2);
+  test('Selector Groups', testSelectorGroups);
+  test('Combinator', testCombinator);
+  test('Wildcards', testWildcard);
+  test('Pseudo', testPseudo);
+  test('Attributes', testAttribute);
+  test('Negation', testNegation);
+  test('@host', testHost);
+  test('Parse List<int> as input', testArrayOfChars);
+  test('Simple Emitter', testEmitter);
+}
diff --git a/test/declaration_test.dart b/test/declaration_test.dart
new file mode 100644
index 0000000..3d6912c
--- /dev/null
+++ b/test/declaration_test.dart
@@ -0,0 +1,1036 @@
+// 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 declaration_test;
+
+import 'package:unittest/unittest.dart';
+import 'testing.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+
+
+/** CSS compiler options no checks in in memory style sheet. */
+List options = ['--no-colors', 'memory'];
+
+void testSimpleTerms() {
+  var errors = [];
+  final String input = r'''
+@ import url("test.css");
+.foo {
+  background-color: #191919;
+  width: 10PX;
+  height: 22mM !important;
+  border-width: 20cm;
+  margin-width: 33%;
+  border-height: 30EM;
+  width: .6in;
+  length: 1.2in;
+  -web-stuff: -10Px;
+}''';
+  final String generated = r'''
+@import "test.css";
+.foo {
+  background-color: #191919;
+  width: 10px;
+  height: 22mm !important;
+  border-width: 20cm;
+  margin-width: 33%;
+  border-height: 30em;
+  width: .6in;
+  length: 1.2in;
+  -web-stuff: -10px;
+}''';
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  final String input2 = r'''
+* {
+  border-color: green;
+}''';
+  final String generated2 = r'''
+* {
+  border-color: #008000;
+}''';
+
+  stylesheet = parseCss(input2, errors: errors..clear());
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+}
+
+/**
+ * Declarations with comments, references with single-quotes, double-quotes,
+ * no quotes.  Hex values with # and letters, and functions (rgba, url, etc.)
+ */
+void testDeclarations() {
+  var errors = [];
+  final String input = r'''
+.more {
+  color: white;
+  color: black;
+  color: cyan;
+  color: red;
+  color: #aabbcc;  /* test -- 3 */
+  color: blue;
+  background-image: url(http://test.jpeg);
+  background-image: url("http://double_quote.html");
+  background-image: url('http://single_quote.html');
+  color: rgba(10,20,255);  <!-- test CDO/CDC  -->
+  color: #123aef;   /* hex # part integer and part identifier */
+}''';
+  final String generated = r'''
+.more {
+  color: #fff;
+  color: #000;
+  color: #0ff;
+  color: #f00;
+  color: #abc;
+  color: #00f;
+  background-image: url("http://test.jpeg");
+  background-image: url("http://double_quote.html");
+  background-image: url("http://single_quote.html");
+  color: rgba(10, 20, 255);
+  color: #123aef;
+}''';
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testIdentifiers() {
+  var errors = [];
+  final String input = r'''
+#da {
+  height: 100px;
+}
+#foo {
+  width: 10px;
+  color: #ff00cc;
+}
+''';
+  final String generated = r'''
+#da {
+  height: 100px;
+}
+#foo {
+  width: 10px;
+  color: #f0c;
+}''';
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testComposites() {
+  var errors = [];
+  final String input = r'''
+.xyzzy {
+  border: 10px 80px 90px 100px;
+  width: 99%;
+}
+@-webkit-keyframes pulsate {
+  0% {
+    -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+  }
+}''';
+  final String generated = r'''
+.xyzzy {
+  border: 10px 80px 90px 100px;
+  width: 99%;
+}
+@-webkit-keyframes pulsate {
+  0% {
+  -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+  }
+}''';
+
+  var stylesheet = parseCss(input, errors: errors);
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testUnits() {
+  var errors = [];
+  final String input = r'''
+#id-1 {
+  transition: color 0.4s;
+  animation-duration: 500ms;
+  top: 1em;
+  left: 200ex;
+  right: 300px;
+  bottom: 400cm;
+  border-width: 2.5mm;
+  margin-top: .5in;
+  margin-left: 5pc;
+  margin-right: 5ex;
+  margin-bottom: 5ch;
+  font-size: 10pt;
+  padding-top: 22rem;
+  padding-left: 33vw;
+  padding-right: 34vh;
+  padding-bottom: 3vmin;
+  transform: rotate(20deg);
+  voice-pitch: 10hz;
+}
+#id-2 {
+  left: 2fr;
+  font-size: 10vmax;
+  transform: rotatex(20rad);
+  voice-pitch: 10khz;
+  -web-kit-resolution: 2dpi;    /* Bogus property name testing dpi unit. */
+}
+#id-3 {
+  -web-kit-resolution: 3dpcm;   /* Bogus property name testing dpi unit. */
+  transform: rotatey(20grad);
+}
+#id-4 {
+  -web-kit-resolution: 4dppx;   /* Bogus property name testing dpi unit. */
+  transform: rotatez(20turn);
+}
+''';
+
+  final String generated = r'''
+#id-1 {
+  transition: color 0.4s;
+  animation-duration: 500ms;
+  top: 1em;
+  left: 200ex;
+  right: 300px;
+  bottom: 400cm;
+  border-width: 2.5mm;
+  margin-top: .5in;
+  margin-left: 5pc;
+  margin-right: 5ex;
+  margin-bottom: 5ch;
+  font-size: 10pt;
+  padding-top: 22rem;
+  padding-left: 33vw;
+  padding-right: 34vh;
+  padding-bottom: 3vmin;
+  transform: rotate(20deg);
+  voice-pitch: 10hz;
+}
+#id-2 {
+  left: 2fr;
+  font-size: 10vmax;
+  transform: rotatex(20rad);
+  voice-pitch: 10khz;
+  -web-kit-resolution: 2dpi;
+}
+#id-3 {
+  -web-kit-resolution: 3dpcm;
+  transform: rotatey(20grad);
+}
+#id-4 {
+  -web-kit-resolution: 4dppx;
+  transform: rotatez(20turn);
+}''';
+
+  var stylesheet = parseCss(input, errors: errors, opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testUnicode() {
+  var errors = [];
+  final String input = r'''
+.toggle:after {
+  content: '✔';
+  line-height: 43px;
+  font-size: 20px;
+  color: #d9d9d9;
+  text-shadow: 0 -1px 0 #bfbfbf;
+}
+''';
+
+  final String generated = r'''
+.toggle:after {
+  content: "✔";
+  line-height: 43px;
+  font-size: 20px;
+  color: #d9d9d9;
+  text-shadow: 0 -1px 0 #bfbfbf;
+}''';
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testNewerCss() {
+  var errors = [];
+  final String input = r'''
+@media screen,print {
+  .foobar_screen {
+    width: 10px;
+  }
+}
+@page {
+  height: 22px;
+  size: 3in 3in;
+}
+@page : left {
+  width: 10px;
+}
+@page bar : left { @top-left { margin: 8px; } }
+@charset "ISO-8859-1";
+@charset 'ASCII';''';
+
+  final String generated = r'''
+@media screen, print {
+.foobar_screen {
+  width: 10px;
+}
+}
+@page {
+  height: 22px;
+  size: 3in 3in;
+}
+@page:left {
+  width: 10px;
+}
+@page bar:left {
+@top-left {
+  margin: 8px;
+}
+}
+@charset "ISO-8859-1";
+@charset "ASCII";''';
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testMediaQueries() {
+  var errors = [];
+  String input = '''
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+  .todo-item .toggle {
+    background: none;
+  }
+  #todo-item .toggle {
+    height: 40px;
+  }
+}''';
+  String generated = '''
+@media screen AND (-webkit-min-device-pixel-ratio:0) {
+.todo-item .toggle {
+  background: none;
+}
+#todo-item .toggle {
+  height: 40px;
+}
+}''';
+
+  var stylesheet = parseCss(input, errors: errors, opts: options);
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  input = '''
+  @media handheld and (min-width: 20em), 
+         screen and (min-width: 20em) {
+    #id { color: red; }
+    .myclass { height: 20px; }
+  }
+  @media print and (min-resolution: 300dpi) {
+    #anotherId {
+      color: #fff;
+    }
+  }
+  @media print and (min-resolution: 280dpcm) {
+    #finalId {
+      color: #aaa;
+    }
+    .class2 {
+      border: 20px;
+    }
+  }''';
+  generated =
+    '''@media handheld AND (min-width:20em), screen AND (min-width:20em) {
+#id {
+  color: #f00;
+}
+.myclass {
+  height: 20px;
+}
+} @media print AND (min-resolution:300dpi) {
+#anotherId {
+  color: #fff;
+}
+} @media print AND (min-resolution:280dpcm) {
+#finalId {
+  color: #aaa;
+}
+.class2 {
+  border: 20px;
+}
+}''';
+
+  stylesheet = parseCss(input, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  input = '''
+@media only screen and (min-device-width: 4000px) and
+    (min-device-height: 2000px), screen (another: 100px) {
+      html {
+        font-size: 10em;
+      }
+    }''';
+  generated = '@media ONLY screen AND (min-device-width:4000px) '
+      'AND (min-device-height:2000px), screen (another:100px) {\n'
+      'html {\n  font-size: 10em;\n}\n}';
+
+  stylesheet = parseCss(input, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  input = '''
+@media screen,print (min-device-width: 4000px) and
+    (min-device-height: 2000px), screen (another: 100px) {
+      html {
+        font-size: 10em;
+      }
+    }''';
+  generated = '@media screen, print (min-device-width:4000px) AND '
+      '(min-device-height:2000px), screen (another:100px) {\n'
+      'html {\n  font-size: 10em;\n}\n}';
+
+  stylesheet = parseCss(input, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  input = '''
+@import "test.css" ONLY screen, NOT print (min-device-width: 4000px);''';
+  generated =
+      '@import "test.css" ONLY screen, NOT print (min-device-width:4000px);';
+
+  stylesheet = parseCss(input, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testFontFace() {
+  var errors = [];
+
+  final String input = '''
+@font-face {
+  font-family: BBCBengali;
+  src: url(fonts/BBCBengali.ttf) format("opentype");
+  unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???;
+}''';
+  final String generated = '''@font-face  {
+  font-family: BBCBengali;
+  src: url("fonts/BBCBengali.ttf") format("opentype");
+  unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???;
+}''';
+  var stylesheet = parseCss(input, errors: errors, opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  final String input1 = '''
+@font-face {
+  font-family: Gentium;
+  src: url(http://example.com/fonts/Gentium.ttf);
+  src: url(http://example.com/fonts/Gentium.ttf);
+}''';
+  final String generated1 = '''@font-face  {
+  font-family: Gentium;
+  src: url("http://example.com/fonts/Gentium.ttf");
+  src: url("http://example.com/fonts/Gentium.ttf");
+}''';
+
+  stylesheet = parseCss(input1, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated1);
+
+  final String input2 = '''
+@font-face {
+src: url(ideal-sans-serif.woff) format("woff"),
+     url(basic-sans-serif.ttf) format("opentype"),
+     local(Gentium Bold);
+}''';
+  final String generated2 =
+      '@font-face  {\n'
+      '  src: url("ideal-sans-serif.woff") '
+      'format("woff"), url("basic-sans-serif.ttf") '
+      'format("opentype"), local(Gentium Bold);\n}';
+
+  stylesheet = parseCss(input2, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+
+  final String input3 = '''@font-face {
+  font-family: MyGentium Text Ornaments;
+  src: local(Gentium Bold),   /* full font name */
+       local(Gentium-Bold),   /* Postscript name */
+       url(GentiumBold.ttf);  /* otherwise, download it */
+  font-weight: bold;
+}''';
+  final String generated3 = '''@font-face  {
+  font-family: MyGentium Text Ornaments;
+  src: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+  font-weight: bold;
+}''';
+
+  stylesheet = parseCss(input3, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated3);
+
+  final String input4 = '''
+@font-face {
+  font-family: STIXGeneral;
+  src: local(STIXGeneral), url(/stixfonts/STIXGeneral.otf);
+  unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+}''';
+  final String generated4 = '''@font-face  {
+  font-family: STIXGeneral;
+  src: local(STIXGeneral), url("/stixfonts/STIXGeneral.otf");
+  unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+}''';
+  stylesheet = parseCss(input4, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated4);
+}
+
+void testCssFile() {
+  var errors = [];
+  final String input = r'''
+@import 'simple.css'
+@import "test.css" print
+@import url(test.css) screen, print
+@import url(http://google.com/maps/maps.css);
+
+div[href^='test'] {
+  height: 10px;
+}
+
+@-webkit-keyframes pulsate {
+  from {
+    -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+  }
+  10% {
+    -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+  }
+  30% {
+    -webkit-transform: translate3d(0, 2, 0) scale(1.0);
+  }
+}
+
+.foobar {
+    grid-columns: 10px ("content" 1fr 10px)[4];
+}
+
+.test-background {
+  background:  url(http://www.foo.com/bar.png);
+}
+''';
+
+  final String generated =
+      '@import "simple.css"; '
+      '@import "test.css" print; '
+      '@import "test.css" screen, print; '
+      '@import "http://google.com/maps/maps.css";\n'
+      'div[href^="test"] {\n'
+      '  height: 10px;\n'
+      '}\n'
+      '@-webkit-keyframes pulsate {\n'
+      '  from {\n'
+      '  -webkit-transform: translate3d(0, 0, 0) scale(1.0);\n'
+      '  }\n'
+      '  10% {\n'
+      '  -webkit-transform: translate3d(0, 0, 0) scale(1.0);\n'
+      '  }\n'
+      '  30% {\n'
+      '  -webkit-transform: translate3d(0, 2, 0) scale(1.0);\n'
+      '  }\n'
+      '}\n'
+      '.foobar {\n'
+      '  grid-columns: 10px ("content" 1fr 10px) [4];\n'
+      '}\n'
+      '.test-background {\n'
+      '  background: url("http://www.foo.com/bar.png");\n'
+      '}';
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testCompactEmitter() {
+  var errors = [];
+
+  // Check !import compactly emitted.
+  final String input = r'''
+div {
+  color: green !important;
+}
+''';
+  final String generated = "div { color: green!important; }";
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(compactOuptut(stylesheet), generated);
+
+  // Check namespace directive compactly emitted.
+  final String input2 = "@namespace a url(http://www.example.org/a);";
+  final String generated2 = "@namespace a url(http://www.example.org/a);";
+
+  var stylesheet2 = parseCss(input2, errors: errors..clear());
+
+  expect(stylesheet2 != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(compactOuptut(stylesheet2), generated2);
+}
+
+void testNotSelectors() {
+  var errors = [];
+
+  final String input = r'''
+.details:not(.open-details) x-element,
+.details:not(.open-details) .summary {
+  overflow: hidden;
+}
+
+.details:not(.open-details) x-icon {
+  margin-left: 99px;
+}
+
+.kind-class .details:not(.open-details) x-icon {
+  margin-left: 0px;
+}
+
+.name {
+  margin-left: 0px;
+}
+
+.details:not(.open-details) .the-class {
+  width: 80px;
+}
+
+*:focus
+{
+  outline: none;
+}
+
+body > h2:not(:first-of-type):not(:last-of-type) {
+  color: red;
+}
+
+.details-1:not([DISABLED]) {
+  outline: none;
+}
+
+html|*:not(:link):not(:visited) {
+  width: 92%;
+}
+
+*|*:not(*) {
+  font-weight: bold;
+}
+
+*:not(:not([disabled])) { color: blue; }
+''';
+  final String generated = r'''
+.details:not(.open-details) x-element, .details:not(.open-details) .summary {
+  overflow: hidden;
+}
+.details:not(.open-details) x-icon {
+  margin-left: 99px;
+}
+.kind-class .details:not(.open-details) x-icon {
+  margin-left: 0px;
+}
+.name {
+  margin-left: 0px;
+}
+.details:not(.open-details) .the-class {
+  width: 80px;
+}
+*:focus {
+  outline: none;
+}
+body > h2:not(:first-of-type):not(:last-of-type) {
+  color: #f00;
+}
+.details-1:not([DISABLED]) {
+  outline: none;
+}
+html|*:not(:link):not(:visited) {
+  width: 92%;
+}
+*|*:not(*) {
+  font-weight: bold;
+}
+*:not(:not([disabled])) {
+  color: #00f;
+}''';
+
+  var stylesheet = parseCss(input, errors: errors, opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testIE() {
+  var errors = [];
+  final String input =
+".test {\n"
+"  filter: progid:DXImageTransform.Microsoft.gradient"
+"(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');\n"
+"}";
+  final String generated =
+".test {\n"
+"  filter: progid:DXImageTransform.Microsoft.gradient"
+"(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');\n"
+"}";
+
+  var stylesheet = parseCss(input,  errors: errors, opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  final String input2 =
+".test {\n"
+"  filter: progid:DXImageTransform.Microsoft.gradient"
+"(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670')\n"
+"        progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);\n"
+"}";
+
+  final String generated2 =
+".test {\n"
+"  filter: progid:DXImageTransform.Microsoft.gradient"
+"(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670')\n"
+"         progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);\n"
+"}";
+
+  stylesheet = parseCss(input2, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+
+  final String input3 = '''
+div {
+  filter: alpha(opacity=80);          /* IE7 and under */
+  -ms-filter: "Alpha(Opacity=40)";    /* IE8 and newer */
+
+  Filter: Blur(Add = 0, Direction = 225, Strength = 10);
+  Filter: FlipV;
+  Filter: Gray;
+  FILTER: Chroma(Color = #000000) Mask(Color=#00FF00);
+  Filter: Alpha(Opacity=100, FinishOpacity=0, Style=2, StartX=20, StartY=40, 
+      FinishX=0, FinishY=0) Wave(Add=0, Freq=5, LightStrength=20, 
+      Phase=220, Strength=10);
+}
+''';
+  final String generated3 = 'div {\n  filter: alpha(opacity=80);\n'
+      '  -ms-filter: "Alpha(Opacity=40)";\n'
+      '  Filter: Blur(Add = 0, Direction = 225, Strength = 10);\n'
+      '  Filter: FlipV;\n  Filter: Gray;\n'
+      '  FILTER: Chroma(Color = #000000)  Mask(Color=#00FF00);\n'
+      '  Filter: Alpha(Opacity=100, FinishOpacity=0, Style=2, '
+      'StartX=20, StartY=40, \n'
+      '      FinishX=0, FinishY=0)  Wave(Add=0, Freq=5, LightStrength=20, \n'
+      '      Phase=220, Strength=10);\n}';
+
+  stylesheet = parseCss(input3, errors: errors..clear(), opts: options);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated3);
+}
+
+/**
+ *  Test IE specific declaration syntax:
+ *    IE6 property name prefixed with _ (normal CSS property name can start
+ *    with an underscore).
+ *
+ *    IE7 or below property add asterisk before the CSS property.
+ *
+ *    IE8 or below add \9 at end of declaration expression e.g.,
+ *        background: red\9;
+ */
+void testIEDeclaration() {
+  var errors = [];
+
+  final input = '''
+.testIE-6 {
+  _zoom : 5;
+}
+.clearfix {
+  *zoom: 1;
+}
+audio, video {
+  display: inline-block;
+  *display: inline;
+  *zoom: 1;
+}
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+.uneditable-input:focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  outline: 0;
+  outline: thin dotted \\9; /* IE6-9 */
+}
+
+input[type="radio"], input[type="checkbox"] {
+  margin-top: 1px \\9;
+  *margin-top: 0;
+}
+
+input.search-query {
+  padding-right: 14px;
+  padding-right: 4px \\9;
+  padding-left: 14px;
+  padding-left: 4px \\9; /* IE7-8 no border-radius, don't indent padding. */
+}
+
+.btn.active {
+  background-color: #cccccc \\9;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+from {
+background-position: 40px 0;
+}
+to {
+background-position: 0 0;
+}
+}
+
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-ms-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}''';
+
+  final generated = '''.testIE-6 {
+  _zoom: 5;
+}
+.clearfix {
+  *zoom: 1;
+}
+audio, video {
+  display: inline-block;
+  *display: inline;
+  *zoom: 1;
+}
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+.uneditable-input:focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  outline: 0;
+  outline: thin dotted \\9;
+}
+input[type="radio"], input[type="checkbox"] {
+  margin-top: 1px \\9;
+  *margin-top: 0;
+}
+input.search-query {
+  padding-right: 14px;
+  padding-right: 4px \\9;
+  padding-left: 14px;
+  padding-left: 4px \\9;
+}
+.btn.active {
+  background-color: #ccc \\9;
+}
+@-webkit-keyframes progress-bar-stripes {
+  from {
+  background-position: 40px 0;
+  }
+  to {
+  background-position: 0 0;
+  }
+}
+@-moz-keyframes progress-bar-stripes {
+  from {
+  background-position: 40px 0;
+  }
+  to {
+  background-position: 0 0;
+  }
+}
+@keyframes progress-bar-stripes {
+  from {
+  background-position: 40px 0;
+  }
+  to {
+  background-position: 0 0;
+  }
+}
+@-o-keyframes progress-bar-stripes {
+  from {
+  background-position: 40px 0;
+  }
+  to {
+  background-position: 0 0;
+  }
+}
+@keyframes progress-bar-stripes {
+  from {
+  background-position: 40px 0;
+  }
+  to {
+  background-position: 0 0;
+  }
+}''';
+
+  var stylesheet = parseCss(input, errors: errors, opts: options);
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void testHangs() {
+  final optionErrors = ['--no-colors', '--warnings_as_errors', 'memory'];
+  var errors = [];
+
+  // Bad hexvalue had caused a hang in processTerm.
+  final input = r'''#a { color: #ebebeburl(0/IE8+9+); }''';
+  var stylesheet = parseCss(input, errors: errors, opts: optionErrors);
+
+  expect(stylesheet != null, true);
+  expect(errors.length, 3, reason: errors.toString());
+
+  var errorMessage = errors[0];
+  expect(errorMessage.message, contains('Bad hex number'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 0);
+  expect(errorMessage.span.start.column, 12);
+  expect(errorMessage.span.text, '#ebebeburl');
+
+  errorMessage = errors[1];
+  expect(errorMessage.message, contains('expected }, but found +'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 0);
+  expect(errorMessage.span.start.column, 30);
+  expect(errorMessage.span.text, '+');
+
+  errorMessage = errors[2];
+  expect(errorMessage.message, contains('premature end of file unknown CSS'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 0);
+  expect(errorMessage.span.start.column, 31);
+  expect(errorMessage.span.text, ')');
+
+  // Missing closing parenthesis for keyframes.
+  final input2 = r'''@-ms-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+''';
+
+  stylesheet = parseCss(input2, errors: errors..clear(), opts: optionErrors);
+
+  expect(stylesheet != null, true);
+
+  expect(errors.length, 1, reason: errors.toString());
+
+  errorMessage = errors[0];
+  expect(errorMessage.message, contains('unexpected end of file'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 7);
+  expect(errorMessage.span.start.column, 0);
+  expect(errorMessage.span.text, '');
+}
+
+main() {
+  test('Simple Terms', testSimpleTerms);
+  test('Declarations', testDeclarations);
+  test('Identifiers', testIdentifiers);
+  test('Composites', testComposites);
+  test('Units', testUnits);
+  test('Unicode', testUnicode);
+  test('Newer CSS', testNewerCss);
+  test('Media Queries', testMediaQueries);
+  test('Font-Face', testFontFace);
+  test('CSS file', testCssFile);
+  test('Compact Emitter', testCompactEmitter);
+  test('Selector Negation', testNotSelectors);
+  test('IE stuff', testIE);
+  test('IE declaration syntax', testIEDeclaration);
+  test('Hanging bugs', testHangs);
+}
diff --git a/test/error_test.dart b/test/error_test.dart
new file mode 100644
index 0000000..98acffc
--- /dev/null
+++ b/test/error_test.dart
@@ -0,0 +1,355 @@
+// Copyright (c) 2013, 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 error_test;
+
+import 'package:unittest/unittest.dart';
+import 'testing.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'package:csslib/src/messages.dart';
+
+/**
+ * Test for unsupported font-weights values of bolder, lighter and inherit.
+ */
+void testUnsupportedFontWeights() {
+  var errors = [];
+
+  // TODO(terry): Need to support bolder.
+  // font-weight value bolder.
+  var input = ".foobar { font-weight: bolder; }";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:24: Unknown property value bolder
+.foobar { font-weight: bolder; }
+                       ^^^^^^''');
+  expect(stylesheet != null, true);
+
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  font-weight: bolder;
+}''');
+
+  // TODO(terry): Need to support lighter.
+  // font-weight value lighter.
+  input = ".foobar { font-weight: lighter; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:24: Unknown property value lighter
+.foobar { font-weight: lighter; }
+                       ^^^^^^^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  font-weight: lighter;
+}''');
+
+  // TODO(terry): Need to support inherit.
+  // font-weight value inherit.
+  input = ".foobar { font-weight: inherit; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:24: Unknown property value inherit
+.foobar { font-weight: inherit; }
+                       ^^^^^^^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  font-weight: inherit;
+}''');
+}
+
+/**
+ * Test for unsupported line-height values of units other than px, pt and
+ * inherit.
+ */
+void testUnsupportedLineHeights() {
+  var errors = [];
+
+  // line-height value in percentge unit.
+  var input = ".foobar { line-height: 120%; }";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:24: Unexpected value for line-height
+.foobar { line-height: 120%; }
+                       ^^^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  line-height: 120%;
+}''');
+
+  // TODO(terry): Need to support all units.
+  // line-height value in cm unit.
+  input = ".foobar { line-height: 20cm; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:24: Unexpected unit for line-height
+.foobar { line-height: 20cm; }
+                       ^^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  line-height: 20cm;
+}''');
+
+  // TODO(terry): Need to support inherit.
+  // line-height value inherit.
+  input = ".foobar { line-height: inherit; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:24: Unknown property value inherit
+.foobar { line-height: inherit; }
+                       ^^^^^^^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  line-height: inherit;
+}''');
+}
+
+/** Test for bad selectors. */
+void testBadSelectors() {
+  var errors = [];
+
+  // Invalid id selector.
+  var input = "# foo { color: #ff00ff; }";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:1: Not a valid ID selector expected #id
+# foo { color: #ff00ff; }
+^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+# foo {
+  color: #f0f;
+}''');
+
+  // Invalid class selector.
+  input = ". foo { color: #ff00ff; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:1: Not a valid class selector expected .className
+. foo { color: #ff00ff; }
+^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+. foo {
+  color: #f0f;
+}''');
+}
+
+/** Test for bad hex values. */
+void testBadHexValues() {
+  var errors = [];
+
+  // Invalid hex value.
+  var input = ".foobar { color: #AH787; }";
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:18: Bad hex number
+.foobar { color: #AH787; }
+                 ^^^^^^''');
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  color: #AH787;
+}''');
+
+  // Bad color constant.
+  input = ".foobar { color: redder; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:18: Unknown property value redder
+.foobar { color: redder; }
+                 ^^^^^^''');
+
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  color: redder;
+}''');
+
+  // Bad hex color #<space>ffffff.
+  input = ".foobar { color: # ffffff; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:18: Expected hex number
+.foobar { color: # ffffff; }
+                 ^''');
+
+  expect(stylesheet != null, true);
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  color: # ffffff;
+}''');
+
+  // Bad hex color #<space>123fff.
+  input = ".foobar { color: # 123fff; }";
+  stylesheet = parseCss(input, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:18: Expected hex number
+.foobar { color: # 123fff; }
+                 ^''');
+
+  expect(stylesheet != null, true);
+
+  // Formating is off with an extra space.  However, the entire value is bad
+  // and isn't processed anyway.
+  expect(prettyPrint(stylesheet), r'''
+.foobar {
+  color: # 123 fff;
+}''');
+
+}
+
+void testBadUnicode() {
+  var errors = [];
+  final String input = '''
+@font-face {
+  src: url(fonts/BBCBengali.ttf) format("opentype");
+  unicode-range: U+400-200;
+}''';
+
+  var stylesheet = parseCss(input, errors: errors);
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(),
+      'error :3:20: unicode first range can not be greater than last\n'
+      '  unicode-range: U+400-200;\n'
+      '                   ^^^^^^^');
+
+  final String input2 = '''
+@font-face {
+  src: url(fonts/BBCBengali.ttf) format("opentype");
+  unicode-range: U+12FFFF;
+}''';
+
+  stylesheet = parseCss(input2, errors: errors..clear());
+
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(),
+      'error :3:20: unicode range must be less than 10FFFF\n'
+      '  unicode-range: U+12FFFF;\n'
+      '                   ^^^^^^');
+}
+
+void testBadNesting() {
+  var errors = [];
+
+  // Test for bad declaration in a nested rule.
+  final String input = '''
+div {
+  width: 20px;
+  span + ul { color: blue; }
+  span + ul > #aaaa {
+    color: #ffghghgh;
+  }
+  background-color: red;
+}
+''';
+
+  var stylesheet = parseCss(input, errors: errors);
+  expect(errors.length, 1);
+  var errorMessage = messages.messages[0];
+  expect(errorMessage.message, contains('Bad hex number'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 4);
+  expect(errorMessage.span.start.column, 11);
+  expect(errorMessage.span.text, '#ffghghgh');
+
+  // Test for bad selector syntax.
+  final String input2 = '''
+div {
+  span + ul #aaaa > (3333)  {
+    color: #ffghghgh;
+  }
+}
+''';
+  var stylesheet2 = parseCss(input2, errors: errors..clear());
+  expect(errors.length, 4);
+  errorMessage = messages.messages[0];
+  expect(errorMessage.message, contains(':, but found +'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 1);
+  expect(errorMessage.span.start.column, 7);
+  expect(errorMessage.span.text, '+');
+
+  errorMessage = messages.messages[1];
+  expect(errorMessage.message, contains('Unknown property value ul'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 1);
+  expect(errorMessage.span.start.column, 9);
+  expect(errorMessage.span.text, 'ul');
+
+  errorMessage = messages.messages[2];
+  expect(errorMessage.message, contains('expected }, but found >'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 1);
+  expect(errorMessage.span.start.column, 18);
+  expect(errorMessage.span.text, '>');
+
+  errorMessage = messages.messages[3];
+  expect(errorMessage.message, contains('premature end of file unknown CSS'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 1);
+  expect(errorMessage.span.start.column, 20);
+  expect(errorMessage.span.text, '(');
+
+  // Test for missing close braces and bad declaration.
+  final String input3 = '''
+div {
+  span {
+    color: #green;
+}
+''';
+  var stylesheet3 = parseCss(input3, errors: errors..clear());
+  expect(errors.length, 2);
+  errorMessage = messages.messages[0];
+  expect(errorMessage.message, contains('Bad hex number'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 2);
+  expect(errorMessage.span.start.column, 11);
+  expect(errorMessage.span.text, '#green');
+
+  errorMessage = messages.messages[1];
+  expect(errorMessage.message, contains('expected }, but found end of file'));
+  expect(errorMessage.span, isNotNull);
+  expect(errorMessage.span.start.line, 3);
+  expect(errorMessage.span.start.column, 1);
+  expect(errorMessage.span.text, '\n');
+}
+
+main() {
+  test('font-weight value errors', testUnsupportedFontWeights);
+  test('line-height value errors', testUnsupportedLineHeights);
+  test('bad selectors', testBadSelectors);
+  test('bad Hex values', testBadHexValues);
+  test('bad unicode ranges', testBadUnicode);
+  test('nested rules', testBadNesting);
+}
diff --git a/test/nested_test.dart b/test/nested_test.dart
new file mode 100644
index 0000000..e6b0025
--- /dev/null
+++ b/test/nested_test.dart
@@ -0,0 +1,614 @@
+// Copyright (c) 2013, 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 nested_test;
+
+import 'dart:utf';
+import 'package:unittest/unittest.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'testing.dart';
+
+List optionsCss = ['--no-colors', 'memory'];
+
+compileAndValidate(String input, String generated) {
+  var errors = [];
+  var stylesheet = compileCss(input, errors: errors, opts: optionsCss);
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+selectorVariations() {
+  final input1 = r'''html { color: red; }''';
+  final generated1 = r'''html {
+  color: #f00;
+}''';
+  compileAndValidate(input1, generated1);
+
+  final input2 = r'''button { span { height: 200 } }''';
+  final generated2 = r'''button {
+}
+button span {
+  height: 200;
+}''';
+  compileAndValidate(input2, generated2);
+
+  final input3 = r'''div { color: red; } button { span { height: 200 } }''';
+  final generated3 = r'''div {
+  color: #f00;
+}
+button {
+}
+button span {
+  height: 200;
+}''';
+  compileAndValidate(input3, generated3);
+
+  final input4 = r'''#header { color: red; h1 { font-size: 26px; } }''';
+  final generated4 = r'''#header {
+  color: #f00;
+}
+#header h1 {
+  font-size: 26px;
+}''';
+  compileAndValidate(input4, generated4);
+
+  final input5 = r'''
+#header {
+  color: red;
+  h1 { font-size: 26px; }
+  background-color: blue;
+}''';
+  final generated5 = r'''#header {
+  color: #f00;
+  background-color: #00f;
+}
+#header h1 {
+  font-size: 26px;
+}''';
+  compileAndValidate(input5, generated5);
+
+  final input6 = r'''html { body {color: red; }}''';
+  final generated6 = r'''html {
+}
+html body {
+  color: #f00;
+}''';
+  compileAndValidate(input6, generated6);
+
+  final input7 = r'''html body {color: red; }''';
+  final generated7 = r'''html body {
+  color: #f00;
+}''';
+  compileAndValidate(input7, generated7);
+
+  final input8 = r'''
+html, body { color: red; }
+button { height: 200 }
+body { width: 300px; }''';
+  final generated8 = r'''html, body {
+  color: #f00;
+}
+button {
+  height: 200;
+}
+body {
+  width: 300px;
+}''';
+  compileAndValidate(input8, generated8);
+
+  final input9 = '''
+html, body {
+  color: red;
+  button { height: 200 }
+  div { width: 300px; }
+}''';
+  final generated9 = r'''html, body {
+  color: #f00;
+}
+html button, body button {
+  height: 200;
+}
+html div, body div {
+  width: 300px;
+}''';
+  compileAndValidate(input9, generated9);
+
+  final input10 = '''
+html {
+  color: red;
+  button, div { height: 200 }
+  body { width: 300px; }
+}''';
+  final generated10 = r'''html {
+  color: #f00;
+}
+html button, html div {
+  height: 200;
+}
+html body {
+  width: 300px;
+}''';
+  compileAndValidate(input10, generated10);
+
+  final input11 = '''
+html, body {
+  color: red;
+  button, div { height: 200 }
+  table { width: 300px; }
+}''';
+  final generated11 = r'''html, body {
+  color: #f00;
+}
+html button, body button, html div, body div {
+  height: 200;
+}
+html table, body table {
+  width: 300px;
+}''';
+  compileAndValidate(input11, generated11);
+
+  final input12 = '''
+html, body {
+  color: red;
+  button, div {
+    span, a, ul { height: 200 }
+  }
+  table { width: 300px; }
+}''';
+  final generated12 = r'''html, body {
+  color: #f00;
+}
+'''
+'html button span, body button span, html div span, body div span, '
+'html button a, body button a, html div a, body div a, html button ul, '
+r'''body button ul, html div ul, body div ul {
+  height: 200;
+}
+html table, body table {
+  width: 300px;
+}''';
+  compileAndValidate(input12, generated12);
+
+  final input13 = r'''
+#header {
+  div {
+  width: 100px;
+  a { height: 200px; }
+  }
+  color: blue;
+}
+span { color: #1f1f1f; }
+''';
+  final generated13 = r'''#header {
+  color: #00f;
+}
+#header div {
+  width: 100px;
+}
+#header div a {
+  height: 200px;
+}
+span {
+  color: #1f1f1f;
+}''';
+  compileAndValidate(input13, generated13);
+}
+
+void simpleNest() {
+  final errors = [];
+  final input = '''
+div span { color: green; }
+#header {
+  color: red;
+  h1 {
+    font-size: 26px;
+    font-weight: bold;
+  }
+  p {
+    font-size: 12px;
+    a {
+      text-decoration: none;
+    }
+  }
+  background-color: blue;
+}
+div > span[attr="foo"] { color: yellow; }
+''';
+
+  final generated =
+r'''div span {
+  color: #008000;
+}
+#header {
+  color: #f00;
+  background-color: #00f;
+}
+#header h1 {
+  font-size: 26px;
+  font-weight: bold;
+}
+#header p {
+  font-size: 12px;
+}
+#header p a {
+  text-decoration: none;
+}
+div > span[attr="foo"] {
+  color: #ff0;
+}''';
+  compileAndValidate(input, generated);
+}
+
+void complexNest() {
+  final errors = [];
+  final input = '''
+@font-face  { font-family: arial; }
+div { color: #f0f0f0; }
+#header + div {
+  color: url(abc.png);
+  *[attr="bar"] {
+    font-size: 26px;
+    font-weight: bold;
+  }
+  p~ul {
+    font-size: 12px;
+    :not(p) {
+      text-decoration: none;
+      div > span[attr="foo"] { color: yellow; }
+    }
+  }
+  background-color: blue;
+  span {
+    color: red;
+    .one { color: blue; }
+    .two { color: green; }
+    .three { color: yellow; }
+    .four {
+       .four-1 { background-color: #00000f; }
+       .four-2 { background-color: #0000ff; }
+       .four-3 { background-color: #000fff; }
+       .four-4 {
+         height: 44px;
+         .four-4-1 { height: 10px; }
+         .four-4-2 { height: 20px; }
+         .four-4-3 { height: 30px; }
+         width: 44px;
+       }
+    }
+  }
+}
+span { color: #1f1f2f; }
+''';
+
+  final generated = r'''@font-face  {
+  font-family: arial;
+}
+div {
+  color: #f0f0f0;
+}
+#header + div {
+  color: url("abc.png");
+  background-color: #00f;
+}
+#header + div *[attr="bar"] {
+  font-size: 26px;
+  font-weight: bold;
+}
+#header + div p ~ ul {
+  font-size: 12px;
+}
+#header + div p ~ ul :not(p) {
+  text-decoration: none;
+}
+#header + div p ~ ul :not(p) div > span[attr="foo"] {
+  color: #ff0;
+}
+#header + div span {
+  color: #f00;
+}
+#header + div span .one {
+  color: #00f;
+}
+#header + div span .two {
+  color: #008000;
+}
+#header + div span .three {
+  color: #ff0;
+}
+#header + div span .four .four-1 {
+  background-color: #00000f;
+}
+#header + div span .four .four-2 {
+  background-color: #00f;
+}
+#header + div span .four .four-3 {
+  background-color: #000fff;
+}
+#header + div span .four .four-4 {
+  height: 44px;
+  width: 44px;
+}
+#header + div span .four .four-4 .four-4-1 {
+  height: 10px;
+}
+#header + div span .four .four-4 .four-4-2 {
+  height: 20px;
+}
+#header + div span .four .four-4 .four-4-3 {
+  height: 30px;
+}
+span {
+  color: #1f1f2f;
+}''';
+
+  compileAndValidate(input, generated);
+}
+
+void mediaNesting() {
+  var errors = [];
+
+  final input = r'''
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+  #toggle-all {
+    image: url(test.jpb);
+    div, table {
+      background: none;
+      a { width: 100px; }
+    }
+    color: red;
+  }
+}
+''';
+  final generated = r'''@media screen AND (-webkit-min-device-pixel-ratio:0) {
+#toggle-all {
+  image: url("test.jpb");
+  color: #f00;
+}
+#toggle-all div, #toggle-all table {
+  background: none;
+}
+#toggle-all div a, #toggle-all table a {
+  width: 100px;
+}
+}''';
+
+  compileAndValidate(input, generated);
+}
+
+void simpleThis() {
+  final errors = [];
+  final input = '''#header {
+  h1 {
+    font-size: 26px;
+    font-weight: bold;
+  }
+  p { font-size: 12px;
+    a { text-decoration: none;
+      &:hover { border-width: 1px }
+    }
+  }
+}
+''';
+
+  final generated = r'''#header {
+}
+#header h1 {
+  font-size: 26px;
+  font-weight: bold;
+}
+#header p {
+  font-size: 12px;
+}
+#header p a {
+  text-decoration: none;
+}
+#header p a:hover {
+  border-width: 1px;
+}''';
+
+  compileAndValidate(input, generated);
+}
+
+void complexThis() {
+  var errors = [];
+
+  final input1 = r'''
+.light {
+  .leftCol {
+    .textLink {
+      color: fooL1;
+      &:hover { color: barL1;}
+    } 
+    .picLink {
+      background-image: url(/fooL1.jpg);
+      &:hover { background-image: url(/barL1.jpg);}
+    }
+    .textWithIconLink {
+      color: fooL2;
+      background-image: url(/fooL2.jpg);
+      &:hover { color: barL2; background-image: url(/barL2.jpg);}
+    }
+  }   
+}''';
+
+  final generated1 = r'''.light {
+}
+.light .leftCol .textLink {
+  color: fooL1;
+}
+.light .leftCol .textLink:hover {
+  color: barL1;
+}
+.light .leftCol .picLink {
+  background-image: url("/fooL1.jpg");
+}
+.light .leftCol .picLink:hover {
+  background-image: url("/barL1.jpg");
+}
+.light .leftCol .textWithIconLink {
+  color: fooL2;
+  background-image: url("/fooL2.jpg");
+}
+.light .leftCol .textWithIconLink:hover {
+  color: barL2;
+  background-image: url("/barL2.jpg");
+}''';
+
+  compileAndValidate(input1, generated1);
+
+  final input2 = r'''
+.textLink {
+  .light .leftCol & {
+    color: fooL1;
+    &:hover { color: barL1; }
+  }      
+  .light .rightCol & {
+    color: fooL3;
+    &:hover { color: barL3; }
+  } 
+}''';
+
+  final generated2 = r'''
+.textLink {
+}
+.light .leftCol .textLink {
+  color: fooL1;
+}
+.light .leftCol .textLink:hover {
+  color: barL1;
+}
+.light .rightCol .textLink {
+  color: fooL3;
+}
+.light .rightCol .textLink:hover {
+  color: barL3;
+}''';
+
+  compileAndValidate(input2, generated2);
+}
+
+variationsThis() {
+  var errors = [];
+
+  final input1 = r'''
+.textLink {
+  a {
+    light .leftCol & {
+      color: red;
+    }
+  }
+}''';
+  final generated1 = r'''.textLink {
+}
+light .leftCol .textLink a {
+  color: #f00;
+}''';
+
+  compileAndValidate(input1, generated1);
+
+  final input2 = r'''.textLink {
+  a {
+    & light .leftCol & {
+      color: red;
+    }
+  }
+}''';
+  final generated2 = r'''.textLink {
+}
+.textLink a light .leftCol .textLink a {
+  color: #f00;
+}''';
+  compileAndValidate(input2, generated2);
+
+  final input3 = r'''
+.textLink {
+  a {
+    & light .leftCol { color: red; }
+  }
+}''';
+  final generated3 = r'''.textLink {
+}
+.textLink a light .leftCol {
+  color: #f00;
+}''';
+  compileAndValidate(input3, generated3);
+
+  final input4 = r'''
+.textLink {
+  a {
+    & light .leftCol { color: red; }
+    &:hover { width: 100px; }
+  }
+}''';
+  final generated4 = r'''.textLink {
+}
+.textLink a light .leftCol {
+  color: #f00;
+}
+.textLink a:hover {
+  width: 100px;
+}''';
+  compileAndValidate(input4, generated4);
+
+  final input5 = r'''.textLink { a { &:hover { color: red; } } }''';
+  final generated5 = r'''.textLink {
+}
+.textLink a:hover {
+  color: #f00;
+}''';
+
+  compileAndValidate(input5, generated5);
+
+  final input6 = r'''.textLink { &:hover { color: red; } }''';
+  final generated6 = r'''.textLink {
+}
+.textLink:hover {
+  color: #f00;
+}''';
+  compileAndValidate(input6, generated6);
+
+  final input7 = r'''.textLink { a { & + & { color: red; } } }''';
+  final generated7 = r'''.textLink {
+}
+.textLink a + .textLink a {
+  color: #f00;
+}''';
+  compileAndValidate(input7, generated7);
+
+  final input8 = r'''.textLink { a { & { color: red; } } }''';
+  final generated8 = r'''.textLink {
+}
+.textLink a {
+  color: #f00;
+}''';
+  compileAndValidate(input8, generated8);
+
+  final input9 = r'''.textLink { a { & ~ & { color: red; } } }''';
+  final generated9 = r'''.textLink {
+}
+.textLink a ~ .textLink a {
+  color: #f00;
+}''';
+  compileAndValidate(input9, generated9);
+
+  final input10 = r'''.textLink { a { & & { color: red; } } }''';
+  final generated10 = r'''.textLink {
+}
+.textLink a .textLink a {
+  color: #f00;
+}''';
+  compileAndValidate(input10, generated10);
+}
+
+main() {
+  test('Selector and Nested Variations', selectorVariations);
+  test('Simple nesting', simpleNest);
+  test('Complex nesting', complexNest);
+  test('@media nesting', mediaNesting);
+  test('Simple &', simpleThis);
+  test("Variations &", variationsThis);
+  test('Complex &', complexThis);
+}
diff --git a/test/run.sh b/test/run.sh
new file mode 100755
index 0000000..7d26209
--- /dev/null
+++ b/test/run.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# 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.
+
+# Usage: call directly in the commandline as test/run.sh ensuring that you have
+# 'dart' in your path. Filter tests by passing a pattern as an argument to this
+# script.
+
+# TODO(sigmund): replace with a real test runner
+
+# bail on error
+set -e
+
+# print commands executed by this script
+# set -x
+
+DIR=$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd )
+DART_FLAGS="--checked"
+TEST_PATTERN=$1
+
+if [[ ($TEST_PATTERN == "") ]]; then
+  # Note: dart_analyzer needs to be run from the root directory for proper path
+  # canonicalization.
+  pushd $DIR/.. &>/dev/null
+  echo Analyzing compiler for warnings or type errors
+  dartanalyzer --fatal-warnings --fatal-type-errors bin/css.dart
+  popd &>/dev/null
+fi
+ 
+pushd $DIR &>/dev/null
+if [[ ($TEST_PATTERN == "canary") || ($TEST_PATTERN = "") ]]; then
+  dart $DART_FLAGS run_all.dart
+else
+  dart $DART_FLAGS run_all.dart $TEST_PATTERN
+fi
+popd &>/dev/null
+
+echo All tests completed.
diff --git a/test/run_all.dart b/test/run_all.dart
new file mode 100644
index 0000000..517d5a0
--- /dev/null
+++ b/test/run_all.dart
@@ -0,0 +1,39 @@
+// 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.
+
+/**
+ * This is a helper for run.sh. We try to run all of the Dart code in one
+ * instance of the Dart VM to reduce warm-up time.
+ */
+library run_impl;
+
+import 'dart:io';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'testing.dart';
+
+import 'compiler_test.dart' as compiler_test;
+import 'declaration_test.dart' as declaration_test;
+import 'var_test.dart' as var_test;
+import 'nested_test.dart' as nested_test;
+import 'error_test.dart' as error_test;
+import 'selector_test.dart' as selector_test;
+import 'visitor_test.dart' as visitor_test;
+
+main() {
+  var args = new Options().arguments;
+
+  var pattern = new RegExp(args.length > 0 ? args[0] : '.');
+
+  useCompactVMConfiguration();
+  useMockMessages();
+
+  if (pattern.hasMatch('compiler_test.dart')) compiler_test.main();
+  if (pattern.hasMatch('declaration_test.dart')) declaration_test.main();
+  if (pattern.hasMatch('var_test.dart')) var_test.main();
+  if (pattern.hasMatch('nested_test.dart')) nested_test.main();
+  if (pattern.hasMatch('selector_test.dart')) selector_test.main();
+  if (pattern.hasMatch('visitor_test.dart')) visitor_test.main();
+  if (pattern.hasMatch('error_test.dart')) error_test.main();
+}
diff --git a/test/selector_test.dart b/test/selector_test.dart
new file mode 100644
index 0000000..ad015bb
--- /dev/null
+++ b/test/selector_test.dart
@@ -0,0 +1,67 @@
+// 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 selector_test;
+
+import 'package:unittest/unittest.dart';
+import 'testing.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+
+void testSelectorSuccesses() {
+  var errors = [];
+  var selectorAst = selector('#div .foo', errors: errors);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('#div .foo', compactOuptut(selectorAst));
+
+  // Valid selectors for class names.
+  selectorAst = selector('.foo', errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('.foo', compactOuptut(selectorAst));
+
+  selectorAst = selector('.foobar .xyzzy', errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('.foobar .xyzzy', compactOuptut(selectorAst));
+
+  selectorAst = selector('.foobar .a-story .xyzzy', errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('.foobar .a-story .xyzzy', compactOuptut(selectorAst));
+
+  selectorAst = selector('.foobar .xyzzy .a-story .b-story',
+      errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('.foobar .xyzzy .a-story .b-story', compactOuptut(selectorAst));
+
+  // Valid selectors for element IDs.
+  selectorAst = selector('#id1', errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('#id1', compactOuptut(selectorAst));
+
+  selectorAst = selector('#id-number-3', errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('#id-number-3', compactOuptut(selectorAst));
+
+  selectorAst = selector('#_privateId', errors: errors..clear());
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect('#_privateId', compactOuptut(selectorAst));
+}
+
+// TODO(terry): Move this failure case to a failure_test.dart when the analyzer
+//              and validator exit then they'll be a bunch more checks.
+void testSelectorFailures() {
+  var errors = [];
+
+  // Test for invalid class name (can't start with number).
+  var selectorAst = selector('.foobar .1a-story .xyzzy', errors: errors);
+  expect(errors.isEmpty, false);
+  expect(errors[0].toString(), r'''
+error :1:9: name must start with a alpha character, but found a number
+.foobar .1a-story .xyzzy
+        ^^''');
+}
+
+main() {
+  test('Valid Selectors', testSelectorSuccesses);
+  test('Invalid Selectors', testSelectorFailures);
+}
diff --git a/test/testing.dart b/test/testing.dart
new file mode 100644
index 0000000..e7b14bd
--- /dev/null
+++ b/test/testing.dart
@@ -0,0 +1,64 @@
+// 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.
+
+/** Common definitions used for setting up the test environment. */
+library testing;
+
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'package:csslib/src/messages.dart';
+
+useMockMessages() {
+  messages = new Messages(printHandler: (message) {});
+}
+
+/**
+ * Spin-up CSS parser in checked mode to detect any problematic CSS.  Normally,
+ * CSS will allow any property/value pairs regardless of validity; all of our
+ * tests (by default) will ensure that the CSS is really valid.
+ */
+StyleSheet parseCss(String cssInput, {List errors, List opts}) =>
+  parse(cssInput, errors: errors, options: opts == null ?
+      ['--no-colors', '--checked', '--warnings_as_errors', 'memory'] : opts);
+
+/**
+ * Spin-up CSS parser in checked mode to detect any problematic CSS.  Normally,
+ * CSS will allow any property/value pairs regardless of validity; all of our
+ * tests (by default) will ensure that the CSS is really valid.
+ */
+StyleSheet compileCss(String cssInput, {List errors, List opts}) =>
+  compile(cssInput, errors: errors, options: opts == null ?
+      ['--no-colors', '--checked', '--warnings_as_errors', 'memory'] : opts);
+
+/** CSS emitter walks the style sheet tree and emits readable CSS. */
+var _emitCss = new CssPrinter();
+
+/** Simple Visitor does nothing but walk tree. */
+var _cssVisitor = new Visitor();
+
+/** Pretty printer for CSS. */
+String prettyPrint(StyleSheet ss) {
+  // Walk the tree testing basic Vistor class.
+  walkTree(ss);
+  return (_emitCss..visitTree(ss, pretty: true)).toString();
+}
+
+/**
+ * Helper function to emit compact (non-pretty printed) CSS for suite test
+ * comparsions.  Spaces, new lines, etc. are reduced for easier comparsions of
+ * expected suite test results.
+ */
+String compactOuptut(StyleSheet ss) {
+  walkTree(ss);
+  return (_emitCss..visitTree(ss, pretty: false)).toString();
+}
+
+/** Walks the style sheet tree does nothing; insures the basic walker works. */
+void walkTree(StyleSheet ss) {
+  _cssVisitor..visitTree(ss);
+}
+
+String dumpTree(StyleSheet ss) => treeToDebugString(ss);
+
+
diff --git a/test/var_test.dart b/test/var_test.dart
new file mode 100644
index 0000000..fb870c7
--- /dev/null
+++ b/test/var_test.dart
@@ -0,0 +1,629 @@
+// Copyright (c) 2013, 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 var_test;
+
+import 'dart:utf';
+import 'package:unittest/unittest.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'testing.dart';
+
+void simpleVar() {
+  final errors = [];
+  final input = ''':root {
+  var-color-background: red;
+  var-color-foreground: blue;
+
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #00ff00;
+}
+.testIt {
+  color: var(color-foreground);
+  background: var(color-background);
+}
+''';
+
+  final generated = ''':root {
+  var-color-background: #f00;
+  var-color-foreground: #00f;
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #0f0;
+}
+.testIt {
+  color: var(color-foreground);
+  background: var(color-background);
+}''';
+
+  var stylesheet = compileCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void expressionsVar() {
+  final errors = [];
+  final input = ''':root {
+  var-color-background: red;
+  var-color-foreground: blue;
+
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #00ff00;
+
+  var-image: url(test.png);
+
+  var-b-width: 20cm;
+  var-m-width: 33%;
+  var-b-height: 30EM;
+  var-width: .6in;
+  var-length: 1.2in;
+  var-web-stuff: -10Px;
+  var-rgba: rgba(10,20,255);
+  var-transition: color 0.4s;
+  var-transform: rotate(20deg);
+  var-content: "✔";
+  var-text-shadow: 0 -1px 0 #bfbfbf;
+  var-font-family: Gentium;
+  var-src: url("http://example.com/fonts/Gentium.ttf");
+  var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+  var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+  var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+  var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+
+.testIt {
+  color: var(color-foreground);
+  background: var(c);
+  background-image: var(image);
+
+  border-width: var(b-width);
+  margin-width: var(m-width);
+  border-height: var(b-height);
+  width: var(width);
+  length: var(length);
+  -web-stuff: var(web-stuff);
+  background-color: var(rgba);
+
+  transition: var(transition);
+  transform: var(transform);
+  content: var(content);
+  text-shadow: var(text-shadow);
+}
+
+@font-face {
+  font-family: var(font-family);
+  src: var(src);
+  unicode-range: var(unicode-range);
+}
+
+@font-face {
+  font-family: var(font-family);
+  src: var(src-1);
+  unicode-range: var(unicode-range-1);
+}
+
+.foobar {
+    grid-columns: var(grid-columns);
+}
+''';
+
+  final generated = ''':root {
+  var-color-background: #f00;
+  var-color-foreground: #00f;
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #0f0;
+  var-image: url("test.png");
+  var-b-width: 20cm;
+  var-m-width: 33%;
+  var-b-height: 30em;
+  var-width: .6in;
+  var-length: 1.2in;
+  var-web-stuff: -10px;
+  var-rgba: rgba(10, 20, 255);
+  var-transition: color 0.4s;
+  var-transform: rotate(20deg);
+  var-content: "✔";
+  var-text-shadow: 0 -1px 0 #bfbfbf;
+  var-font-family: Gentium;
+  var-src: url("http://example.com/fonts/Gentium.ttf");
+  var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+  var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+  var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+  var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+.testIt {
+  color: var(color-foreground);
+  background: var(c);
+  background-image: var(image);
+  border-width: var(b-width);
+  margin-width: var(m-width);
+  border-height: var(b-height);
+  width: var(width);
+  length: var(length);
+  -web-stuff: var(web-stuff);
+  background-color: var(rgba);
+  transition: var(transition);
+  transform: var(transform);
+  content: var(content);
+  text-shadow: var(text-shadow);
+}
+@font-face  {
+  font-family: var(font-family);
+  src: var(src);
+  unicode-range: var(unicode-range);
+}
+@font-face  {
+  font-family: var(font-family);
+  src: var(src-1);
+  unicode-range: var(unicode-range-1);
+}
+.foobar {
+  grid-columns: var(grid-columns);
+}''';
+
+  var stylesheet = compileCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void defaultVar() {
+  final errors = [];
+  final input = '''
+:root {
+  var-color-background: red;
+  var-color-foreground: blue;
+
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #00ff00;
+
+  var-image: url(test.png);
+
+  var-b-width: 20cm;
+  var-m-width: 33%;
+  var-b-height: 30EM;
+}
+
+.test {
+  background-color: var(test, orange);
+}
+
+body {
+  background: var(a) var(image) no-repeat right top;
+}
+
+div {
+  background: var(color-background) url('img_tree.png') no-repeat right top;
+}
+
+.test-2 {
+  background: var(color-background) var(image-2, url('img_1.png')) 
+              no-repeat right top;
+}
+
+.test-3 {
+  background: var(color-background) var(image-2) no-repeat right top;
+}
+
+.test-4 {
+  background: #ffff00 var(image) no-repeat right top;
+}
+
+.test-5 {
+  background: var(test-color, var(a)) var(image) no-repeat right top;
+}
+
+.test-6 {
+  border: red var(a-1, solid 20px);
+}
+''';
+
+  final generated = ''':root {
+  var-color-background: #f00;
+  var-color-foreground: #00f;
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #0f0;
+  var-image: url("test.png");
+  var-b-width: 20cm;
+  var-m-width: 33%;
+  var-b-height: 30em;
+}
+.test {
+  background-color: var(test, #ffa500);
+}
+body {
+  background: var(a) var(image) no-repeat right top;
+}
+div {
+  background: var(color-background) url("img_tree.png") no-repeat right top;
+}
+.test-2 {
+  background: var(color-background) var(image-2, url("img_1.png")) no-repeat right top;
+}
+.test-3 {
+  background: var(color-background) var(image-2) no-repeat right top;
+}
+.test-4 {
+  background: #ff0 var(image) no-repeat right top;
+}
+.test-5 {
+  background: var(test-color, var(a)) var(image) no-repeat right top;
+}
+.test-6 {
+  border: #f00 var(a-1, solid 20px);
+}''';
+
+  var stylesheet = compileCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+void cyclesVar() {
+  final errors = [];
+  final input = ''':root {
+  var-color-background: red;
+  var-color-foreground: blue;
+
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #00ff00;
+
+  var-one: var(two);
+  var-two: var(one);
+
+  var-four: var(five);
+  var-five: var(six);
+  var-six: var(four);
+
+  var-def-1: var(def-2);
+  var-def-2: var(def-3);
+  var-def-3: var(def-2);
+}
+.testIt {
+  color: var(color-foreground);
+  background: var(color-background);
+}
+.test-2 {
+  color: var(one);
+}
+''';
+
+  final generated = ''':root {
+  var-color-background: #f00;
+  var-color-foreground: #00f;
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #0f0;
+}
+.testIt {
+  color: var(color-foreground);
+  background: var(color-background);
+}
+.test-2 {
+  color: var(one);
+}''';
+
+  var stylesheet = compileCss(input, errors: errors,
+      opts: ['--no-colors', '--warnings_as_errors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.length, 8, reason: errors.toString());
+  expect(errors[0].toString(),
+      'error :14:3: var cycle detected var-six\n'
+      '  var-six: var(four);\n'
+      '  ^^^^^^^^^^^^^^^^^^');
+  expect(errors[1].toString(),
+      'error :18:3: var cycle detected var-def-3\n'
+      '  var-def-3: var(def-2);\n'
+      '  ^^^^^^^^^^^^^^^^^^^^^');
+  expect(errors[2].toString(),
+      'error :10:3: var cycle detected var-two\n'
+      '  var-two: var(one);\n'
+      '  ^^^^^^^^^^^^^^^^^');
+  expect(errors[3].toString(),
+      'error :17:3: var cycle detected var-def-2\n'
+      '  var-def-2: var(def-3);\n'
+      '  ^^^^^^^^^^^^^^^^^^^^^');
+  expect(errors[4].toString(),
+      'error :16:3: var cycle detected var-def-1\n'
+      '  var-def-1: var(def-2);\n'
+      '  ^^^^^^^^^^^^^^^^^^^^^');
+  expect(errors[5].toString(),
+      'error :13:3: var cycle detected var-five\n'
+      '  var-five: var(six);\n'
+      '  ^^^^^^^^^^^^^^^^^^');
+  expect(errors[6].toString(),
+      'error :9:3: var cycle detected var-one\n'
+      '  var-one: var(two);\n'
+      '  ^^^^^^^^^^^^^^^^^');
+  expect(errors[7].toString(),
+      'error :12:3: var cycle detected var-four\n'
+      '  var-four: var(five);\n'
+      '  ^^^^^^^^^^^^^^^^^^^');
+  expect(prettyPrint(stylesheet), generated);
+}
+
+parserVar() {
+  final errors = [];
+  final input = ''':root {
+  var-color-background: red;
+  var-color-foreground: blue;
+
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #00ff00;
+
+  var-image: url(test.png);
+
+  var-b-width: 20cm;
+  var-m-width: 33%;
+  var-b-height: 30EM;
+  var-width: .6in;
+  var-length: 1.2in;
+  var-web-stuff: -10Px;
+  var-rgba: rgba(10,20,255);
+  var-transition: color 0.4s;
+  var-transform: rotate(20deg);
+  var-content: "✔";
+  var-text-shadow: 0 -1px 0 #bfbfbf;
+  var-font-family: Gentium;
+  var-src: url("http://example.com/fonts/Gentium.ttf");
+  var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+  var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+  var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+  var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+
+.testIt {
+  color: var(color-foreground);
+  background: var(c);
+  background-image: var(image);
+
+  border-width: var(b-width);
+  margin-width: var(m-width);
+  border-height: var(b-height);
+  width: var(width);
+  length: var(length);
+  -web-stuff: var(web-stuff);
+  background-color: var(rgba);
+
+  transition: var(transition);
+  transform: var(transform);
+  content: var(content);
+  text-shadow: var(text-shadow);
+}
+
+@font-face {
+  font-family: var(font-family);
+  src: var(src);
+  unicode-range: var(unicode-range);
+}
+
+@font-face {
+  font-family: var(font-family);
+  src: var(src-1);
+  unicode-range: var(unicode-range-1);
+}
+
+.foobar {
+    grid-columns: var(grid-columns);
+}
+''';
+
+  final generated = ''':root {
+  var-color-background: #f00;
+  var-color-foreground: #00f;
+  var-a: var(b);
+  var-b: var(c);
+  var-c: #0f0;
+  var-image: url("test.png");
+  var-b-width: 20cm;
+  var-m-width: 33%;
+  var-b-height: 30em;
+  var-width: .6in;
+  var-length: 1.2in;
+  var-web-stuff: -10px;
+  var-rgba: rgba(10, 20, 255);
+  var-transition: color 0.4s;
+  var-transform: rotate(20deg);
+  var-content: "✔";
+  var-text-shadow: 0 -1px 0 #bfbfbf;
+  var-font-family: Gentium;
+  var-src: url("http://example.com/fonts/Gentium.ttf");
+  var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+  var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+  var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+  var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+.testIt {
+  color: var(color-foreground);
+  background: var(c);
+  background-image: var(image);
+  border-width: var(b-width);
+  margin-width: var(m-width);
+  border-height: var(b-height);
+  width: var(width);
+  length: var(length);
+  -web-stuff: var(web-stuff);
+  background-color: var(rgba);
+  transition: var(transition);
+  transform: var(transform);
+  content: var(content);
+  text-shadow: var(text-shadow);
+}
+@font-face  {
+  font-family: var(font-family);
+  src: var(src);
+  unicode-range: var(unicode-range);
+}
+@font-face  {
+  font-family: var(font-family);
+  src: var(src-1);
+  unicode-range: var(unicode-range-1);
+}
+.foobar {
+  grid-columns: var(grid-columns);
+}''';
+
+  var stylesheet = parseCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+}
+
+testVar() {
+  final errors = [];
+  final input = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+  background-color: var(color-background);
+  color: var(color-foreground);
+}
+''';
+  final generated = '''
+var-color-background: #f00;
+var-color-foreground: #00f;
+
+.test {
+  background-color: var(color-background);
+  color: var(color-foreground);
+}''';
+
+  var stylesheet = parseCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  stylesheet = compileCss(input, errors: errors..clear(),
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  final input2 = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+  background-color: @color-background;
+  color: @color-foreground;
+}
+''';
+  final generated2 = '''var-color-background: #f00;
+var-color-foreground: #00f;
+
+.test {
+  background-color: var(color-background);
+  color: var(color-foreground);
+}''';
+
+  stylesheet = parseCss(input, errors: errors..clear(),
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+
+  stylesheet = compileCss(input2, errors: errors..clear(),
+      opts: ['--no-colors', 'memory', '--no-less']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+}
+
+testLess() {
+  final errors = [];
+  final input = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+  background-color: var(color-background);
+  color: var(color-foreground);
+}
+''';
+  final generated = '''var-color-background: #f00;
+var-color-foreground: #00f;
+
+.test {
+  background-color: var(color-background);
+  color: var(color-foreground);
+}''';
+
+  var stylesheet = parseCss(input, errors: errors,
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  stylesheet = compileCss(input, errors: errors..clear(),
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated);
+
+  final input2 = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+  background-color: @color-background;
+  color: @color-foreground;
+}
+''';
+  final generated2 = '''var-color-background: #f00;
+var-color-foreground: #00f;
+
+.test {
+  background-color: var(color-background);
+  color: var(color-foreground);
+}''';
+
+  stylesheet = parseCss(input, errors: errors..clear(),
+      opts: ['--no-colors', 'memory']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+
+  stylesheet = compileCss(input2, errors: errors..clear(),
+      opts: ['--no-colors', 'memory', '--no-less']);
+
+  expect(stylesheet != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+  expect(prettyPrint(stylesheet), generated2);
+}
+
+main() {
+  test('Simple var', simpleVar);
+  test('Expressions var', expressionsVar);
+  test('Default value in var()', defaultVar);
+  test('CSS Parser only var', parserVar);
+  test('Var syntax', testVar);
+  test('Cycles var', cyclesVar);
+  test('Less syntax', testLess);
+}
diff --git a/test/visitor_test.dart b/test/visitor_test.dart
new file mode 100644
index 0000000..e60dbc5
--- /dev/null
+++ b/test/visitor_test.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2013, 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 visitor_test;
+
+import 'dart:utf';
+import 'package:unittest/unittest.dart';
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'testing.dart';
+
+class ClassVisitor extends Visitor {
+  final List expectedClasses;
+  final Set<String> foundClasses = new Set();
+
+  ClassVisitor(this.expectedClasses);
+
+  void visitClassSelector(ClassSelector node) {
+    foundClasses.add(node.name);
+  }
+
+  bool get matches {
+    bool match = true;
+    foundClasses.forEach((value) {
+      match = match && expectedClasses.contains(value);
+    });
+    expectedClasses.forEach((value) {
+      match = match && foundClasses.contains(value);
+    });
+
+    return match;
+  }
+}
+
+void testClassVisitors() {
+  var errors = [];
+  var in1 = '.foobar { }';
+
+  var s = parseCss(in1, errors: errors);
+
+  expect(s != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  var clsVisits = new ClassVisitor(['foobar'])..visitTree(s);
+  expect(clsVisits.matches, true);
+
+  in1= '''
+      .foobar1 { }
+      .xyzzy .foo #my-div { color: red; }
+      div.hello { font: arial; }
+    ''';
+
+  s = parseCss(in1, errors: errors..clear(), opts: ['--no-colors', 'memory']);
+
+  expect(s != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  clsVisits =
+      new ClassVisitor(['foobar1', 'xyzzy', 'foo', 'hello'])..visitTree(s);
+  expect(clsVisits.matches, true);
+
+  expect(prettyPrint(s), r'''
+.foobar1 {
+}
+.xyzzy .foo #my-div {
+  color: #f00;
+}
+div.hello {
+  font: arial;
+}''');
+}
+
+class PolyfillEmitter extends CssPrinter {
+  final String _prefix;
+
+  PolyfillEmitter(this._prefix);
+
+  void visitClassSelector(ClassSelector node) {
+    emit('.${_prefix}_${node.name}');
+  }
+}
+
+String polyfillPrint(String prefix, StyleSheet ss) =>
+  (new PolyfillEmitter(prefix)..visitTree(ss, pretty: true)).toString();
+
+void testPolyFill() {
+  var errors = [];
+  final input = r'''
+.foobar { }
+div.xyzzy { }
+#foo .foo .bar .foobar { }
+''';
+
+  final generated = r'''
+.myComponent_foobar {
+}
+div.myComponent_xyzzy {
+}
+#foo .myComponent_foo .myComponent_bar .myComponent_foobar {
+}''';
+
+  var s = parseCss(input, errors: errors);
+  expect(s != null, true);
+  expect(errors.isEmpty, true, reason: errors.toString());
+
+  final emitted = polyfillPrint('myComponent', s);
+  expect(emitted, generated);
+}
+
+main() {
+  test('Class Visitors', testClassVisitors);
+  test('Polyfill', testPolyFill);
+}