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

Side by Side Diff: client/layout/GridLayoutParser.dart

Issue 9382027: Move client/{base, observable, layout, touch, util, view} to samples/ui_lib . (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « client/layout/GridLayoutParams.dart ('k') | client/layout/GridTracks.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Base class for simple recursive descent parsers.
7 * Handles the lower level stuff, i.e. what a scanner/tokenizer would do.
8 */
9 class _Parser {
10 static final WHITESPACE = ' \r\n\t';
11
12 // TODO(jmesserly): shouldn't need this optimization, but dart_json parser
13 // found that they needed this.
14 static final A_BIG = 65; // 'A'.charCodeAt(0)
15 static final Z_BIG = 90; // 'Z'.charCodeAt(0)
16 static final A_SMALL = 97; // 'a'.charCodeAt(0)
17 static final Z_SMALL = 122; // 'z'.charCodeAt(0)
18 static final TAB = 9; // '\t'.charCodeAt(0)
19 static final NEW_LINE = 10; // '\n'.charCodeAt(0)
20 static final LINE_FEED = 13; // '\r'.charCodeAt(0)
21 static final SPACE = 32; // ' '.charCodeAt(0)
22 static final ZERO = 48; // '0'.charCodeAt(0)
23 static final NINE = 57; // '9'.charCodeAt(0)
24 static final DOT = 46; // '.'.charCodeAt(0)
25 static final R_PAREN = 41; // ')'.charCodeAt(0)
26
27 final String _src;
28 int _offset;
29
30 // TODO(jmesserly): should be this._offset = 0, see bug 5332175.
31 _Parser(this._src) : _offset = 0;
32
33 // TODO(jmesserly): these should exist in the standard lib.
34 // I took this from dart_json.dart
35 static bool _isWhitespace(int c) {
36 switch (c) {
37 case SPACE:
38 case TAB:
39 case NEW_LINE:
40 case LINE_FEED:
41 return true;
42 }
43 return false;
44 }
45
46 static bool _isDigit(int c) {
47 return (ZERO <= c) && (c <= NINE);
48 }
49
50 static bool _isLetter(int c) {
51 return (A_SMALL <= c) && (c <= Z_SMALL) || (A_BIG <= c) && (c <= Z_BIG);
52 }
53
54 void _error(String msg) {
55 throw new SyntaxErrorException(msg, _src, _offset);
56 }
57
58 int get length() => _src.length;
59
60 int get remaining() => _src.length - _offset;
61
62 int _peekChar() => _src.charCodeAt(_offset);
63
64 bool get endOfInput() => _offset >= _src.length;
65
66 bool _maybeEatWhitespace() {
67 int start = _offset;
68 while (_offset < length && _isWhitespace(_peekChar())) {
69 _offset++;
70 }
71 return _offset != start;
72 }
73
74 bool _maybeEatMultiLineComment() {
75 if (_maybeEat('/*', /*eatWhitespace:*/false)) {
76 while (!_maybeEat('*/', /*eatWhitespace:*/false)) {
77 if (_offset >= length) {
78 _error('expected */');
79 }
80 _offset++;
81 }
82 return true;
83 }
84 return false;
85 }
86
87 void _maybeEatWhitespaceOrComments() {
88 while (_maybeEatWhitespace() || _maybeEatMultiLineComment()) {}
89 }
90
91 void _eatEnd() {
92 _maybeEatWhitespaceOrComments();
93 if (!endOfInput) {
94 _error('expected end of input');
95 }
96 }
97
98 bool _maybeEat(String value, [bool eatWhitespace = true]) {
99 if (eatWhitespace) {
100 _maybeEatWhitespaceOrComments();
101 }
102 if (remaining < value.length) {
103 return false;
104 }
105 for (int i = 0; i < value.length; i++) {
106 if (_src[_offset + i] != value[i]) {
107 return false;
108 }
109 }
110
111 // If we're eating something that's like a word, make sure
112 // it's not followed by more characters.
113 // This is ugly. Proper tokenization would make this cleaner.
114 if (_isLetter(value.charCodeAt(value.length - 1))) {
115 int i = _offset + value.length;
116 if (i < _src.length && _isLetter(_src.charCodeAt(i))) {
117 return false;
118 }
119 }
120
121 _offset += value.length;
122 return true;
123 }
124
125 void _eat(String value, [bool eatWhitespace = true]) {
126 if (!_maybeEat(value)) {
127 _error('expected "$value"');
128 }
129 }
130
131 String _maybeEatString() {
132 // TODO(jmesserly): make this match CSS string parsing
133 String quote = "'";
134 if (!_maybeEat(quote)) {
135 quote = '"';
136 if (!_maybeEat(quote)) {
137 return null;
138 }
139 }
140
141 bool hasEscape = false;
142 int start = _offset;
143 while (!_maybeEat(quote)) {
144 if (endOfInput) {
145 _error('expected "$quote"');
146 }
147 if (_maybeEat('\\')) {
148 hasEscape = true;
149 }
150 _offset++;
151 }
152 String result = _src.substring(start, _offset - 1);
153 if (hasEscape) {
154 // TODO(jmesserly): more escape sequences
155 result = result.replaceFirst('\\', '');
156 }
157 return result;
158 }
159
160 /** Eats something like a keyword. */
161 String _eatWord() {
162 int start = _offset;
163 while (_offset < length && _isLetter(_peekChar())) {
164 _offset++;
165 }
166 return _src.substring(start, _offset);
167 }
168
169 /** Eats an integer. */
170 int _maybeEatInt() {
171 int start = _offset;
172 bool dot = false;
173 while (_offset < length && _isDigit(_peekChar())) {
174 _offset++;
175 }
176
177 if (start == _offset) {
178 return null;
179 }
180
181 return Math.parseInt(_src.substring(start, _offset));
182 }
183
184 /** Eats an integer. */
185 int _eatInt() {
186 int result = _maybeEatInt();
187 if (result == null) {
188 _error('expected positive integer');
189 }
190 return result;
191 }
192
193 /** Eats something like a positive decimal: 12.345. */
194 num _eatDouble() {
195 int start = _offset;
196 bool dot = false;
197 while (_offset < length) {
198 int c = _peekChar();
199 if (!_isDigit(c)) {
200 if (c == DOT && !dot) {
201 dot = true;
202 } else {
203 // Not a digit or decimal seperator
204 break;
205 }
206 }
207 _offset++;
208 }
209
210 if (start == _offset) {
211 _error('expected positive decimal number');
212 }
213
214 return Math.parseDouble(_src.substring(start, _offset));
215 }
216 }
217
218 /** Parses a grid template. */
219 class _GridTemplateParser extends _Parser {
220 _GridTemplateParser._internal(String src) : super(src);
221
222 /** Parses the grid-rows and grid-columns CSS properties into object form. */
223 static GridTemplate parse(String str) {
224 if (str == null) return null;
225 final p = new _GridTemplateParser._internal(str);
226 final result = p._parseTemplate();
227 p._eatEnd();
228 return result;
229 }
230
231 /** Parses a grid-cell value. */
232 static String parseCell(String str) {
233 if (str == null) return null;
234 final p = new _GridTemplateParser._internal(str);
235 final result = p._maybeEatString();
236 p._eatEnd();
237 return result;
238 }
239
240 // => <string>+ | 'none'
241 GridTemplate _parseTemplate() {
242 if (_maybeEat('none')) {
243 return null;
244 }
245 final rows = new List<String>();
246 String row;
247 while ((row = _maybeEatString()) != null) {
248 rows.add(row);
249 }
250 if (rows.length == 0) {
251 _error('expected at least one cell, or "none"');
252 }
253 return new GridTemplate(rows);
254 }
255 }
256
257 /** Parses a grid-row or grid-column */
258 class _GridItemParser extends _Parser {
259 _GridItemParser._internal(String src) : super(src);
260
261 /** Parses the grid-rows and grid-columns CSS properties into object form. */
262 static _GridLocation parse(String cell, GridTrackList list) {
263 if (cell == null) return null;
264 final p = new _GridItemParser._internal(cell);
265 final result = p._parseTrack(list);
266 p._eatEnd();
267 return result;
268 }
269
270 // [ [ <integer> | <string> | 'start' | 'end' ]
271 // [ <integer> | <string> | 'start' | 'end' ]? ]
272 // | 'auto'
273 _GridLocation _parseTrack(GridTrackList list) {
274 if (_maybeEat('auto')) {
275 return null;
276 }
277 int start = _maybeParseLine(list);
278 if (start == null) {
279 _error('expected row/column number or name');
280 }
281 int end = _maybeParseLine(list);
282 int span = null;
283 if (end != null) {
284 span = end - start;
285 if (span <= 0) {
286 _error('expected row/column span to be a positive integer');
287 }
288 }
289 return new _GridLocation(start, span);
290 }
291
292 // [ <integer> | <string> | 'start' | 'end' ]
293 int _maybeParseLine(GridTrackList list) {
294 if (_maybeEat('start')) {
295 return 1;
296 } else if (_maybeEat('end')) {
297 // The end is exclusive and 1-based, so return one past the size of the
298 // track list.
299 // TODO(jmesserly): this won't interact properly with implicit
300 // rows/columns. Instead it will snap to the number of tracks at the point
301 // where it is evaluated.
302 return list.tracks.length + 1;
303 }
304
305 String name = _maybeEatString();
306 if (name == null) {
307 return _maybeEatInt();
308 } else {
309 int edge = list.lineNames[name];
310 if (edge == null) {
311 _error('row/column name "$name" not found in the parent\'s '
312 + ' grid-row/grid-columns properties');
313 }
314 return edge;
315 }
316 }
317 }
318
319 /**
320 * Parses grid-rows and grid-column properties, see:
321 * [http://dev.w3.org/csswg/css3-grid-align/#grid-columns-and-rows-properties]
322 * This is kept as a recursive descent parser for simplicity.
323 */
324 // TODO(jmesserly): implement missing features from the spec. Mainly around
325 // CSS units, support for all escape sequences, etc.
326 class _GridTrackParser extends _Parser {
327 final List<GridTrack> _tracks;
328 final Map<String, int> _lineNames;
329
330 _GridTrackParser._internal(String src)
331 : super(src),
332 _tracks = new List<GridTrack>(),
333 _lineNames = new Map<String, int>();
334
335 /** Parses the grid-rows and grid-columns CSS properties into object form. */
336 static GridTrackList parse(String str) {
337 if (str == null) return null;
338 final p = new _GridTrackParser._internal(str);
339 final result = p._parseTrackList();
340 p._eatEnd();
341 return result;
342 }
343
344 /**
345 * Parses the grid-row-sizing and grid-column-sizing CSS properties into
346 * object form.
347 */
348 static TrackSizing parseTrackSizing(String str) {
349 if (str == null) str = 'auto';
350 final p = new _GridTrackParser._internal(str);
351 final result = p._parseTrackMinmax();
352 p._eatEnd();
353 return result;
354 }
355
356 // <track-list> => [ [ <string> ]* <track-group> [ <string> ]* ]+ | 'none'
357 GridTrackList _parseTrackList() {
358 if (_maybeEat('none')) {
359 return null;
360 }
361 _parseTrackListHelper();
362 return new GridTrackList(_tracks, _lineNames);
363 }
364
365 /** Code shared by _parseTrackList and _parseTrackGroup */
366 void _parseTrackListHelper([List<GridTrack> resultTracks = null]) {
367 _maybeEatWhitespace();
368 while (!endOfInput) {
369 String name;
370 while ((name = _maybeEatString()) != null) {
371 _lineNames[name] = _tracks.length + 1; // should be 1-based
372 }
373
374 _maybeEatWhitespace();
375 if (endOfInput) {
376 return;
377 }
378
379 if (resultTracks != null) {
380 if (_peekChar() == R_PAREN) {
381 return;
382 }
383 resultTracks.add(new GridTrack(_parseTrackMinmax()));
384 } else {
385 _parseTrackGroup();
386 }
387
388 _maybeEatWhitespace();
389 }
390 }
391
392 // <track-group> => [ '(' [ [ <string> ]* <track-minmax> [ <string> ]* ]+ ')'
393 // [ '[' <positive-number> ']' ]? ]
394 // | <track-minmax>
395 void _parseTrackGroup() {
396 if (_maybeEat('(')) {
397 final tracks = new List<GridTrack>();
398 _parseTrackListHelper(tracks);
399 _eat(')');
400 if (_maybeEat('[')) {
401 num expand = _eatInt();
402 _eat(']');
403
404 if (expand <= 0) {
405 _error('expected positive number');
406 }
407
408 // Repeat the track definition (but not the names) the specified number
409 // of times. See:
410 // http://dev.w3.org/csswg/css3-grid-align/#grid-repeating-columns-and-r ows
411 for (int i = 0; i < expand; i++) {
412 for (GridTrack t in tracks) {
413 _tracks.add(t.clone());
414 }
415 }
416 }
417 } else {
418 _tracks.add(new GridTrack(_parseTrackMinmax()));
419 }
420 }
421
422 // <track-minmax> => 'minmax(' <track-breadth> ',' <track-breadth> ')'
423 // | 'auto' | <track-breadth>
424 TrackSizing _parseTrackMinmax() {
425 if (_maybeEat('auto') || _maybeEat('fit-content')) {
426 return const TrackSizing.auto();
427 }
428 if (_maybeEat('minmax(')) {
429 final min = _parseTrackBreadth();
430 _eat(',');
431 final max = _parseTrackBreadth();
432 _eat(')');
433 return new TrackSizing(min, max);
434 } else {
435 final breadth = _parseTrackBreadth();
436 return new TrackSizing(breadth, breadth);
437 }
438 }
439
440 // <track-breadth> => <length> | <percentage> | <fraction>
441 // | 'min-content' | 'max-content'
442 SizingFunction _parseTrackBreadth() {
443 if (_maybeEat('min-content')) {
444 return const MinContentSizing();
445 } else if (_maybeEat('max-content')) {
446 return const MaxContentSizing();
447 }
448
449 num value = _eatDouble();
450
451 String units;
452 if (_maybeEat('%')) {
453 units = '%';
454 } else {
455 units = _eatWord();
456 }
457
458 if (units == 'fr') {
459 return new FractionSizing(value);
460 } else {
461 return new FixedSizing(value, units);
462 }
463 }
464 }
465
466
467 /**
468 * Exception thrown because the grid style properties had incorrect values.
469 */
470 class SyntaxErrorException implements Exception {
471 final String _message;
472 final int _offset;
473 final String _source;
474
475 const SyntaxErrorException(this._message, this._source, this._offset);
476
477 String toString() {
478 String location;
479 if (_offset < _source.length) {
480 location = 'location: ' + _source.substring(_offset);
481 } else {
482 location = 'end of input';
483 }
484 return 'SyntaxErrorException: $_message at $location';
485 }
486 }
OLDNEW
« no previous file with comments | « client/layout/GridLayoutParams.dart ('k') | client/layout/GridTracks.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698