| Index: client/layout/GridLayoutParser.dart
|
| ===================================================================
|
| --- client/layout/GridLayoutParser.dart (revision 4144)
|
| +++ client/layout/GridLayoutParser.dart (working copy)
|
| @@ -1,486 +0,0 @@
|
| -// Copyright (c) 2011, 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.
|
| -
|
| -/**
|
| - * Base class for simple recursive descent parsers.
|
| - * Handles the lower level stuff, i.e. what a scanner/tokenizer would do.
|
| - */
|
| -class _Parser {
|
| - static final WHITESPACE = ' \r\n\t';
|
| -
|
| - // TODO(jmesserly): shouldn't need this optimization, but dart_json parser
|
| - // found that they needed this.
|
| - static final A_BIG = 65; // 'A'.charCodeAt(0)
|
| - static final Z_BIG = 90; // 'Z'.charCodeAt(0)
|
| - static final A_SMALL = 97; // 'a'.charCodeAt(0)
|
| - static final Z_SMALL = 122; // 'z'.charCodeAt(0)
|
| - static final TAB = 9; // '\t'.charCodeAt(0)
|
| - static final NEW_LINE = 10; // '\n'.charCodeAt(0)
|
| - static final LINE_FEED = 13; // '\r'.charCodeAt(0)
|
| - static final SPACE = 32; // ' '.charCodeAt(0)
|
| - static final ZERO = 48; // '0'.charCodeAt(0)
|
| - static final NINE = 57; // '9'.charCodeAt(0)
|
| - static final DOT = 46; // '.'.charCodeAt(0)
|
| - static final R_PAREN = 41; // ')'.charCodeAt(0)
|
| -
|
| - final String _src;
|
| - int _offset;
|
| -
|
| - // TODO(jmesserly): should be this._offset = 0, see bug 5332175.
|
| - _Parser(this._src) : _offset = 0;
|
| -
|
| - // TODO(jmesserly): these should exist in the standard lib.
|
| - // I took this from dart_json.dart
|
| - static bool _isWhitespace(int c) {
|
| - switch (c) {
|
| - case SPACE:
|
| - case TAB:
|
| - case NEW_LINE:
|
| - case LINE_FEED:
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - static bool _isDigit(int c) {
|
| - return (ZERO <= c) && (c <= NINE);
|
| - }
|
| -
|
| - static bool _isLetter(int c) {
|
| - return (A_SMALL <= c) && (c <= Z_SMALL) || (A_BIG <= c) && (c <= Z_BIG);
|
| - }
|
| -
|
| - void _error(String msg) {
|
| - throw new SyntaxErrorException(msg, _src, _offset);
|
| - }
|
| -
|
| - int get length() => _src.length;
|
| -
|
| - int get remaining() => _src.length - _offset;
|
| -
|
| - int _peekChar() => _src.charCodeAt(_offset);
|
| -
|
| - bool get endOfInput() => _offset >= _src.length;
|
| -
|
| - bool _maybeEatWhitespace() {
|
| - int start = _offset;
|
| - while (_offset < length && _isWhitespace(_peekChar())) {
|
| - _offset++;
|
| - }
|
| - return _offset != start;
|
| - }
|
| -
|
| - bool _maybeEatMultiLineComment() {
|
| - if (_maybeEat('/*', /*eatWhitespace:*/false)) {
|
| - while (!_maybeEat('*/', /*eatWhitespace:*/false)) {
|
| - if (_offset >= length) {
|
| - _error('expected */');
|
| - }
|
| - _offset++;
|
| - }
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - void _maybeEatWhitespaceOrComments() {
|
| - while (_maybeEatWhitespace() || _maybeEatMultiLineComment()) {}
|
| - }
|
| -
|
| - void _eatEnd() {
|
| - _maybeEatWhitespaceOrComments();
|
| - if (!endOfInput) {
|
| - _error('expected end of input');
|
| - }
|
| - }
|
| -
|
| - bool _maybeEat(String value, [bool eatWhitespace = true]) {
|
| - if (eatWhitespace) {
|
| - _maybeEatWhitespaceOrComments();
|
| - }
|
| - if (remaining < value.length) {
|
| - return false;
|
| - }
|
| - for (int i = 0; i < value.length; i++) {
|
| - if (_src[_offset + i] != value[i]) {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - // If we're eating something that's like a word, make sure
|
| - // it's not followed by more characters.
|
| - // This is ugly. Proper tokenization would make this cleaner.
|
| - if (_isLetter(value.charCodeAt(value.length - 1))) {
|
| - int i = _offset + value.length;
|
| - if (i < _src.length && _isLetter(_src.charCodeAt(i))) {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - _offset += value.length;
|
| - return true;
|
| - }
|
| -
|
| - void _eat(String value, [bool eatWhitespace = true]) {
|
| - if (!_maybeEat(value)) {
|
| - _error('expected "$value"');
|
| - }
|
| - }
|
| -
|
| - String _maybeEatString() {
|
| - // TODO(jmesserly): make this match CSS string parsing
|
| - String quote = "'";
|
| - if (!_maybeEat(quote)) {
|
| - quote = '"';
|
| - if (!_maybeEat(quote)) {
|
| - return null;
|
| - }
|
| - }
|
| -
|
| - bool hasEscape = false;
|
| - int start = _offset;
|
| - while (!_maybeEat(quote)) {
|
| - if (endOfInput) {
|
| - _error('expected "$quote"');
|
| - }
|
| - if (_maybeEat('\\')) {
|
| - hasEscape = true;
|
| - }
|
| - _offset++;
|
| - }
|
| - String result = _src.substring(start, _offset - 1);
|
| - if (hasEscape) {
|
| - // TODO(jmesserly): more escape sequences
|
| - result = result.replaceFirst('\\', '');
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - /** Eats something like a keyword. */
|
| - String _eatWord() {
|
| - int start = _offset;
|
| - while (_offset < length && _isLetter(_peekChar())) {
|
| - _offset++;
|
| - }
|
| - return _src.substring(start, _offset);
|
| - }
|
| -
|
| - /** Eats an integer. */
|
| - int _maybeEatInt() {
|
| - int start = _offset;
|
| - bool dot = false;
|
| - while (_offset < length && _isDigit(_peekChar())) {
|
| - _offset++;
|
| - }
|
| -
|
| - if (start == _offset) {
|
| - return null;
|
| - }
|
| -
|
| - return Math.parseInt(_src.substring(start, _offset));
|
| - }
|
| -
|
| - /** Eats an integer. */
|
| - int _eatInt() {
|
| - int result = _maybeEatInt();
|
| - if (result == null) {
|
| - _error('expected positive integer');
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - /** Eats something like a positive decimal: 12.345. */
|
| - num _eatDouble() {
|
| - int start = _offset;
|
| - bool dot = false;
|
| - while (_offset < length) {
|
| - int c = _peekChar();
|
| - if (!_isDigit(c)) {
|
| - if (c == DOT && !dot) {
|
| - dot = true;
|
| - } else {
|
| - // Not a digit or decimal seperator
|
| - break;
|
| - }
|
| - }
|
| - _offset++;
|
| - }
|
| -
|
| - if (start == _offset) {
|
| - _error('expected positive decimal number');
|
| - }
|
| -
|
| - return Math.parseDouble(_src.substring(start, _offset));
|
| - }
|
| -}
|
| -
|
| -/** Parses a grid template. */
|
| -class _GridTemplateParser extends _Parser {
|
| - _GridTemplateParser._internal(String src) : super(src);
|
| -
|
| - /** Parses the grid-rows and grid-columns CSS properties into object form. */
|
| - static GridTemplate parse(String str) {
|
| - if (str == null) return null;
|
| - final p = new _GridTemplateParser._internal(str);
|
| - final result = p._parseTemplate();
|
| - p._eatEnd();
|
| - return result;
|
| - }
|
| -
|
| - /** Parses a grid-cell value. */
|
| - static String parseCell(String str) {
|
| - if (str == null) return null;
|
| - final p = new _GridTemplateParser._internal(str);
|
| - final result = p._maybeEatString();
|
| - p._eatEnd();
|
| - return result;
|
| - }
|
| -
|
| - // => <string>+ | 'none'
|
| - GridTemplate _parseTemplate() {
|
| - if (_maybeEat('none')) {
|
| - return null;
|
| - }
|
| - final rows = new List<String>();
|
| - String row;
|
| - while ((row = _maybeEatString()) != null) {
|
| - rows.add(row);
|
| - }
|
| - if (rows.length == 0) {
|
| - _error('expected at least one cell, or "none"');
|
| - }
|
| - return new GridTemplate(rows);
|
| - }
|
| -}
|
| -
|
| -/** Parses a grid-row or grid-column */
|
| -class _GridItemParser extends _Parser {
|
| - _GridItemParser._internal(String src) : super(src);
|
| -
|
| - /** Parses the grid-rows and grid-columns CSS properties into object form. */
|
| - static _GridLocation parse(String cell, GridTrackList list) {
|
| - if (cell == null) return null;
|
| - final p = new _GridItemParser._internal(cell);
|
| - final result = p._parseTrack(list);
|
| - p._eatEnd();
|
| - return result;
|
| - }
|
| -
|
| - // [ [ <integer> | <string> | 'start' | 'end' ]
|
| - // [ <integer> | <string> | 'start' | 'end' ]? ]
|
| - // | 'auto'
|
| - _GridLocation _parseTrack(GridTrackList list) {
|
| - if (_maybeEat('auto')) {
|
| - return null;
|
| - }
|
| - int start = _maybeParseLine(list);
|
| - if (start == null) {
|
| - _error('expected row/column number or name');
|
| - }
|
| - int end = _maybeParseLine(list);
|
| - int span = null;
|
| - if (end != null) {
|
| - span = end - start;
|
| - if (span <= 0) {
|
| - _error('expected row/column span to be a positive integer');
|
| - }
|
| - }
|
| - return new _GridLocation(start, span);
|
| - }
|
| -
|
| - // [ <integer> | <string> | 'start' | 'end' ]
|
| - int _maybeParseLine(GridTrackList list) {
|
| - if (_maybeEat('start')) {
|
| - return 1;
|
| - } else if (_maybeEat('end')) {
|
| - // The end is exclusive and 1-based, so return one past the size of the
|
| - // track list.
|
| - // TODO(jmesserly): this won't interact properly with implicit
|
| - // rows/columns. Instead it will snap to the number of tracks at the point
|
| - // where it is evaluated.
|
| - return list.tracks.length + 1;
|
| - }
|
| -
|
| - String name = _maybeEatString();
|
| - if (name == null) {
|
| - return _maybeEatInt();
|
| - } else {
|
| - int edge = list.lineNames[name];
|
| - if (edge == null) {
|
| - _error('row/column name "$name" not found in the parent\'s '
|
| - + ' grid-row/grid-columns properties');
|
| - }
|
| - return edge;
|
| - }
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Parses grid-rows and grid-column properties, see:
|
| - * [http://dev.w3.org/csswg/css3-grid-align/#grid-columns-and-rows-properties]
|
| - * This is kept as a recursive descent parser for simplicity.
|
| - */
|
| -// TODO(jmesserly): implement missing features from the spec. Mainly around
|
| -// CSS units, support for all escape sequences, etc.
|
| -class _GridTrackParser extends _Parser {
|
| - final List<GridTrack> _tracks;
|
| - final Map<String, int> _lineNames;
|
| -
|
| - _GridTrackParser._internal(String src)
|
| - : super(src),
|
| - _tracks = new List<GridTrack>(),
|
| - _lineNames = new Map<String, int>();
|
| -
|
| - /** Parses the grid-rows and grid-columns CSS properties into object form. */
|
| - static GridTrackList parse(String str) {
|
| - if (str == null) return null;
|
| - final p = new _GridTrackParser._internal(str);
|
| - final result = p._parseTrackList();
|
| - p._eatEnd();
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * Parses the grid-row-sizing and grid-column-sizing CSS properties into
|
| - * object form.
|
| - */
|
| - static TrackSizing parseTrackSizing(String str) {
|
| - if (str == null) str = 'auto';
|
| - final p = new _GridTrackParser._internal(str);
|
| - final result = p._parseTrackMinmax();
|
| - p._eatEnd();
|
| - return result;
|
| - }
|
| -
|
| - // <track-list> => [ [ <string> ]* <track-group> [ <string> ]* ]+ | 'none'
|
| - GridTrackList _parseTrackList() {
|
| - if (_maybeEat('none')) {
|
| - return null;
|
| - }
|
| - _parseTrackListHelper();
|
| - return new GridTrackList(_tracks, _lineNames);
|
| - }
|
| -
|
| - /** Code shared by _parseTrackList and _parseTrackGroup */
|
| - void _parseTrackListHelper([List<GridTrack> resultTracks = null]) {
|
| - _maybeEatWhitespace();
|
| - while (!endOfInput) {
|
| - String name;
|
| - while ((name = _maybeEatString()) != null) {
|
| - _lineNames[name] = _tracks.length + 1; // should be 1-based
|
| - }
|
| -
|
| - _maybeEatWhitespace();
|
| - if (endOfInput) {
|
| - return;
|
| - }
|
| -
|
| - if (resultTracks != null) {
|
| - if (_peekChar() == R_PAREN) {
|
| - return;
|
| - }
|
| - resultTracks.add(new GridTrack(_parseTrackMinmax()));
|
| - } else {
|
| - _parseTrackGroup();
|
| - }
|
| -
|
| - _maybeEatWhitespace();
|
| - }
|
| - }
|
| -
|
| - // <track-group> => [ '(' [ [ <string> ]* <track-minmax> [ <string> ]* ]+ ')'
|
| - // [ '[' <positive-number> ']' ]? ]
|
| - // | <track-minmax>
|
| - void _parseTrackGroup() {
|
| - if (_maybeEat('(')) {
|
| - final tracks = new List<GridTrack>();
|
| - _parseTrackListHelper(tracks);
|
| - _eat(')');
|
| - if (_maybeEat('[')) {
|
| - num expand = _eatInt();
|
| - _eat(']');
|
| -
|
| - if (expand <= 0) {
|
| - _error('expected positive number');
|
| - }
|
| -
|
| - // Repeat the track definition (but not the names) the specified number
|
| - // of times. See:
|
| - // http://dev.w3.org/csswg/css3-grid-align/#grid-repeating-columns-and-rows
|
| - for (int i = 0; i < expand; i++) {
|
| - for (GridTrack t in tracks) {
|
| - _tracks.add(t.clone());
|
| - }
|
| - }
|
| - }
|
| - } else {
|
| - _tracks.add(new GridTrack(_parseTrackMinmax()));
|
| - }
|
| - }
|
| -
|
| - // <track-minmax> => 'minmax(' <track-breadth> ',' <track-breadth> ')'
|
| - // | 'auto' | <track-breadth>
|
| - TrackSizing _parseTrackMinmax() {
|
| - if (_maybeEat('auto') || _maybeEat('fit-content')) {
|
| - return const TrackSizing.auto();
|
| - }
|
| - if (_maybeEat('minmax(')) {
|
| - final min = _parseTrackBreadth();
|
| - _eat(',');
|
| - final max = _parseTrackBreadth();
|
| - _eat(')');
|
| - return new TrackSizing(min, max);
|
| - } else {
|
| - final breadth = _parseTrackBreadth();
|
| - return new TrackSizing(breadth, breadth);
|
| - }
|
| - }
|
| -
|
| - // <track-breadth> => <length> | <percentage> | <fraction>
|
| - // | 'min-content' | 'max-content'
|
| - SizingFunction _parseTrackBreadth() {
|
| - if (_maybeEat('min-content')) {
|
| - return const MinContentSizing();
|
| - } else if (_maybeEat('max-content')) {
|
| - return const MaxContentSizing();
|
| - }
|
| -
|
| - num value = _eatDouble();
|
| -
|
| - String units;
|
| - if (_maybeEat('%')) {
|
| - units = '%';
|
| - } else {
|
| - units = _eatWord();
|
| - }
|
| -
|
| - if (units == 'fr') {
|
| - return new FractionSizing(value);
|
| - } else {
|
| - return new FixedSizing(value, units);
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -/**
|
| - * Exception thrown because the grid style properties had incorrect values.
|
| - */
|
| -class SyntaxErrorException implements Exception {
|
| - final String _message;
|
| - final int _offset;
|
| - final String _source;
|
| -
|
| - const SyntaxErrorException(this._message, this._source, this._offset);
|
| -
|
| - String toString() {
|
| - String location;
|
| - if (_offset < _source.length) {
|
| - location = 'location: ' + _source.substring(_offset);
|
| - } else {
|
| - location = 'end of input';
|
| - }
|
| - return 'SyntaxErrorException: $_message at $location';
|
| - }
|
| -}
|
|
|