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'; |
- } |
-} |