Files
sdk/samples-dev/swarm/swarm_ui_lib/layout/GridLayoutParser.dart
T
Aske Simon Christensen 4004194c29 Clean up super calls in swarm.
Remove redundant super calls and move all other super calls to the end
of the initializer list, as required by Dart 2.

Change-Id: I2b0968b9c6508d00a3379c225470d5d9c0ec9f78
Reviewed-on: https://dart-review.googlesource.com/69967
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
2018-08-21 17:56:13 +00:00

488 lines
13 KiB
Dart

// 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.
part of layout;
/**
* Base class for simple recursive descent parsers.
* Handles the lower level stuff, i.e. what a scanner/tokenizer would do.
*/
class _Parser {
static const WHITESPACE = ' \r\n\t';
// TODO(jmesserly): shouldn't need this optimization, but dart_json parser
// found that they needed this.
static const A_BIG = 65; // 'A'.codeUnitAt(0)
static const Z_BIG = 90; // 'Z'.codeUnitAt(0)
static const A_SMALL = 97; // 'a'.codeUnitAt(0)
static const Z_SMALL = 122; // 'z'.codeUnitAt(0)
static const TAB = 9; // '\t'.codeUnitAt(0)
static const NEW_LINE = 10; // '\n'.codeUnitAt(0)
static const LINE_FEED = 13; // '\r'.codeUnitAt(0)
static const SPACE = 32; // ' '.codeUnitAt(0)
static const ZERO = 48; // '0'.codeUnitAt(0)
static const NINE = 57; // '9'.codeUnitAt(0)
static const DOT = 46; // '.'.codeUnitAt(0)
static const R_PAREN = 41; // ')'.codeUnitAt(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.codeUnitAt(_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.codeUnitAt(value.length - 1))) {
int i = _offset + value.length;
if (i < _src.length && _isLetter(_src.codeUnitAt(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 int.parse(_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 double.parse(_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)
: _tracks = new List<GridTrack>(),
_lineNames = new Map<String, int>(),
super(src);
/** 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() == _Parser.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';
}
}