More changes for CSS to run under VM.

Need unique name for library collision with template parser.

Few more changes to work under VM.

More changes for lang.dart seperation.

Seperation from lang.dart (tokenizer can't access private fields of base class in another library).  This was allowed in frog but not in the VM.  VM is right so need to sever dependency.

BUG=
TEST=

Review URL: https://chromiumcodereview.appspot.com//9695048

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@5544 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
terry@google.com 2012-03-15 21:19:54 +00:00
parent 7d1277342f
commit 91dd628bd8
61 changed files with 6966 additions and 222 deletions

25
utils/css/css Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
# To pre-process CSS files, run this script with the path to a .scss file, like:
#
# $ $DART/utils/css/css foo.scss foo.css
#
# To use your Dart VM must be on your $PATH e.g.,
#
# export PATH=$PATH:/home/<your name>/dart-all/dart/out/Release_ia32/
SRCFILE=$PWD/$1
OUTPUT=$PWD/$2
if [[ $1 == $2 ]]
then
echo -e "\033[31msource file must be different then output file\033[0m"
exit 1
fi
# Path of this bash script.
BASE_DIR="$( cd "$( dirname "$0" )" && pwd )"
# Pre-process the file
dart $BASE_DIR/tool.dart $SRCFILE $OUTPUT

View file

@ -4,47 +4,53 @@
#library('css');
#import('../../frog/lang.dart', prefix:'lang');
#import('../../frog/file_system.dart');
#import('../../frog/file_system_memory.dart');
#import("../lib/file_system.dart");
#import('../lib/file_system_memory.dart');
#source('cssoptions.dart');
#source('source.dart');
#source('tokenkind.dart');
#source('token.dart');
#source('tokenizer_base.dart');
#source('tokenizer.dart');
#source('treebase.dart');
#source('tree.dart');
#source('cssselectorexception.dart');
#source('cssworld.dart');
#source('parser.dart');
#source('validate.dart');
#source('generate.dart');
#source('world.dart');
void initCssWorld([bool commandLine = true]) {
var fs = new MemoryFileSystem();
lang.parseOptions('', [], fs);
lang.initializeWorld(fs);
lang.world.process();
lang.world.resolveAll();
FileSystem fs = new MemoryFileSystem();
parseOptions([], fs);
initializeWorld(fs);
// TODO(terry): Should be set by arguments. When run as a tool these aren't
// set when run internaly set these so we can compile CSS and catch any
// problems programmatically.
lang.options.throwOnErrors = true;
lang.options.throwOnFatal = true;
lang.options.useColors = commandLine ? true : false;
options.throwOnErrors = true;
options.throwOnFatal = true;
options.useColors = commandLine ? true : false;
options.warningsAsErrors = false;
options.showWarnings = true;
}
// TODO(terry): Add obfuscation mapping file.
void cssParseAndValidate(String cssExpression, CssWorld world) {
Parser parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
void cssParseAndValidate(String cssExpression, CssWorld cssworld) {
Parser parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
cssExpression));
var tree = parser.parseTemplate();
if (tree != null) {
Validate.template(tree.selectors, world);
Validate.template(tree.selectors, cssworld);
}
}
// Returns pretty printed tree of the expression.
String cssParseAndValidateDebug(String cssExpression, CssWorld world) {
Parser parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
String cssParseAndValidateDebug(String cssExpression, CssWorld cssworld) {
Parser parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
cssExpression));
String output = "";
String prettyTree = "";
@ -52,7 +58,7 @@ String cssParseAndValidateDebug(String cssExpression, CssWorld world) {
var tree = parser.parseTemplate();
if (tree != null) {
prettyTree = tree.toDebugString();
Validate.template(tree.selectors, world);
Validate.template(tree.selectors, cssworld);
output = prettyTree;
}
} catch (var e) {

8
utils/css/css.html Executable file
View file

@ -0,0 +1,8 @@
<html>
<head>
<title>CSS UITest</title>
</head>
<body>
<script type="application/dart" src="uitest.dart"></script>
</body>
</html>

93
utils/css/cssoptions.dart Normal file
View file

@ -0,0 +1,93 @@
// 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.
/** General options used by the compiler. */
CSSOptions options;
/** Extracts options from command-line arguments. */
void parseOptions(List<String> args, var files) {
assert(options == null);
options = new CSSOptions(args, files);
}
class CSSOptions {
/** Location of corelib and other special dart libraries. */
String libDir;
/* The top-level dart script to compile. */
String dartScript;
/** Where to place the generated code. */
String outfile;
// Options that modify behavior significantly
bool warningsAsErrors = false;
bool checkOnly = false;
// Message support
bool throwOnErrors = false;
bool throwOnWarnings = false;
bool throwOnFatal = false;
bool showInfo = false;
bool showWarnings = true;
bool useColors = true;
/**
* Options to be used later for passing to the generated code. These are all
* the arguments after the first dart script, if any.
*/
List<String> childArgs;
CSSOptions(List<String> args, var files) {
bool ignoreUnrecognizedFlags = false;
bool passedLibDir = false;
childArgs = [];
// Start from 2 to skip arguments representing the compiler command
// (node/python followed by frogsh/frog.py).
loop: for (int i = 2; i < args.length; i++) {
var arg = args[i];
switch (arg) {
case '--check-only':
checkOnly = true;
break;
case '--verbose':
showInfo = true;
break;
case '--suppress_warnings':
showWarnings = false;
break;
case '--warnings_as_errors':
warningsAsErrors = true;
break;
case '--throw_on_errors':
throwOnErrors = true;
break;
case '--throw_on_warnings':
throwOnWarnings = true;
break;
case '--no_colors':
useColors = false;
break;
case '--checked':
checkOnly = true;
break;
default:
if (!ignoreUnrecognizedFlags) {
print('unrecognized flag: "$arg"');
}
}
}
}
}

View file

@ -8,7 +8,7 @@
/** Can be thrown on any Css runtime problem includes source location. */
class CssSelectorException implements Exception {
final String _message;
final lang.SourceSpan _location;
final SourceSpan _location;
CssSelectorException(this._message, [this._location = null]);

View file

@ -23,7 +23,7 @@ class Generate {
return classes;
}
static dartClass(FileSystem files, String outPath, Stylesheet stylesheet,
static dartClass(var files, String outPath, Stylesheet stylesheet,
String filename) {
List<String> knownClasses = [];

View file

@ -10,11 +10,15 @@ class Parser {
var _fs; // If non-null filesystem to read files.
String _basePath; // Base path of CSS file.
final lang.SourceFile source;
final SourceFile source;
Token _previousToken;
Token _peekToken;
// Communicating errors back to template parser.
// TODO(terry): Need a better mechanism (e.g., common World).
var _erroMsgRedirector;
lang.Token _previousToken;
lang.Token _peekToken;
Parser(this.source, [int start = 0, this._fs = null, this._basePath = null]) {
tokenizer = new Tokenizer(source, true, start);
_peekToken = tokenizer.next();
@ -22,17 +26,38 @@ class Parser {
}
// Main entry point for parsing an entire CSS file.
Stylesheet parse() {
List<lang.Node> productions = [];
// If nestedCSS is true when we're back at processing directives from top and
// we encounter a } then stop we're inside of a template e.g.,
//
// template ... {
// css {
// .item {
// left: 10px;
// }
// }
// <div>...</div>
// }
//
Stylesheet parse([bool nestedCSS = false, var erroMsgRedirector = null]) {
// TODO(terry): Hack for migrating CSS errors back to template errors.
_erroMsgRedirector = erroMsgRedirector;
List<ASTNode> productions = [];
int start = _peekToken.start;
while (!_maybeEat(TokenKind.END_OF_FILE)) {
while (!_maybeEat(TokenKind.END_OF_FILE) &&
(!nestedCSS && !_peekKind(TokenKind.RBRACE))) {
// TODO(terry): Need to handle charset, import, media and page.
var directive = processDirective();
if (directive != null) {
productions.add(directive);
} else {
productions.add(processRuleSet());
RuleSet ruleset = processRuleSet();
if (ruleset != null) {
productions.add(ruleset);
} else {
break;
}
}
}
@ -64,7 +89,7 @@ class Parser {
return _peekToken.kind;
}
lang.Token _next() {
Token _next() {
_previousToken = _peekToken;
_peekToken = tokenizer.next();
return _previousToken;
@ -110,24 +135,32 @@ class Parser {
_error(message, tok.span);
}
void _error(String message, [lang.SourceSpan location=null]) {
void _error(String message, [SourceSpan location=null]) {
if (location === null) {
location = _peekToken.span;
}
lang.world.fatal(message, location); // syntax errors are fatal for now
if (_erroMsgRedirector == null) {
world.fatal(message, location); // syntax errors are fatal for now
} else {
String text = "";
if (location != null) {
text = location.toMessageString("");
}
_erroMsgRedirector.displayError("CSS error: \r${text}\r${message}");
}
}
void _warning(String message, [lang.SourceSpan location=null]) {
void _warning(String message, [SourceSpan location=null]) {
if (location === null) {
location = _peekToken.span;
}
lang.world.warning(message, location);
world.warning(message, location);
}
lang.SourceSpan _makeSpan(int start) {
return new lang.SourceSpan(source, start, _previousToken.end);
SourceSpan _makeSpan(int start) {
return new SourceSpan(source, start, _previousToken.end);
}
///////////////////////////////////////////////////////////////////
@ -313,7 +346,7 @@ class Parser {
// Yes, let's parse this file as well.
String fullFN = '${basePath}${filename}';
String contents = _fs.readAll(fullFN);
Parser parser = new Parser(new lang.SourceFile(fullFN, contents), 0,
Parser parser = new Parser(new SourceFile(fullFN, contents), 0,
_fs, basePath);
Stylesheet stylesheet = parser.parse();
return new IncludeDirective(filename, stylesheet, _makeSpan(start));
@ -340,9 +373,9 @@ class Parser {
_eat(TokenKind.LBRACE);
List<lang.Node> productions = [];
List<ASTNode> productions = [];
int start = _peekToken.start;
start = _peekToken.start;
while (!_maybeEat(TokenKind.END_OF_FILE)) {
RuleSet ruleset = processRuleSet();
if (ruleset == null) {
@ -360,7 +393,7 @@ class Parser {
}
}
processRuleSet() {
RuleSet processRuleSet() {
int start = _peekToken.start;
SelectorGroup selGroup = processSelectorGroup();
@ -698,7 +731,7 @@ class Parser {
//
processTerm() {
int start = _peekToken.start;
lang.Token t; // token for term's value
Token t; // token for term's value
var value; // value of term (numeric values)
var unary = "";
@ -778,24 +811,27 @@ class Parser {
}
// What kind of identifier is it?
int value;
try {
// Named color?
value = TokenKind.matchColorName(nameValue.name);
int colorValue = TokenKind.matchColorName(nameValue.name);
// Yes, process the color as an RGB value.
String rgbColor = TokenKind.decimalToHex(value);
int value;
String rgbColor = TokenKind.decimalToHex(colorValue);
try {
value = parseHex(rgbColor);
colorValue = parseHex(rgbColor);
} catch (HexNumberException hne) {
_error('Bad hex number', _makeSpan(start));
}
return new HexColorTerm(value, rgbColor, _makeSpan(start));
return new HexColorTerm(colorValue, rgbColor, _makeSpan(start));
} catch (final error) {
if (error is NoColorMatchException) {
// TODO(terry): Other named things to match with validator?
_warning('Unknown property value ${error.name}', _makeSpan(start));
// TODO(terry): Disable call to _warning need one World class for
// both CSS parser and other parser (e.g., template)
// so all warnings, errors, options, etc. are driven
// from the one World.
// _warning('Unknown property value ${error.name}', _makeSpan(start));
return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
}
}

186
utils/css/source.dart Normal file
View file

@ -0,0 +1,186 @@
// 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.
// TODO(jimhug): This should be an interface to work better with tools.
/**
* Represents a file of source code.
*/
class SourceFile implements Comparable {
// TODO(terry): This filename for in memory buffer. May need to rework if
// filename is used for more than informational.
static String IN_MEMORY_FILE = '<buffer>';
/** The name of the file. */
final String filename;
/** The text content of the file. */
String _text;
/**
* The order of the source file in a given library. This is used while we're
* writing code for a library. A single source file can be used
*/
// TODO(jmesserly): I don't like having properties that are only valid
// sometimes. An alternative would be to store it in a Map that's used by
// WorldGenerator while it's emitting code. This seems simpler.
int orderInLibrary;
List<int> _lineStarts;
SourceFile(this.filename, this._text);
String get text() => _text;
set text(String newText) {
if (newText != _text) {
_text = newText;
_lineStarts = null;
orderInLibrary = null;
}
}
List<int> get lineStarts() {
if (_lineStarts == null) {
var starts = [0];
var index = 0;
while (index < text.length) {
index = text.indexOf('\n', index) + 1;
if (index <= 0) break;
starts.add(index);
}
starts.add(text.length + 1);
_lineStarts = starts;
}
return _lineStarts;
}
int getLine(int position) {
// TODO(jimhug): Implement as binary search.
var starts = lineStarts;
for (int i=0; i < starts.length; i++) {
if (starts[i] > position) return i-1;
}
world.internalError('bad position');
}
int getColumn(int line, int position) {
return position - lineStarts[line];
}
/**
* Create a pretty string representation from a character position
* in the file.
*/
String getLocationMessage(String message, int start,
[int end, bool includeText=false]) {
var line = getLine(start);
var column = getColumn(line, start);
var buf = new StringBuffer(
'${filename}:${line + 1}:${column + 1}: $message');
if (includeText) {
buf.add('\n');
var textLine;
// +1 for 0-indexing, +1 again to avoid the last line of the file
if ((line + 2) < _lineStarts.length) {
textLine = text.substring(_lineStarts[line], _lineStarts[line+1]);
} else {
textLine = text.substring(_lineStarts[line]) + '\n';
}
int toColumn = Math.min(column + (end-start), textLine.length);
if (options.useColors) {
buf.add(textLine.substring(0, column));
buf.add(_RED_COLOR);
buf.add(textLine.substring(column, toColumn));
buf.add(_NO_COLOR);
buf.add(textLine.substring(toColumn));
} else {
buf.add(textLine);
}
int i = 0;
for (; i < column; i++) {
buf.add(' ');
}
if (options.useColors) buf.add(_RED_COLOR);
for (; i < toColumn; i++) {
buf.add('^');
}
if (options.useColors) buf.add(_NO_COLOR);
}
return buf.toString();
}
/** Compares two source files. */
int compareTo(SourceFile other) {
if (orderInLibrary != null && other.orderInLibrary != null) {
return orderInLibrary - other.orderInLibrary;
} else {
return filename.compareTo(other.filename);
}
}
}
/**
* A range of characters in a [SourceFile]. Used to represent the source
* positions of [Token]s and [Node]s for error reporting or other tooling
* work.
*/
// TODO(jmesserly): Rename to Span - but first write cool refactoring tool
class SourceSpan implements Comparable {
/** The [SourceFile] that contains this span. */
final SourceFile file;
/** The character position of the start of this span. */
final int start;
/** The character position of the end of this span. */
final int end;
SourceSpan(this.file, this.start, this.end);
/** Returns the source text corresponding to this [Span]. */
String get text() {
return file.text.substring(start, end);
}
toMessageString(String message) {
return file.getLocationMessage(message, start, end: end, includeText: true);
}
int get line() {
return file.getLine(start);
}
int get column() {
return file.getColumn(line, start);
}
int get endLine() {
return file.getLine(end);
}
int get endColumn() {
return file.getColumn(endLine, end);
}
String get locationText() {
var line = file.getLine(start);
var column = file.getColumn(line, start);
return '${file.filename}:${line + 1}:${column + 1}';
}
/** Compares two source spans by file and position. Handles nulls. */
int compareTo(SourceSpan other) {
if (file == other.file) {
int d = start - other.start;
return d == 0 ? (end - other.end) : d;
}
return file.compareTo(other.file);
}
}

60
utils/css/token.dart Normal file
View file

@ -0,0 +1,60 @@
// 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.
/**
* A single token in the Dart language.
*/
class Token {
/** A member of [TokenKind] specifying what kind of token this is. */
final int kind;
/** The [SourceFile] this token came from. */
final SourceFile source;
/** The start and end indexes into the [SourceFile] of this [Token]. */
final int start, end;
Token(this.kind, this.source, this.start, this.end) {}
Token.fake(this.kind, span)
: this.source = span.file, this.start = span.start, this.end = span.end;
/** Returns the source text corresponding to this [Token]. */
String get text() {
return source.text.substring(start, end);
}
/** 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;
}
}
/** Returns a [SourceSpan] representing the source location. */
SourceSpan get span() {
return new SourceSpan(source, start, end);
}
}
/** A token containing a parsed literal value. */
class LiteralToken extends Token {
var value;
LiteralToken(kind, source, start, end, this.value)
: super(kind, source, start, end);
}
/** A token containing error information. */
class ErrorToken extends Token {
String message;
ErrorToken(kind, source, start, end, this.message)
: super(kind, source, start, end);
}

View file

@ -2,17 +2,19 @@
// 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.
class Tokenizer extends lang.TokenizerBase {
class Tokenizer extends CSSTokenizerBase {
TokenKind cssTokens;
bool _selectorParsing;
Tokenizer(lang.SourceFile source, bool skipWhitespace, [int index = 0])
Tokenizer(SourceFile source, bool skipWhitespace, [int index = 0])
: super(source, skipWhitespace, index), _selectorParsing = false {
cssTokens = new TokenKind();
}
lang.Token next() {
int get startIndex() => _startIndex;
Token next() {
// keep track of our starting position
_startIndex = _index;
@ -47,8 +49,8 @@ class Tokenizer extends lang.TokenizerBase {
int start = _startIndex; // Start where the dot started.
if (maybeEatDigit()) {
// looks like a number dot followed by digit(s).
lang.Token num = finishNumber();
if (num.kind == TokenKind.INTEGER) {
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);
@ -84,7 +86,7 @@ class Tokenizer extends lang.TokenizerBase {
if (maybeEatDigit()) {
return finishNumber();
} else if (TokenizerHelpers.isIdentifierStart(ch)) {
return this.finishIdentifier();
return this.finishIdentifier(ch);
} else {
return _finishToken(TokenKind.MINUS);
}
@ -151,11 +153,11 @@ class Tokenizer extends lang.TokenizerBase {
return _finishToken(TokenKind.DOLLAR);
}
case cssTokens.tokens[TokenKind.BANG]:
lang.Token tok = finishIdentifier();
Token tok = finishIdentifier(ch);
return (tok == null) ? _finishToken(TokenKind.BANG) : tok;
default:
if (TokenizerHelpers.isIdentifierStart(ch)) {
return this.finishIdentifier();
return this.finishIdentifier(ch);
} else if (isDigit(ch)) {
return this.finishNumber();
} else {
@ -166,7 +168,7 @@ class Tokenizer extends lang.TokenizerBase {
// TODO(jmesserly): we need a way to emit human readable error messages from
// the tokenizer.
lang.Token _errorToken() {
Token _errorToken([String message = null]) {
return _finishToken(TokenKind.ERROR);
}
@ -186,7 +188,7 @@ class Tokenizer extends lang.TokenizerBase {
}
// Need to override so CSS version of isIdentifierPart is used.
lang.Token finishIdentifier() {
Token finishIdentifier(int ch) {
while (_index < _text.length) {
// if (!TokenizerHelpers.isIdentifierPart(_text.charCodeAt(_index++))) {
if (!TokenizerHelpers.isIdentifierPart(_text.charCodeAt(_index))) {
@ -207,11 +209,11 @@ class Tokenizer extends lang.TokenizerBase {
}
}
lang.Token finishImportant() {
Token finishImportant() {
}
lang.Token finishNumber() {
Token finishNumber() {
eatDigits();
if (_peekChar() == 46/*.*/) {
@ -254,7 +256,7 @@ class Tokenizer extends lang.TokenizerBase {
return false;
}
lang.Token finishMultiLineComment() {
Token finishMultiLineComment() {
while (true) {
int ch = _nextChar();
if (ch == 0) {
@ -285,18 +287,33 @@ class Tokenizer extends lang.TokenizerBase {
}
/** Static helper methods. */
/** Static helper methods. */
class TokenizerHelpers {
static bool isIdentifierStart(int c) =>
lang.TokenizerHelpers.isIdentifierStart(c) || c == 95 /*_*/ ||
c == 45; /*-*/
static bool isIdentifierStart(int c) {
return ((c >= 97/*a*/ && c <= 122/*z*/) || (c >= 65/*A*/ && c <= 90/*Z*/) ||
c == 95/*_*/ || c == 45 /*-*/);
}
static bool isDigit(int c) => lang.TokenizerHelpers.isDigit(c);
static bool isDigit(int c) {
return (c >= 48/*0*/ && c <= 57/*9*/);
}
static bool isHexDigit(int c) => lang.TokenizerHelpers.isHexDigit(c);
static bool isHexDigit(int c) {
return (isDigit(c) || (c >= 97/*a*/ && c <= 102/*f*/) || (c >= 65/*A*/ && c <= 70/*F*/));
}
static bool isWhitespace(int c) => lang.TokenizerHelpers.isWhitespace(c);
static bool isWhitespace(int c) {
return (c == 32/*' '*/ || c == 9/*'\t'*/ || c == 10/*'\n'*/ || c == 13/*'\r'*/);
}
static bool isIdentifierPart(int c) =>
lang.TokenizerHelpers.isIdentifierPart(c) || c == 45 /*-*/;
static bool isIdentifierPart(int c) {
return (isIdentifierStart(c) || isDigit(c) || c == 45 /*-*/);
}
static bool isInterpIdentifierPart(int c) {
return (isIdentifierStart(c) || isDigit(c));
}
}

View file

@ -0,0 +1,454 @@
// 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.
// Generated by scripts/tokenizer_gen.py.
interface TokenSource {
Token next();
}
class InterpStack {
InterpStack next, previous;
final int quote;
final bool isMultiline;
int depth;
InterpStack(this.previous, this.quote, this.isMultiline): depth = -1;
InterpStack pop() {
return this.previous;
}
static InterpStack push(InterpStack stack, int quote, bool isMultiline) {
var newStack = new InterpStack(stack, quote, isMultiline);
if (stack != null) newStack.previous = stack;
return newStack;
}
}
/**
* The base class for our tokenizer. The hand coded parts are in this file, with
* the generated parts in the subclass Tokenizer.
*/
class CSSTokenizerBase extends TokenizerHelpers implements TokenSource {
final SourceFile _source;
final bool _skipWhitespace;
String _text;
int _index;
int _startIndex;
/** Keeps track of string interpolation state. */
InterpStack _interpStack;
CSSTokenizerBase(this._source, this._skipWhitespace, [index = 0])
: this._index = index {
_text = _source.text;
}
abstract Token next();
abstract int getIdentifierKind();
int _nextChar() {
if (_index < _text.length) {
return _text.charCodeAt(_index++);
} else {
return 0;
}
}
int _peekChar() {
if (_index < _text.length) {
return _text.charCodeAt(_index);
} else {
return 0;
}
}
bool _maybeEatChar(int ch) {
if (_index < _text.length) {
if (_text.charCodeAt(_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, _source, _startIndex, _index);
}
Token _errorToken([String message = null]) {
return new ErrorToken(
TokenKind.ERROR, _source, _startIndex, _index, message);
}
Token finishWhitespace() {
_index--;
while (_index < _text.length) {
final ch = _text.charCodeAt(_index++);
if (ch == 32/*' '*/ || ch == 9/*'\t'*/ || ch == 13/*'\r'*/) {
// do nothing
} else if (ch == 10/*'\n'*/) {
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 == 10/*'\n'*/ || ch == 13/*'\r'*/) {
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 == 42/*'*'*/) {
if (_maybeEatChar(47/*'/'*/)) {
nesting--;
}
} else if (ch == 47/*'/'*/) {
if (_maybeEatChar(42/*'*'*/)) {
nesting++;
}
}
} while (nesting > 0);
if (_skipWhitespace) {
return next();
} else {
return _finishToken(TokenKind.COMMENT);
}
}
void eatDigits() {
while (_index < _text.length) {
if (isDigit(_text.charCodeAt(_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.charCodeAt(_index));
if (digit == -1) {
if (hexLength === null) {
return result;
} else {
return -1;
}
}
_hexDigit(_text.charCodeAt(_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() == 46/*.*/) {
// Handle the case of 1.toString().
_nextChar();
if (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(45/*-*/);
_maybeEatChar(43/*+*/);
eatDigits();
}
if (_peekChar() != 0 && 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, _source, _startIndex, _index, s);
}
Token _makeRawStringToken(bool isMultiline) {
String s;
if (isMultiline) {
// Skip initial newline in multiline strings
int start = _startIndex + 4;
if (_source.text[start] == '\n') start++;
s = _source.text.substring(start, _index - 3);
} else {
s = _source.text.substring(_startIndex + 2, _index - 1);
}
return new LiteralToken(TokenKind.STRING, _source, _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 == 36/*$*/) {
// start of string interp
_interpStack = InterpStack.push(_interpStack, quote, true);
return _makeStringToken(buf, true);
} else if (ch == 92/*\*/) {
var escapeVal = readEscapeSequence();
if (escapeVal == -1) {
return _errorToken("invalid hex escape sequence");
} else {
buf.add(escapeVal);
}
} else {
buf.add(ch);
}
}
}
Token _finishOpenBrace() {
if (_interpStack != null) {
if (_interpStack.depth == -1) {
_interpStack.depth = 1;
} else {
assert(_interpStack.depth >= 0);
_interpStack.depth += 1;
}
}
return _finishToken(TokenKind.LBRACE);
}
Token _finishCloseBrace() {
if (_interpStack != null) {
_interpStack.depth -= 1;
assert(_interpStack.depth >= 0);
}
return _finishToken(TokenKind.RBRACE);
}
Token finishString(int quote) {
if (_maybeEatChar(quote)) {
if (_maybeEatChar(quote)) {
// skip an initial newline
_maybeEatChar(10/*'\n'*/);
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 == 36/*$*/) {
// start of string interp
_interpStack = InterpStack.push(_interpStack, quote, false);
return _makeStringToken(buf, true);
} else if (ch == 0) {
return _errorToken();
} else if (ch == 92/*\*/) {
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 0x0a/*'\n'*/;
case 114/*r*/:
return 0x0d/*'\r'*/;
case 102/*f*/:
return 0x0c/*'\f'*/;
case 98/*b*/:
return 0x08/*'\b'*/;
case 116/*t*/:
return 0x09/*'\t'*/;
case 118/*v*/:
return 0x0b/*'\v'*/;
case 120/*x*/:
hexValue = readHex(2);
break;
case 117/*u*/:
if (_maybeEatChar(123/*{*/)) {
hexValue = readHex();
if (!_maybeEatChar(125/*}*/)) {
return -1;
} else {
break;
}
} 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){
world.fatal('unicode values greater than 2 bytes not implemented yet');
return -1;
} else {
return -1;
}
}
Token finishDot() {
if (isDigit(_peekChar())) {
eatDigits();
return finishNumberExtra(TokenKind.DOUBLE);
} else {
return _finishToken(TokenKind.DOT);
}
}
Token finishIdentifier(int ch) {
while (_index < _text.length) {
if (!isIdentifierPart(_text.charCodeAt(_index++))) {
_index--;
break;
}
}
int kind = getIdentifierKind();
if (kind == TokenKind.IDENTIFIER) {
return _finishToken(TokenKind.IDENTIFIER);
} else {
return _finishToken(kind);
}
}
}

View file

@ -382,7 +382,7 @@ class TokenKind {
if (length == ident.length) {
int idx = offset;
bool match = true;
for (final identIdx = 0; identIdx < ident.length; identIdx++) {
for (int identIdx = 0; identIdx < ident.length; identIdx++) {
int identChar = ident.charCodeAt(identIdx);
int char = text.charCodeAt(idx++);
// Compare lowercase to lowercase then check if char is uppercase.
@ -447,7 +447,7 @@ class TokenKind {
if (length == ident.length) {
int idx = 0;
bool match = true;
for (final identIdx = 0; identIdx < ident.length; identIdx++) {
for (int identIdx = 0; identIdx < ident.length; identIdx++) {
int identChar = ident.charCodeAt(identIdx);
int char = text.charCodeAt(idx++);
// Compare lowercase to lowercase then check if char is uppercase.
@ -485,7 +485,7 @@ class TokenKind {
}
StringBuffer invertResult = new StringBuffer();
for (final idx = result.length - 1; idx >= 0; idx--) {
for (int idx = result.length - 1; idx >= 0; idx--) {
invertResult.add(result[idx]);
}
return invertResult.toString();

View file

@ -4,11 +4,11 @@
#library('csstool');
#import('../../frog/lang.dart', prefix:'lang');
#import('../../frog/file_system.dart');
#import('../../frog/file_system_node.dart');
#import('../../frog/lib/node/node.dart');
#import('dart:io');
#import('css.dart');
#import('../lib/file_system.dart');
#import('../lib/file_system_vm.dart');
FileSystem files;
@ -22,18 +22,18 @@ num time(callback()) {
}
printStats(num elapsed, [String filename = '']) {
print('Parsed\033[32m ${filename}\033[0m in ${elapsed} msec.');
print('Parsed ${_GREEN_COLOR}${filename}${_NO_COLOR} in ${elapsed} msec.');
}
/**
* Run from the `utils/css` directory.
*/
void main() {
// process.argv[0] == node and process.argv[1] == minfrog
assert(process.argv.length == 4);
var optionArgs = new Options().arguments;
assert(optionArgs.length == 2);
String sourceFullFn = process.argv[2];
String outputFullFn = process.argv[3];
String sourceFullFn = optionArgs[0];
String outputFullFn = optionArgs[1];
String sourcePath;
String sourceFilename;
@ -51,7 +51,7 @@ void main() {
initCssWorld();
files = new NodeFileSystem();
files = new VMFileSystem();
if (!files.fileExists(sourceFullFn)) {
// Display colored error message if file is missing.
print("\033[31mCSS source file missing - ${sourceFullFn}\033[0m");
@ -62,7 +62,7 @@ void main() {
final elapsed = time(() {
Parser parser = new Parser(
new lang.SourceFile(sourceFullFn, source), 0, files, sourcePath);
new SourceFile(sourceFullFn, source), 0, files, sourcePath);
stylesheet = parser.parse();
});

View file

@ -8,18 +8,18 @@
/////////////////////////////////////////////////////////////////////////
class Identifier extends lang.Node {
class Identifier extends ASTNode {
String name;
Identifier(this.name, lang.SourceSpan span): super(span);
Identifier(this.name, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitIdentifier(this);
String toString() => name;
}
class Wildcard extends lang.Node {
Wildcard(lang.SourceSpan span): super(span);
class Wildcard extends ASTNode {
Wildcard(SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitWildcard(this);
@ -27,10 +27,10 @@ class Wildcard extends lang.Node {
}
// /* .... */
class CssComment extends lang.Node {
class CssComment extends ASTNode {
String comment;
CssComment(this.comment, lang.SourceSpan span): super(span);
CssComment(this.comment, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitCssComment(this);
@ -39,17 +39,17 @@ class CssComment extends lang.Node {
// CDO/CDC (Comment Definition Open <!-- and Comment Definition Close -->).
class CommentDefinition extends CssComment {
CommentDefinition(String comment, lang.SourceSpan span): super(comment, span);
CommentDefinition(String comment, SourceSpan span): super(comment, span);
visit(TreeVisitor visitor) => visitor.visitCommentDefinition(this);
String toString() => '<!-- ${comment} -->';
}
class SelectorGroup extends lang.Node {
class SelectorGroup extends ASTNode {
List<Selector> _selectors;
SelectorGroup(this._selectors, lang.SourceSpan span): super(span);
SelectorGroup(this._selectors, SourceSpan span): super(span);
List<Selector> get selectors() => _selectors;
@ -69,17 +69,17 @@ class SelectorGroup extends lang.Node {
/** A multiline string showing the node and its children. */
String toDebugString() {
var to = new lang.TreeOutput();
var to = new TreeOutput();
var tp = new TreePrinter(to);
this.visit(tp);
return to.buf.toString();
}
}
class Selector extends lang.Node {
class Selector extends ASTNode {
List<SimpleSelectorSequence> _simpleSelectorSequences;
Selector(this._simpleSelectorSequences, lang.SourceSpan span) : super(span);
Selector(this._simpleSelectorSequences, SourceSpan span) : super(span);
List<SimpleSelectorSequence> get simpleSelectorSequences() =>
_simpleSelectorSequences;
@ -93,8 +93,8 @@ class Selector extends lang.Node {
String toString() {
StringBuffer buff = new StringBuffer();
for (_simpleSelectorSequence in _simpleSelectorSequences) {
buff.add(_simpleSelectorSequence.toString());
for (final simpleSelectorSequence in _simpleSelectorSequences) {
buff.add(simpleSelectorSequence.toString());
}
return buff.toString();
}
@ -102,11 +102,11 @@ class Selector extends lang.Node {
visit(TreeVisitor visitor) => visitor.visitSelector(this);
}
class SimpleSelectorSequence extends lang.Node {
class SimpleSelectorSequence extends ASTNode {
int _combinator; // +, >, ~, NONE
SimpleSelector _selector;
SimpleSelectorSequence(this._selector, lang.SourceSpan span,
SimpleSelectorSequence(this._selector, SourceSpan span,
[this._combinator = TokenKind.COMBINATOR_NONE]) : super(span);
get simpleSelector() => _selector;
@ -132,10 +132,10 @@ class SimpleSelectorSequence extends lang.Node {
/* All other selectors (element, #id, .class, attribute, pseudo, negation,
* namespace, *) are derived from this selector.
*/
class SimpleSelector extends lang.Node {
class SimpleSelector extends ASTNode {
var _name;
SimpleSelector(this._name, lang.SourceSpan span) : super(span);
SimpleSelector(this._name, SourceSpan span) : super(span);
// Name can be an Identifier or WildCard we'll return either the name or '*'.
String get name() => isWildcard() ? '*' : _name.name;
@ -149,7 +149,7 @@ class SimpleSelector extends lang.Node {
// element name
class ElementSelector extends SimpleSelector {
ElementSelector(var name, lang.SourceSpan span) : super(name, span);
ElementSelector(var name, SourceSpan span) : super(name, span);
visit(TreeVisitor visitor) => visitor.visitElementSelector(this);
@ -157,7 +157,7 @@ class ElementSelector extends SimpleSelector {
/** A multiline string showing the node and its children. */
String toDebugString() {
var to = new lang.TreeOutput();
var to = new TreeOutput();
var tp = new TreePrinter(to);
this.visit(tp);
return to.buf.toString();
@ -168,7 +168,7 @@ class ElementSelector extends SimpleSelector {
class NamespaceSelector extends SimpleSelector {
var _namespace; // null, Wildcard or Identifier
NamespaceSelector(this._namespace, var name, lang.SourceSpan span) :
NamespaceSelector(this._namespace, var name, SourceSpan span) :
super(name, span);
String get namespace() => _namespace is Wildcard ? '*' : _namespace.name;
@ -188,7 +188,7 @@ class AttributeSelector extends SimpleSelector {
var _value;
AttributeSelector(Identifier name, this._op, this._value,
lang.SourceSpan span) : super(name, span);
SourceSpan span) : super(name, span);
String matchOperator() {
switch (_op) {
@ -240,7 +240,7 @@ class AttributeSelector extends SimpleSelector {
// #id
class IdSelector extends SimpleSelector {
IdSelector(Identifier name, lang.SourceSpan span) : super(name, span);
IdSelector(Identifier name, SourceSpan span) : super(name, span);
visit(TreeVisitor visitor) => visitor.visitIdSelector(this);
@ -249,7 +249,7 @@ class IdSelector extends SimpleSelector {
// .class
class ClassSelector extends SimpleSelector {
ClassSelector(Identifier name, lang.SourceSpan span) : super(name, span);
ClassSelector(Identifier name, SourceSpan span) : super(name, span);
visit(TreeVisitor visitor) => visitor.visitClassSelector(this);
@ -258,7 +258,7 @@ class ClassSelector extends SimpleSelector {
// :pseudoClass
class PseudoClassSelector extends SimpleSelector {
PseudoClassSelector(Identifier name, lang.SourceSpan span) :
PseudoClassSelector(Identifier name, SourceSpan span) :
super(name, span);
visit(TreeVisitor visitor) => visitor.visitPseudoClassSelector(this);
@ -268,7 +268,7 @@ class PseudoClassSelector extends SimpleSelector {
// ::pseudoElement
class PseudoElementSelector extends SimpleSelector {
PseudoElementSelector(Identifier name, lang.SourceSpan span) :
PseudoElementSelector(Identifier name, SourceSpan span) :
super(name, span);
visit(TreeVisitor visitor) => visitor.visitPseudoElementSelector(this);
@ -279,16 +279,16 @@ class PseudoElementSelector extends SimpleSelector {
// TODO(terry): Implement
// NOT
class NotSelector extends SimpleSelector {
NotSelector(String name, lang.SourceSpan span) : super(name, span);
NotSelector(String name, SourceSpan span) : super(name, span);
visit(TreeVisitor visitor) => visitor.visitNotSelector(this);
}
class Stylesheet extends lang.Node {
class Stylesheet extends ASTNode {
// Contains charset, ruleset, directives (media, page, etc.)
List<lang.Node> _topLevels;
List<ASTNode> _topLevels;
Stylesheet(this._topLevels, lang.SourceSpan span) : super(span) {
Stylesheet(this._topLevels, SourceSpan span) : super(span) {
for (final node in _topLevels) {
assert(node is TopLevelProduction || node is Directive);
}
@ -296,7 +296,7 @@ class Stylesheet extends lang.Node {
visit(TreeVisitor visitor) => visitor.visitStylesheet(this);
List<lang.Node> get topLevels() => _topLevels;
List<ASTNode> get topLevels() => _topLevels;
String toString() {
StringBuffer buff = new StringBuffer();
@ -308,15 +308,15 @@ class Stylesheet extends lang.Node {
/** A multiline string showing the node and its children. */
String toDebugString() {
var to = new lang.TreeOutput();
var to = new TreeOutput();
var tp = new TreePrinter(to);
this.visit(tp);
return to.buf.toString();
}
}
class TopLevelProduction extends lang.Node {
TopLevelProduction(lang.SourceSpan span) : super(span);
class TopLevelProduction extends ASTNode {
TopLevelProduction(SourceSpan span) : super(span);
visit(TreeVisitor visitor) => visitor.visitTopLevelProduction(this);
@ -327,7 +327,7 @@ class RuleSet extends TopLevelProduction {
SelectorGroup _selectorGroup;
DeclarationGroup _declarationGroup;
RuleSet(this._selectorGroup, this._declarationGroup, lang.SourceSpan span) :
RuleSet(this._selectorGroup, this._declarationGroup, SourceSpan span) :
super(span);
SelectorGroup get selectorGroup() => _selectorGroup;
@ -340,8 +340,8 @@ class RuleSet extends TopLevelProduction {
"${_declarationGroup.toString()}}\n";
}
class Directive extends lang.Node {
Directive(lang.SourceSpan span) : super(span);
class Directive extends ASTNode {
Directive(SourceSpan span) : super(span);
String toString() => "Directive";
@ -355,7 +355,7 @@ class ImportDirective extends Directive {
String _import;
List<String> _media;
ImportDirective(this._import, this._media, lang.SourceSpan span) :
ImportDirective(this._import, this._media, SourceSpan span) :
super(span);
visit(TreeVisitor visitor) => visitor.visitImportDirective(this);
@ -379,7 +379,7 @@ class MediaDirective extends Directive {
List<String> _media;
RuleSet _ruleset;
MediaDirective(this._media, this._ruleset, lang.SourceSpan span) :
MediaDirective(this._media, this._ruleset, SourceSpan span) :
super(span);
visit(TreeVisitor visitor) => visitor.visitMediaDirective(this);
@ -404,7 +404,7 @@ class PageDirective extends Directive {
String _pseudoPage;
DeclarationGroup _decls;
PageDirective(this._pseudoPage, this._decls, lang.SourceSpan span) :
PageDirective(this._pseudoPage, this._decls, SourceSpan span) :
super(span);
visit(TreeVisitor visitor) => visitor.visitPageDirective(this);
@ -429,7 +429,7 @@ class KeyFrameDirective extends Directive {
var _name;
List<KeyFrameBlock> _blocks;
KeyFrameDirective(this._name, lang.SourceSpan span) :
KeyFrameDirective(this._name, SourceSpan span) :
_blocks = [], super(span);
add(KeyFrameBlock block) {
@ -451,11 +451,11 @@ class KeyFrameDirective extends Directive {
}
}
class KeyFrameBlock extends lang.Expression {
class KeyFrameBlock extends Expression {
Expressions _blockSelectors;
DeclarationGroup _declarations;
KeyFrameBlock(this._blockSelectors, this._declarations, lang.SourceSpan span):
KeyFrameBlock(this._blockSelectors, this._declarations, SourceSpan span):
super(span);
visit(TreeVisitor visitor) => visitor.visitKeyFrameBlock(this);
@ -473,7 +473,7 @@ class KeyFrameBlock extends lang.Expression {
class FontFaceDirective extends Directive {
List<Declaration> _declarations;
FontFaceDirective(this._declarations, lang.SourceSpan span) : super(span);
FontFaceDirective(this._declarations, SourceSpan span) : super(span);
visit(TreeVisitor visitor) => visitor.visitFontFaceDirective(this);
@ -486,7 +486,7 @@ class IncludeDirective extends Directive {
String _include;
Stylesheet _stylesheet;
IncludeDirective(this._include, this._stylesheet, lang.SourceSpan span) :
IncludeDirective(this._include, this._stylesheet, SourceSpan span) :
super(span);
visit(TreeVisitor visitor) => visitor.visitIncludeDirective(this);
@ -509,7 +509,7 @@ class StyletDirective extends Directive {
String _dartClassName;
List<RuleSet> _rulesets;
StyletDirective(this._dartClassName, this._rulesets, lang.SourceSpan span) :
StyletDirective(this._dartClassName, this._rulesets, SourceSpan span) :
super(span);
bool get isBuiltIn() => false;
@ -524,16 +524,16 @@ class StyletDirective extends Directive {
String toString() => '/* @stylet export as ${_dartClassName} */\n';
}
class Declaration extends lang.Node {
class Declaration extends ASTNode {
Identifier _property;
lang.Expression _expression;
Expression _expression;
bool _important;
Declaration(this._property, this._expression, lang.SourceSpan span) :
Declaration(this._property, this._expression, SourceSpan span) :
_important = false, super(span);
String get property() => _property.name;
lang.Expression get expression() => _expression;
Expression get expression() => _expression;
bool get important() => _important;
set important(bool value) => _important = value;
@ -545,10 +545,10 @@ class Declaration extends lang.Node {
"${_property.name}: ${_expression.toString()}${importantAsString()}";
}
class DeclarationGroup extends lang.Node {
class DeclarationGroup extends ASTNode {
List<Declaration> _declarations;
DeclarationGroup(this._declarations, lang.SourceSpan span) : super(span);
DeclarationGroup(this._declarations, SourceSpan span) : super(span);
List<Declaration> get declarations() => _declarations;
@ -564,27 +564,27 @@ class DeclarationGroup extends lang.Node {
}
}
class OperatorSlash extends lang.Expression {
OperatorSlash(lang.SourceSpan span) : super(span);
class OperatorSlash extends Expression {
OperatorSlash(SourceSpan span) : super(span);
visit(TreeVisitor visitor) => visitor.visitOperatorSlash(this);
String toString() => ' /';
}
class OperatorComma extends lang.Expression {
OperatorComma(lang.SourceSpan span) : super(span);
class OperatorComma extends Expression {
OperatorComma(SourceSpan span) : super(span);
visit(TreeVisitor visitor) => visitor.visitOperatorComma(this);
String toString() => ',';
}
class LiteralTerm extends lang.Expression {
class LiteralTerm extends Expression {
var _value;
String _text;
LiteralTerm(this._value, this._text, lang.SourceSpan span) : super(span);
LiteralTerm(this._value, this._text, SourceSpan span) : super(span);
get value() => _value;
String get text() => _text;
@ -595,7 +595,7 @@ class LiteralTerm extends lang.Expression {
}
class NumberTerm extends LiteralTerm {
NumberTerm(var value, String t, lang.SourceSpan span) : super(value, t, span);
NumberTerm(var value, String t, SourceSpan span) : super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitNumberTerm(this);
}
@ -603,7 +603,7 @@ class NumberTerm extends LiteralTerm {
class UnitTerm extends LiteralTerm {
int _unit;
UnitTerm(var value, String t, lang.SourceSpan span, this._unit) :
UnitTerm(var value, String t, SourceSpan span, this._unit) :
super(value, t, span);
int get unit() => _unit;
@ -615,7 +615,7 @@ class UnitTerm extends LiteralTerm {
}
class LengthTerm extends UnitTerm {
LengthTerm(var value, String t, lang.SourceSpan span,
LengthTerm(var value, String t, SourceSpan 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 ||
@ -629,7 +629,7 @@ class LengthTerm extends UnitTerm {
}
class PercentageTerm extends LiteralTerm {
PercentageTerm(var value, String t, lang.SourceSpan span) :
PercentageTerm(var value, String t, SourceSpan span) :
super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitPercentageTerm(this);
@ -639,7 +639,7 @@ class PercentageTerm extends LiteralTerm {
}
class EmTerm extends LiteralTerm {
EmTerm(var value, String t, lang.SourceSpan span) :
EmTerm(var value, String t, SourceSpan span) :
super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitEmTerm(this);
@ -648,7 +648,7 @@ class EmTerm extends LiteralTerm {
}
class ExTerm extends LiteralTerm {
ExTerm(var value, String t, lang.SourceSpan span) :
ExTerm(var value, String t, SourceSpan span) :
super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitExTerm(this);
@ -657,7 +657,7 @@ class ExTerm extends LiteralTerm {
}
class AngleTerm extends UnitTerm {
AngleTerm(var value, String t, lang.SourceSpan span,
AngleTerm(var value, String t, SourceSpan 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 ||
@ -668,7 +668,7 @@ class AngleTerm extends UnitTerm {
}
class TimeTerm extends UnitTerm {
TimeTerm(var value, String t, lang.SourceSpan span,
TimeTerm(var value, String t, SourceSpan 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 ||
@ -679,7 +679,7 @@ class TimeTerm extends UnitTerm {
}
class FreqTerm extends UnitTerm {
FreqTerm(var value, String t, lang.SourceSpan span,
FreqTerm(var value, String t, SourceSpan span,
[int unit = TokenKind.UNIT_LENGTH_PX]) : super(value, t, span, unit) {
assert(_unit == TokenKind.UNIT_FREQ_HZ || _unit == TokenKind.UNIT_FREQ_KHZ);
}
@ -688,7 +688,7 @@ class FreqTerm extends UnitTerm {
}
class FractionTerm extends LiteralTerm {
FractionTerm(var value, String t, lang.SourceSpan span) :
FractionTerm(var value, String t, SourceSpan span) :
super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitFractionTerm(this);
@ -697,7 +697,7 @@ class FractionTerm extends LiteralTerm {
}
class UriTerm extends LiteralTerm {
UriTerm(String value, lang.SourceSpan span) : super(value, value, span);
UriTerm(String value, SourceSpan span) : super(value, value, span);
visit(TreeVisitor visitor) => visitor.visitUriTerm(this);
@ -705,7 +705,7 @@ class UriTerm extends LiteralTerm {
}
class HexColorTerm extends LiteralTerm {
HexColorTerm(var value, String t, lang.SourceSpan span) :
HexColorTerm(var value, String t, SourceSpan span) :
super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitHexColorTerm(this);
@ -716,7 +716,7 @@ class HexColorTerm extends LiteralTerm {
class FunctionTerm extends LiteralTerm {
Expressions _params;
FunctionTerm(var value, String t, this._params, lang.SourceSpan span)
FunctionTerm(var value, String t, this._params, SourceSpan span)
: super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitFunctionTerm(this);
@ -733,10 +733,10 @@ class FunctionTerm extends LiteralTerm {
}
}
class GroupTerm extends lang.Expression {
class GroupTerm extends Expression {
List<LiteralTerm> _terms;
GroupTerm(lang.SourceSpan span) : _terms = [], super(span);
GroupTerm(SourceSpan span) : _terms = [], super(span);
add(LiteralTerm term) {
_terms.add(term);
@ -760,23 +760,23 @@ class GroupTerm extends lang.Expression {
}
class ItemTerm extends NumberTerm {
ItemTerm(var value, String t, lang.SourceSpan span) : super(value, t, span);
ItemTerm(var value, String t, SourceSpan span) : super(value, t, span);
visit(TreeVisitor visitor) => visitor.visitItemTerm(this);
String toString() => '[${text}]';
}
class Expressions extends lang.Expression {
List<lang.Expression> _expressions;
class Expressions extends Expression {
List<Expression> _expressions;
Expressions(lang.SourceSpan span): super(span), _expressions = [];
Expressions(SourceSpan span): super(span), _expressions = [];
add(lang.Expression expression) {
add(Expression expression) {
_expressions.add(expression);
}
List<lang.Expression> get expressions() => _expressions;
List<Expression> get expressions() => _expressions;
visit(TreeVisitor visitor) => visitor.visitExpressions(this);
@ -797,21 +797,21 @@ class Expressions extends lang.Expression {
}
}
class BinaryExpression extends lang.Expression {
lang.Token op;
lang.Expression x;
lang.Expression y;
class BinaryExpression extends Expression {
Token op;
Expression x;
Expression y;
BinaryExpression(this.op, this.x, this.y, lang.SourceSpan span): super(span);
BinaryExpression(this.op, this.x, this.y, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitBinaryExpression(this);
}
class UnaryExpression extends lang.Expression {
lang.Token op;
lang.Expression self;
class UnaryExpression extends Expression {
Token op;
Expression self;
UnaryExpression(this.op, this.self, lang.SourceSpan span): super(span);
UnaryExpression(this.op, this.self, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitUnaryExpression(this);
}
@ -874,7 +874,7 @@ interface TreeVisitor {
void visitWildcard(Wildcard node);
// TODO(terry): Defined for ../tree.dart.
void visitTypeReference(lang.TypeReference node);
void visitTypeReference(TypeReference node);
}
class TreePrinter implements TreeVisitor {
@ -1273,7 +1273,7 @@ class TreePrinter implements TreeVisitor {
}
// TODO(terry): Defined for frog/tree.dart.
void visitTypeReference(lang.TypeReference node) {
void visitTypeReference(TypeReference node) {
output.heading('Unimplemented');
}
}

124
utils/css/treebase.dart Normal file
View file

@ -0,0 +1,124 @@
// 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.
/**
* The base type for all nodes in a dart abstract syntax tree.
*/
class ASTNode {
/** The source code this [ASTNode] represents. */
SourceSpan span;
ASTNode(this.span) {}
/** Classic double-dispatch visitor for implementing passes. */
abstract visit(TreeVisitor visitor);
/** A multiline string showing the node and its children. */
String toDebugString() {
var to = new TreeOutput();
var tp = new TreePrinter(to);
this.visit(tp);
return to.buf.toString();
}
}
/** The base type for expressions. */
// TODO(terry): Should be abstract class; but frog doesn't support abstract.
class Expression extends ASTNode {
Expression(SourceSpan span): super(span);
visit(TreeVisitor visitor) {} // TODO(terry): remove when abstract.
}
/** The base type for a reference to a [Type]. */
// TODO(terry): Should be abstract class; but frog doesn't support abstract.
class TypeReference extends ASTNode {
TypeReference(SourceSpan span): super(span);
visit(TreeVisitor visitor) {} // TODO(terry): remove when abstract.
}
// TODO(jimhug): Clean-up and factor out of core.
/** Simple class to provide a textual dump of trees for debugging. */
class TreeOutput {
int depth;
StringBuffer buf;
var printer;
static void dump(ASTNode node) {
var o = new TreeOutput();
node.visit(new TreePrinter(o));
print(o.buf);
}
TreeOutput(): this.depth = 0, this.buf = new StringBuffer() {
}
void write(String s) {
for (int i=0; i < depth; i++) {
buf.add(' ');
}
buf.add(s);
}
void writeln(String s) {
write(s);
buf.add('\n');
}
void heading(String name, span) {
write(name);
buf.add(' (${span.locationText})');
buf.add('\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, ASTNode 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.add('null');
buf.add('\n');
} else {
for (var item in list) {
buf.add(item.toString());
buf.add(', ');
}
buf.add('\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(']');
}
}
}

View file

@ -4,8 +4,7 @@
#import('dart:html');
#import('css.dart');
#import('../../frog/lang.dart', prefix:'lang');
#import('../../frog/file_system_memory.dart');
#import('../lib/file_system_memory.dart');
void runCss([bool debug = false, bool parseOnly = false]) {
final Document doc = window.document;
@ -39,8 +38,8 @@ void runCss([bool debug = false, bool parseOnly = false]) {
}
} else if (parseOnly) {
try {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, cssExpr));
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, cssExpr));
Stylesheet stylesheet = parser.parse();
StringBuffer stylesheetTree = new StringBuffer();
String prettyStylesheet = stylesheet.toString();
@ -64,18 +63,16 @@ void runCss([bool debug = false, bool parseOnly = false]) {
final bgcolor = templateValid ? "white" : "red";
final color = templateValid ? "black" : "white";
final valid = templateValid ? "VALID" : "NOT VALID";
String resultStyle = 'resize: none; margin: 0; height: 100%; width: 100%; padding: 5px 7px;';
String validityStyle = '''
font-weight: bold; background-color: $bgcolor; color: $color; border: 1px solid black; border-bottom: 0px solid white;
''';
String resultStyle = "resize:none; margin:0; height:100%; width:100%;"
"padding:5px 7px;";
String validityStyle = "font-weight:bold; background-color:$bgcolor;"
"color:$color; border:1px solid black; border-bottom:0px solid white;";
validity.innerHTML = '''
<div style="$validityStyle">
Expression: $cssExpr is $valid
</div>
''';
result.innerHTML = '''
<textarea style="$resultStyle">$dumpTree</textarea>
''';
result.innerHTML = "<textarea style=\"$resultStyle\">$dumpTree</textarea>";
}
void main() {
@ -128,13 +125,13 @@ void main() {
<tbody>
<tr>
<td>
<button onclick="runCss(true, true)">Parse</button>
<button id=parse>Parse</button>
</td>
<td>
<button onclick="runCss()">Check</button>
<button id=check>Check</button>
</td>
<td>
<button onclick="runCss(true)">Debug</button>
<button id=debug>Debug</button>
</td>
</tr>
</tbody>
@ -175,10 +172,20 @@ void main() {
document.body.style.setProperty("background-color", "lightgray");
document.body.elements.add(element);
// TODO(terry): Needed so runCss isn't shakened out.
if (false) {
ButtonElement parseButton = window.document.query('#parse');
parseButton.on.click.add((MouseEvent e) {
runCss(true, true);
});
ButtonElement checkButton = window.document.query('#check');
checkButton.on.click.add((MouseEvent e) {
runCss();
}
});
ButtonElement debugButton = window.document.query('#debug');
debugButton.on.click.add((MouseEvent e) {
runCss(true);
});
initCssWorld(false);
}

View file

@ -38,7 +38,7 @@ class Validate {
// Validate the @{css expression} only .class and #elementId are valid inside
// of @{...}.
static template(List<lang.Node> selectors, CssWorld cssWorld) {
static template(List<ASTNode> selectors, CssWorld cssWorld) {
var errorSelector; // signal which selector didn't match.
bool found = false; // signal if a selector is matched.
int matches = 0; // < 0 IdSelectors, > 0 ClassSelector
@ -56,7 +56,6 @@ class Validate {
if (!simpleSelector.name.startsWith('_')) {
// TODO(terry): For now iterate through all classes look for faster
// mechanism hash map, etc.
var className;
for (final className in cssWorld.classes) {
if (selector.simpleSelector.name == className) {
matches = _classNameCheck(selector, matches);
@ -106,8 +105,9 @@ class Validate {
}
// Every selector must match.
var selector = selectors[0];
assert((matches >= 0 ? matches : -matches) ==
selectors[0].simpleSelectorSequences.length);
selector.simpleSelectorSequences.length);
}
}

170
utils/css/world.dart Normal file
View file

@ -0,0 +1,170 @@
// 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.
/** The one true [World]. */
World world;
typedef void MessageHandler(String prefix, String message, SourceSpan span);
typedef void PrintHandler(String message);
/**
* Should be called exactly once to setup singleton world.
* Can use world.reset() to reinitialize.
*/
void initializeWorld(var files) {
assert(world == null);
world = new World(files);
world.init();
}
/** Can be thrown on any compiler error and includes source location. */
class CompilerException implements Exception {
final String _message;
final SourceSpan _location;
CompilerException(this._message, this._location);
String toString() {
if (_location != null) {
return 'CompilerException: ${_location.toMessageString(_message)}';
} else {
return 'CompilerException: $_message';
}
}
}
/** Represents a Dart template "world". */
class World {
String template;
var files;
int errors = 0, warnings = 0;
bool seenFatal = false;
MessageHandler messageHandler;
PrintHandler printHandler;
World(this.files);
void reset() {
errors = warnings = 0;
seenFatal = false;
init();
}
init() {
}
// ********************** Message support ***********************
void _message(String color, String prefix, String message,
SourceSpan span, SourceSpan span1, SourceSpan span2, bool throwing) {
if (messageHandler != null) {
// TODO(jimhug): Multiple spans cleaner...
messageHandler(prefix, message, span);
if (span1 != null) {
messageHandler(prefix, message, span1);
}
if (span2 != null) {
messageHandler(prefix, message, span2);
}
} else {
final messageWithPrefix = options.useColors
? (color + prefix + _NO_COLOR + message) : (prefix + message);
var text = messageWithPrefix;
if (span != null) {
text = span.toMessageString(messageWithPrefix);
}
String span1Text = span1 != null ?
span1.toMessageString(messageWithPrefix) : "";
String span2Text = span2 != null ?
span2.toMessageString(messageWithPrefix) : "";
if (printHandler == null) {
print(text);
if (span1 != null) {
print(span1Text);
}
if (span2 != null) {
print(span2Text);
}
} else {
printHandler("${text}\r${span1Text}\r${span2Text}");
}
}
if (throwing) {
throw new CompilerException(prefix + message, span);
}
}
/** [message] is considered a static compile-time error by the Dart lang. */
void error(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
errors++;
_message(_RED_COLOR, 'error: ', message,
span, span1, span2, options.throwOnErrors);
}
/** [message] is considered a type warning by the Dart lang. */
void warning(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
if (options.warningsAsErrors) {
error(message, span, span1, span2);
return;
}
warnings++;
if (options.showWarnings) {
_message(_MAGENTA_COLOR, 'warning: ', message,
span, span1, span2, options.throwOnWarnings);
}
}
/** [message] at [location] is so bad we can't generate runnable code. */
void fatal(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
errors++;
seenFatal = true;
_message(_RED_COLOR, 'fatal: ', message,
span, span1, span2, options.throwOnFatal || options.throwOnErrors);
}
/** [message] at [location] is about a bug in the compiler. */
void internalError(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
_message(_NO_COLOR,
'We are sorry, but...', message, span, span1, span2, true);
}
/**
* [message] at [location] will tell the user about what the compiler
* is doing.
*/
void info(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
if (options.showInfo) {
_message(_GREEN_COLOR, 'info: ', message, span, span1, span2, false);
}
}
bool get hasErrors() => errors > 0;
withTiming(String name, f()) {
final sw = new Stopwatch();
sw.start();
var result = f();
sw.stop();
info('$name in ${sw.elapsedInMs()}msec');
return result;
}
}
// Color constants used for generating messages.
String _GREEN_COLOR = '\u001b[32m';
String _RED_COLOR = '\u001b[31m';
String _MAGENTA_COLOR = '\u001b[35m';
String _NO_COLOR = '\u001b[0m';

View file

@ -0,0 +1,75 @@
// 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.
// TODO(terry): Investigate common library for file I/O shared between frog and tools.
/** Abstraction for file systems and utility functions to manipulate paths. */
#library('file_system');
/**
* Abstraction around file system access to work in a variety of different
* environments.
*/
interface FileSystem {
String readAll(String filename);
void writeString(String outfile, String text);
bool fileExists(String filename);
void createDirectory(String path, [bool recursive]);
void removeDirectory(String path, [bool recursive]);
}
/**
* Replaces all back slashes (\) with forward slashes (/) in [path] and
* return the result.
*/
String canonicalizePath(String path) {
return path.replaceAll('\\', '/');
}
/** Join [path1] to [path2]. */
String joinPaths(String path1, String path2) {
path1 = canonicalizePath(path1);
path2 = canonicalizePath(path2);
var pieces = path1.split('/');
for (var piece in path2.split('/')) {
if (piece == '..' && pieces.length > 0 && pieces.last() != '.'
&& pieces.last() != '..') {
pieces.removeLast();
} else if (piece != '') {
if (pieces.length > 0 && pieces.last() == '.') {
pieces.removeLast();
}
pieces.add(piece);
}
}
return Strings.join(pieces, '/');
}
/** Returns the directory name for the [path]. */
String dirname(String path) {
path = canonicalizePath(path);
int lastSlash = path.lastIndexOf('/', path.length);
if (lastSlash == -1) {
return '.';
} else {
return path.substring(0, lastSlash);
}
}
/** Returns the file name without directory for the [path]. */
String basename(String path) {
path = canonicalizePath(path);
int lastSlash = path.lastIndexOf('/', path.length);
if (lastSlash == -1) {
return path;
} else {
return path.substring(lastSlash + 1);
}
}

View file

@ -0,0 +1,40 @@
// 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.
// TODO(terry): Investigate common library for file I/O shared between frog and tools.
#library('file_system_memory');
#import('file_system.dart');
/**
* [FileSystem] implementation a memory buffer.
*/
class MemoryFileSystem implements FileSystem {
StringBuffer buffer;
MemoryFileSystem() : this.buffer = new StringBuffer();
void writeString(String outfile, String text) {
buffer.add(text);
}
String readAll(String filename) {
return buffer.toString();
}
bool fileExists(String filename) {
return true;
}
void createDirectory(String path, [bool recursive]) {
// TODO(terry): To be implement.
throw 'createDirectory() is not implemented by MemoryFileSystem yet.';
}
void removeDirectory(String path, [bool recursive]) {
// TODO(terry): To be implement.
throw 'removeDirectory() is not implemented by MemoryFileSystem yet.';
}
}

View file

@ -0,0 +1,42 @@
// 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.
// TODO(terry): Investigate common library for file I/O shared between frog and tools.
#library('file_system_vm');
#import('dart:io');
#import('file_system.dart');
#import('dart:utf');
/** File system implementation using the vm api's. */
class VMFileSystem implements FileSystem {
void writeString(String path, String text) {
var file = new File(path).openSync(FileMode.WRITE);
file.writeStringSync(text);
file.closeSync();
}
String readAll(String filename) {
var file = (new File(filename)).openSync();
var length = file.lengthSync();
var buffer = new List<int>(length);
var bytes = file.readListSync(buffer, 0, length);
file.closeSync();
return new String.fromCharCodes(new Utf8Decoder(buffer).decodeRest());
}
bool fileExists(String filename) {
return new File(filename).existsSync();
}
void createDirectory(String path, [bool recursive = false]) {
// TODO(rnystrom): Implement.
throw 'createDirectory() is not implemented by VMFileSystem yet.';
}
void removeDirectory(String path, [bool recursive = false]) {
// TODO(rnystrom): Implement.
throw 'removeDirectory() is not implemented by VMFileSystem yet.';
}
}

767
utils/template/codegen.dart Normal file
View file

@ -0,0 +1,767 @@
// 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.
class CGBlock {
int _blockType; // Code type of this block
int _indent; // Number of spaces to prefix for each statement
bool _inEach; // This block or any currently active blocks is a
// #each. If so then any element marked with a
// var attribute is repeated therefore the var
// is a List type instead of an Element type.
List<CGStatement> _stmts;
int localIndex; // Local variable index (e.g., e0, e1, etc.)
// Block Types:
static final int CONSTRUCTOR = 0;
static final int EACH = 1;
static final int WITH = 2;
CGBlock([this._indent = 4,
this._blockType = CGBlock.CONSTRUCTOR,
this._inEach = false]) :
_stmts = new List<CGStatement>(), localIndex = 0 {
assert(_blockType >= CGBlock.CONSTRUCTOR && _blockType <= CGBlock.WITH);
}
bool get isConstructor() => _blockType == CGBlock.CONSTRUCTOR;
bool get isEach() => _blockType == CGBlock.EACH;
bool get isWith() => _blockType == CGBlock.WITH;
CGStatement push(var elem, var parentName, [bool exact = false]) {
var varName;
if (elem is TemplateElement && elem.hasVar) {
varName = elem.varName;
} else {
varName = localIndex++;
}
CGStatement stmt = new CGStatement(elem, _indent, parentName, varName,
exact, _inEach);
_stmts.add(stmt);
return stmt;
}
void pop() {
_stmts.removeLast();
}
void add(String value) {
if (_stmts.last() != null) {
_stmts.last().add(value);
}
}
CGStatement get last() => _stmts.last();
/**
* Returns mixed list of elements marked with the var attribute. If the
* element is inside of a #each the name exposed is:
*
* List varName;
*
* otherwise it's:
*
* var varName;
*
* TODO(terry): For scalars var varName should be Element tag type e.g.,
*
* DivElement varName;
*/
String get globalDeclarations() {
StringBuffer buff = new StringBuffer();
for (final CGStatement stmt in _stmts) {
buff.add(stmt.globalDeclaration());
}
return buff.toString();
}
/**
* List of statement constructors for each var inside a #each.
*
* ${#each products}
* <div var=myVar>...</div>
* ${/each}
*
* returns:
*
* myVar = [];
*/
String get globalInitializers() {
StringBuffer buff = new StringBuffer();
for (final CGStatement stmt in _stmts) {
buff.add(stmt.globalInitializers());
}
return buff.toString();
}
String get codeBody() {
StringBuffer buff = new StringBuffer();
for (final CGStatement stmt in _stmts) {
buff.add(stmt.emitDartStatement());
}
return buff.toString();
}
}
class CGStatement {
bool _exact; // If True not HTML construct instead exact stmt
bool _repeating; // Stmt in a #each this block or nested block.
StringBuffer _buff;
TemplateElement _elem;
int _indent;
var parentName;
String varName;
bool _globalVariable;
bool _closed;
CGStatement(this._elem, this._indent, this.parentName, var varNameOrIndex,
[this._exact = false, this._repeating = false]) :
_buff = new StringBuffer(), _closed = false {
if (varNameOrIndex is String) {
// We have the global variable name
varName = varNameOrIndex;
_globalVariable = true;
} else {
// local index generate local variable name.
varName = "e${varNameOrIndex}";
_globalVariable = false;
}
}
bool get hasGlobalVariable() => _globalVariable;
String get variableName() => varName;
String globalDeclaration() {
if (hasGlobalVariable) {
String spaces = Codegen.spaces(_indent);
return (_repeating) ?
" List ${varName}; // Repeated elements.\r" : " var ${varName};\r";
}
return "";
}
String globalInitializers() {
if (hasGlobalVariable && _repeating) {
return " ${varName} = [];\r";
}
return "";
}
void add(String value) {
_buff.add(value);
}
bool get isClosed() => _closed;
void close() {
if (_elem is TemplateElement) {
add("</${_elem.tagName}>");
}
_closed = true;
}
String emitDartStatement() {
StringBuffer statement = new StringBuffer();
String spaces = Codegen.spaces(_indent);
if (_exact) {
statement.add("${spaces}${_buff.toString()};\r");
} else {
String localVar = "";
String tmpRepeat;
if (hasGlobalVariable) {
if (_repeating) {
tmpRepeat = "tmp_${varName}";
localVar = "var ";
}
} else {
localVar = "var ";
}
/* Emiting the following code fragment where varName is the attribute
value for var=
varName = new Element.html('HTML GOES HERE');
parent.elements.add(varName);
for repeating elements in a #each:
var tmp_nnn = new Element.html('HTML GOES HERE');
varName.add(tmp_nnn);
parent.elements.add(tmp_nnn);
for elements w/o var attribute set:
var eNNN = new Element.html('HTML GOES HERE');
parent.elements.add(eNNN);
*/
if (tmpRepeat == null) {
statement.add("${spaces}${localVar}${varName} = new Element.html('");
} else {
statement.add("${spaces}${localVar}${tmpRepeat} = new Element.html('");
}
statement.add(_buff.toString());
if (tmpRepeat == null) {
statement.add(
"');\r${spaces}${parentName}.elements.add(${varName});\r");
} else {
statement.add(
"');\r${spaces}${parentName}.elements.add(${tmpRepeat});\r");
statement.add("${spaces}${varName}.add(${tmpRepeat});\r");
}
}
return statement.toString();
}
}
class Codegen {
static final String SPACES = " ";
static String spaces(int numSpaces) {
return SPACES.substring(0, numSpaces);
}
// TODO(terry): Before generating Dart class need a validate phase that
// checks mangles all class names to be prefix with the
// template name to avoid any class name collisions. Also,
// investigate possible runtime check mode to insure that only
// valid CSS class names are used (in case someone uses strings
// and not the generated getters to the CSS class selector. This
// mode would be slower would require that every class name set
// (maybe jQuery too) is for a particular view (requires walking
// the HTML tree looking for a parent template prefix that
// matches the CSS prefix. (more thinking needed).
static String generate(List<Template> templates, String filename) {
List<String> fileParts = filename.split('.');
assert(fileParts.length == 2);
filename = fileParts[0];
StringBuffer buff = new StringBuffer();
int injectId = 0; // Inject function id
buff.add("// Generated Dart class from HTML template.\r");
buff.add("// DO NOT EDIT.\r\r");
buff.add("String safeHTML(String html) {\r");
buff.add(" // TODO(terry): Escaping for XSS vulnerabilities TBD.\r");
buff.add(" return html;\r");
buff.add("}\r\r");
String addStylesheetFuncName = "add_${filename}_templatesStyles";
for (final template in templates) {
// Emit the template class.
TemplateSignature sig = template.signature;
buff.add(_emitClass(sig.name, sig.params, template.content,
addStylesheetFuncName));
}
// TODO(terry): Stylesheet aggregator should not be global needs to be
// bound to this template file not global to the app.
// Emit the stylesheet aggregator.
buff.add("\r\r// Inject all templates stylesheet once into the head.\r");
buff.add("bool ${filename}_stylesheet_added = false;\r");
buff.add("void ${addStylesheetFuncName}() {\r");
buff.add(" if (!${filename}_stylesheet_added) {\r");
buff.add(" StringBuffer styles = new StringBuffer();\r\r");
buff.add(" // All templates stylesheet.\r");
for (final template in templates) {
TemplateSignature sig = template.signature;
buff.add(" styles.add(${sig.name}.stylesheet);\r");
}
buff.add("\r ${filename}_stylesheet_added = true;\r");
buff.add(" document.head.elements.add(new Element.html('<style>"
"\${styles.toString()}</style>'));\r");
buff.add(" }\r");
buff.add("}\r");
return buff.toString();
}
static String _emitCSSSelectors(css.Stylesheet stylesheet) {
if (stylesheet == null) {
return "";
}
List<String> classes = [];
for (final production in stylesheet.topLevels) {
if (production is css.IncludeDirective) {
for (final topLevel in production.styleSheet.topLevels) {
if (topLevel is css.RuleSet) {
classes = css.Generate.computeClassSelectors(topLevel, classes);
}
}
} else if (production is css.RuleSet) {
classes = css.Generate.computeClassSelectors(production, classes);
}
}
List<String> dartNames = [];
for (final String knownClass in classes) {
StringBuffer dartName = new StringBuffer();
List<String> splits = knownClass.split('-');
if (splits.length > 0) {
dartName.add(splits[0]);
for (int idx = 1; idx < splits.length; idx++) {
String part = splits[idx];
// Character between 'a'..'z' mapped to 'A'..'Z'
dartName.add("${part[0].toUpperCase()}${part.substring(1)}");
}
dartNames.add(dartName.toString());
}
}
StringBuffer buff = new StringBuffer();
if (classes.length > 0) {
assert(classes.length == dartNames.length);
buff.add("\r // CSS class selectors for this template.\r");
for (int i = 0; i < classes.length; i++) {
buff.add(
" static String get ${dartNames[i]}() => \"${classes[i]}\";\r");
}
}
return buff.toString();
}
static String _emitClass(String className,
List<Map<Identifier, Identifier>> params,
TemplateContent content,
String addStylesheetFuncName) {
StringBuffer buff = new StringBuffer();
// Emit the template class.
buff.add("class ${className} {\r");
buff.add(" Element _fragment;\r\r");
bool anyParams = false;
for (final param in params) {
buff.add(" ${param['type']} ${param['name']};\r");
anyParams = true;
}
if (anyParams) buff.add("\r");
ElemCG ecg = new ElemCG();
ecg.pushBlock();
// TODO(terry): Only supports singlely rooted need to fix.
ecg.emitConstructHtml(content.html.children[0], "", "_fragment");
// Create all element names marked with var.
String decls = ecg.globalDeclarations;
if (decls.length > 0) {
buff.add("\r // Elements bound to a variable:\r");
buff.add("${decls}\r");
}
// Create the constructor.
buff.add(" ${className}(");
bool firstParam = true;
for (final param in params) {
if (!firstParam) {
buff.add(", ");
}
buff.add("this.${param['name']}");
firstParam = false;
}
buff.add(") {\r");
String initializers = ecg.globalInitializers;
if (initializers.length > 0) {
buff.add(" //Global initializers.\r");
buff.add("${initializers}\r");
}
buff.add(" // Insure stylesheet for template exist in the document.\r");
buff.add(" ${addStylesheetFuncName}();\r\r");
buff.add(" _fragment = new Element.tag('div');\r");
buff.add(ecg.codeBody); // HTML for constructor to build.
buff.add(" }\r\r"); // End constructor
buff.add(" Element get root() => _fragment.nodes.first;\r");
// Emit all CSS class selectors:
buff.add(_emitCSSSelectors(content.css));
// Emit the injection functions.
buff.add("\r // Injection functions:");
for (final expr in ecg.expressions) {
buff.add("${expr}");
}
buff.add("\r // Each functions:\r");
for (var eachFunc in ecg.eachs) {
buff.add("${eachFunc}\r");
}
buff.add("\r // With functions:\r");
for (var withFunc in ecg.withs) {
buff.add("${withFunc}\r");
}
buff.add("\r // CSS for this template.\r");
buff.add(" static final String stylesheet = ");
if (content.css != null) {
buff.add("\'\'\'\r ${content.css.toString()}\r");
buff.add(" \'\'\';\r\r");
// TODO(terry): Emit all known selectors for this template.
buff.add(" // Stylesheet class selectors:\r");
} else {
buff.add("\"\";\r");
}
buff.add("}\r"); // End class
return buff.toString();
}
}
class ElemCG {
// List of identifiers and quoted strings (single and double quoted).
var identRe = const RegExp(
"\s*('\"\\'\\\"[^'\"\\'\\\"]+'\"\\'\\\"|[_A-Za-z][_A-Za-z0-9]*)");
List<CGBlock> _cgBlocks;
StringBuffer _globalDecls; // Global var declarations for all blocks.
StringBuffer _globalInits; // Global List var initializtion for all
// blocks in a #each.
String funcCall; // Func call after element creation?
List<String> expressions; // List of injection function declarations.
List<String> eachs; // List of each function declarations.
List<String> withs; // List of with function declarations.
ElemCG() :
expressions = [],
eachs = [],
withs = [],
_cgBlocks = [],
_globalDecls = new StringBuffer(),
_globalInits = new StringBuffer();
bool get isLastBlockConstructor() {
CGBlock block = _cgBlocks.last();
return block.isConstructor;
}
// Any current active #each blocks.
bool anyEachBlocks(int blockToCreateType) {
bool result = blockToCreateType == CGBlock.EACH;
for (final CGBlock block in _cgBlocks) {
if (block.isEach) {
result = result || true;
}
}
return result;
}
void pushBlock([int indent = 4, int blockType = CGBlock.CONSTRUCTOR]) {
closeStatement();
_cgBlocks.add(new CGBlock(indent, blockType, anyEachBlocks(blockType)));
}
void popBlock() {
_globalDecls.add(lastBlock.globalDeclarations);
_globalInits.add(lastBlock.globalInitializers);
_cgBlocks.removeLast();
}
CGStatement pushStatement(var elem, var parentName) {
return lastBlock.push(elem, parentName, false);
}
CGStatement pushExactStatement(var elem, var parentName) {
return lastBlock.push(elem, parentName, true);
}
bool get isClosedStatement() => lastBlock.last.isClosed;
void closeStatement() {
if (lastBlock != null && lastBlock.last != null &&
!lastBlock.last.isClosed) {
lastBlock.last.close();
}
}
String get lastVariableName() {
if (lastBlock != null && lastBlock.last != null) {
return lastBlock.last.variableName;
}
}
CGBlock get lastBlock() => _cgBlocks.length > 0 ? _cgBlocks.last() : null;
void add(String str) {
_cgBlocks.last().add(str);
}
String get globalDeclarations() {
assert(_cgBlocks.length == 1); // Only constructor body should be left.
_globalDecls.add(lastBlock.globalDeclarations);
return _globalDecls.toString();
}
String get globalInitializers() {
assert(_cgBlocks.length == 1); // Only constructor body should be left.
_globalInits.add(lastBlock.globalInitializers);
return _globalInits.toString();
}
String get codeBody() {
closeStatement();
return _cgBlocks.last().codeBody;
}
/* scopeName for expression
* parentVarOrIndex if # it's a local variable if string it's an exposed
* name (specified by the var attribute) for this element.
*
*/
emitElement(var elem,
[String scopeName = "",
var parentVarOrIdx = 0,
bool immediateNestedEach = false]) {
if (elem is TemplateElement) {
if (!elem.isFragment) {
add("<${elem.tagName}${elem.attributesToString()}>");
}
String prevParent = lastVariableName;
for (var childElem in elem.children) {
if (childElem is TemplateElement) {
if (childElem.hasVar) {
closeStatement();
emitConstructHtml(childElem, scopeName, prevParent,
childElem.varName);
closeStatement();
} else {
closeStatement();
emitConstructHtml(childElem, scopeName, prevParent);
closeStatement();
}
} else {
emitElement(childElem, scopeName, parentVarOrIdx);
}
}
} else if (elem is TemplateText) {
add("${elem.value}");
} else if (elem is TemplateExpression) {
emitExpressions(elem, scopeName);
} else if (elem is TemplateEachCommand) {
// Signal to caller new block coming in, returns "each_" prefix
emitEach(elem, "List", elem.listName.name, "parent", immediateNestedEach);
} else if (elem is TemplateWithCommand) {
// Signal to caller new block coming in, returns "each_" prefix
emitWith(elem, "var", elem.objectName.name, "parent");
}
}
// TODO(terry): Hack prefixing all names with "${scopeName}." but don't touch
// quoted strings.
String _resolveNames(String expr, String prefixPart) {
StringBuffer newExpr = new StringBuffer();
Iterable<Match> matches = identRe.allMatches(expr);
int lastIdx = 0;
for (Match m in matches) {
if (m.start() > lastIdx) {
newExpr.add(expr.substring(lastIdx, m.start()));
}
bool identifier = true;
if (m.start() > 0) {
int charCode = expr.charCodeAt(m.start() - 1);
// Starts with ' or " then it's not an identifier.
identifier = charCode != 34 /* " */ && charCode != 39 /* ' */;
}
String strMatch = expr.substring(m.start(), m.end());
if (identifier) {
newExpr.add("${prefixPart}.${strMatch}");
} else {
// Quoted string don't touch.
newExpr.add("${strMatch}");
}
lastIdx = m.end();
}
if (expr.length > lastIdx) {
newExpr.add(expr.substring(lastIdx));
}
return newExpr.toString();
}
/**
* Construct the HTML each top-level node get's it's own variable.
*
* TODO(terry): Might want to optimize if the other top-level nodes have no
* control structures (with, each, if, etc.). We could
* synthesize a root node and create all the top-level nodes
* under the root node with one innerHTML.
*/
void emitConstructHtml(var elem,
[String scopeName = "",
String parentName = "parent",
var varIndex = 0,
bool immediateNestedEach = false]) {
if (elem is TemplateElement) {
// Never look at the root node (fragment) get it's children.
if (elem.isFragment) {
elem = elem.children[0];
}
CGStatement stmt = pushStatement(elem, parentName);
emitElement(elem, scopeName, stmt.hasGlobalVariable ?
stmt.variableName : varIndex);
} else {
emitElement(elem, scopeName, varIndex, immediateNestedEach);
}
}
/* Any references to products.sales needs to be remaped to item.sales
* for now it's a hack look for first dot and replace with item.
*/
String eachIterNameToItem(String iterName) {
String newName = iterName;
var dotForIter = iterName.indexOf('.');
if (dotForIter >= 0) {
newName = "item${iterName.substring(dotForIter)}";
}
return newName;
}
emitExpressions(TemplateExpression elem, String scopeName) {
StringBuffer func = new StringBuffer();
String newExpr = elem.expression;
if (scopeName.length > 0) {
// In a block #command need the scope passed in.
add("\$\{inject_${expressions.length}(item)\}");
func.add("\r String inject_${expressions.length}(var item) {\r");
// Escape all single-quotes, this expression is embedded as a string
// parameter for the call to safeHTML.
newExpr = _resolveNames(newExpr.replaceAll("'", "\\'"), "item");
} else {
// Not in a block #command item isn't passed in.
add("\$\{inject_${expressions.length}()\}");
func.add("\r String inject_${expressions.length}() {\r");
}
func.add(" return safeHTML('\$\{${newExpr}\}');\r");
func.add(" }\r");
expressions.add(func.toString());
}
emitEach(TemplateEachCommand elem, String iterType, String iterName,
var parentVarOrIdx, bool nestedImmediateEach) {
TemplateDocument docFrag = elem.documentFragment;
int eachIndex = eachs.length;
eachs.add("");
StringBuffer funcBuff = new StringBuffer();
// Prepare function call "each_N(iterName," parent param computed later.
String funcName = "each_${eachIndex}";
funcBuff.add(" ${funcName}(${iterType} items, Element parent) {\r");
funcBuff.add(" for (var item in items) {\r");
pushBlock(6, CGBlock.EACH);
TemplateElement docFragChild = docFrag.children[0];
var children = docFragChild.isFragment ?
docFragChild.children : docFrag.children;
for (var child in children) {
// If any immediate children of the parent #each is an #each then
// so we need to pass the outer #each parent not the last statement's
// variableName when calling the nested #each.
bool eachChild = (child is TemplateEachCommand);
emitConstructHtml(child, iterName, parentVarOrIdx, 0, eachChild);
}
funcBuff.add(codeBody);
popBlock();
funcBuff.add(" }\r");
funcBuff.add(" }\r");
eachs[eachIndex] = funcBuff.toString();
// If nested each then we want to pass the parent otherwise we'll use the
// varName.
var varName = nestedImmediateEach ? "parent" : lastBlock.last.variableName;
pushExactStatement(elem, parentVarOrIdx);
// Setup call to each func as "each_n(xxxxx, " the parent param is filled
// in later when we known the parent variable.
add("${funcName}(${eachIterNameToItem(iterName)}, ${varName})");
}
emitWith(TemplateWithCommand elem, String withType, String withName,
var parentVarIndex) {
TemplateDocument docFrag = elem.documentFragment;
int withIndex = withs.length;
withs.add("");
StringBuffer funcBuff = new StringBuffer();
// Prepare function call "each_N(iterName," parent param computed later.
String funcName = "with_${withIndex}";
funcBuff.add(" ${funcName}(${withType} item, Element parent) {\r");
pushBlock(CGBlock.WITH);
TemplateElement docFragChild = docFrag.children[0];
var children = docFragChild.isFragment ?
docFragChild.children : docFrag.children;
for (var child in children) {
emitConstructHtml(child, withName, "parent");
}
funcBuff.add(codeBody);
popBlock();
funcBuff.add(" }\r");
withs[withIndex] = funcBuff.toString();
var varName = lastBlock.last.variableName;
pushExactStatement(elem, parentVarIndex);
// Setup call to each func as "each_n(xxxxx, " the parent param is filled
// in later when we known the parent variable.
add("${funcName}(${withName}, ${varName})");
}
}

View file

@ -0,0 +1,348 @@
// 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.
// Generated by scripts/tree_gen.py.
/////////////////////////////////////////////////////////////////////////
// CSS specific types:
/////////////////////////////////////////////////////////////////////////
class Identifier extends ASTNode {
String name;
Identifier(this.name, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitIdentifier(this);
String toString() => name;
}
class StringValue extends ASTNode {
String value;
StringValue(this.value, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitStringValue(this);
String toString() => value;
}
// CDO/CDC (Comment Definition Open <!-- and Comment Definition Close -->).
class CommentDefinition extends ASTNode {
String comment;
CommentDefinition(this.comment, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitCommentDefinition(this);
String toString() => '<!-- ${comment} -->';
}
class Template extends ASTNode {
TemplateSignature signature;
TemplateContent content;
Template(this.signature, this.content, SourceSpan span):
super(span);
visit(TreeVisitor visitor) => visitor.visitTemplate(this);
String toString() => "${signature.toString()} \r{\r${content.toString()}\r}\r";
}
class TemplateSignature extends ASTNode {
String name;
List<Map<Identifier, Identifier>> params; // Map of {type:, name:}
TemplateSignature(this.name, this.params, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateSignature(this);
String paramsAsString() {
StringBuffer buff = new StringBuffer();
bool first = true;
for (final param in params) {
if (!first) {
buff.add(", ");
}
if (param['type'] != null) {
buff.add(param['type']);
buff.add(' ');
}
buff.add(param['name']);
first = false;
}
return buff.toString();
}
String toString() => "template ${name}(${paramsAsString()})";
}
class TemplateChildren extends ASTNode {
List<ASTNode> children;
TemplateChildren(this.children, SourceSpan span): super(span);
TemplateChildren.empty(SourceSpan span): super(span);
add(var child) {
if (children == null) {
children = new List<ASTNode>();
}
children.add(child);
}
ASTNode last() => children.last();
ASTNode removeLast() => children.removeLast();
bool get anyChildren() => children != null && children.length > 0;
visit(TreeVisitor visitor) => visitor.visitTemplateChildren(this);
String toString() {
StringBuffer buff = new StringBuffer();
if (children != null) {
for (final child in children) {
buff.add(child.toString());
}
}
return buff.toString();
}
}
class TemplateContent extends ASTNode {
css.Stylesheet css;
TemplateDocument html;
TemplateContent(this.css, this.html, SourceSpan span) : super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateContent(this);
}
class TemplateDocument extends TemplateChildren {
TemplateDocument(List<ASTNode> children, SourceSpan span):
super(children, span);
visit(TreeVisitor visitor) => visitor.visitTemplateDocument(this);
}
class TemplateElement extends TemplateChildren {
int tagTokenId;
List<TemplateAttribute> attributes;
StringValue _varName;
TemplateElement(this.tagTokenId, SourceSpan span): super.empty(span);
TemplateElement.fragment(SourceSpan span) : super.empty(span), tagTokenId = -1;
TemplateElement.attributes(this.tagTokenId, this.attributes, this._varName,
SourceSpan span): super.empty(span);
bool get isFragment() => tagTokenId == -1;
bool get anyAttributes() => attributes != null;
visit(TreeVisitor visitor) => visitor.visitTemplateElement(this);
bool get hasVar() => _varName != null;
String get varName() => hasVar ? _varName.value : null;
String attributesToString() {
StringBuffer buff = new StringBuffer();
for (final attr in attributes) {
buff.add(' ${attr.toString()}');
}
return buff.toString();
}
String get tagName() => isFragment ?
'root' : TokenKind.tagNameFromTokenId(tagTokenId);
String tagStartToString() => "<${tagName} ${attributesToString()}>";
String tagEndToString() => "</${tagName}>";
String toString() {
StringBuffer buff = new StringBuffer(tagStartToString());
if (children != null) {
for (final child in children) {
buff.add(child.toString());
}
buff.add(tagEndToString());
}
return buff.toString();
}
}
class TemplateAttribute extends ASTNode {
String name;
String value;
TemplateAttribute(this.name, this.value, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateAttribute(this);
String toString() => "${name}=\"${value}\"";
}
class TemplateText extends ASTNode {
String value;
TemplateText(this.value, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateText(this);
String toString() => value;
}
class TemplateExpression extends ASTNode {
String expression;
TemplateExpression(this.expression, SourceSpan span): super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateExpression(this);
String toString() => "\$\{value}";
}
class TemplateEachCommand extends ASTNode {
String listName;
TemplateDocument documentFragment;
TemplateEachCommand(this.listName, this.documentFragment, SourceSpan span):
super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateEachCommand(this);
String toString() => "\$\{#each ${listName}}";
}
class TemplateWithCommand extends ASTNode {
String objectName;
TemplateDocument documentFragment;
TemplateWithCommand(this.objectName, this.documentFragment, SourceSpan span):
super(span);
visit(TreeVisitor visitor) => visitor.visitTemplateWithCommand(this);
String toString() => "\$\{#with ${objectName}}";
}
interface TreeVisitor {
void visitIdentifier(Identifier node);
void visitStringValue(StringValue node);
void visitCommentDefinition(CommentDefinition node);
void visitTemplate(Template node);
void visitTemplateSignature(TemplateSignature node);
void visitTemplateChildren(TemplateChildren node);
void visitTemplateDocument(TemplateDocument node);
void visitTemplateContent(TemplateContent node);
void visitTemplateElement(TemplateElement node);
void visitTemplateAttribute(TemplateAttribute node);
void visitTemplateText(TemplateText node);
void visitTemplateExpression(TemplateExpression node);
void visitTemplateEachCommand(TemplateEachCommand node);
void visitTemplateWithCommand(TemplateWithCommand node);
}
class TreePrinter implements TreeVisitor {
var output;
TreePrinter(this.output) { output.printer = this; }
void visitIdentifier(Identifier node) {
output.heading('Identifier(${output.toValue(node.name)})', node.span);
}
void visitStringValue(StringValue node) {
output.heading('"${output.toValue(node.value)}"', node.span);
}
void visitCommentDefinition(CommentDefinition node) {
output.heading('CommentDefinition (CDO/CDC)', node.span);
output.depth++;
output.writeValue('comment value', node.comment);
output.depth--;
}
void visitTemplate(Template node) {
output.heading('Template', node.span);
output.depth++;
visitTemplateSignature(node.signature);
visitTemplateContent(node.content);
output.depth--;
}
void visitTemplateSignature(TemplateSignature node) {
output.heading('TemplateSignature', node.span);
output.depth++;
output.writeValue('Template', node);
output.depth--;
}
void visitTemplateChildren(TemplateChildren node) {
output.writeNodeList('children', node.children);
}
void visitTemplateContent(TemplateContent node) {
visitTemplateDocument(node.html);
if (node.css != null) {
output.depth++;
output.writeValue('---CSS---', node.css.toString());
output.depth--;
}
}
void visitTemplateDocument(TemplateDocument node) {
output.heading('Content', node.span);
output.depth++;
var child = node.children[0];
assert(node.children.length == 1 && child.tagTokenId == -1);
output.writeNodeList("document", node.children);
output.depth--;
}
void visitTemplateElement(TemplateElement node) {
output.heading('Element', node.span);
output.depth++;
output.writeValue('tag', node.tagName);
if (node.attributes != null && (node.attributes.length > 0)) {
output.writeNodeList("attributes", node.attributes);
}
visitTemplateChildren(node);
output.depth--;
}
void visitTemplateAttribute(TemplateAttribute node) {
output.heading('Attribute', node.span);
output.depth++;
output.writeValue('name', node.name);
output.writeValue('value', node.value);
output.depth--;
}
void visitTemplateText(TemplateText node) {
output.heading('Text', node.span);
output.writeValue('value', node.value);
}
void visitTemplateExpression(TemplateExpression node) {
output.heading('Interpolate', node.span);
output.writeValue('expression', "\$\{${node.expression}\}");
}
void visitTemplateEachCommand(TemplateEachCommand node) {
output.heading('#each', node.span);
output.writeValue('list', node.listName);
visitTemplateDocument(node.documentFragment);
}
void visitTemplateWithCommand(TemplateWithCommand node) {
output.heading('#with', node.span);
output.writeValue('object', node.objectName);
visitTemplateDocument(node.documentFragment);
}
}

567
utils/template/parser.dart Normal file
View file

@ -0,0 +1,567 @@
// 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
class TagStack {
List<ASTNode> _stack;
TagStack(var elem) : _stack = [] {
_stack.add(elem);
}
void push(var elem) {
_stack.add(elem);
}
ASTNode pop() {
return _stack.removeLast();
}
top() {
return _stack.last();
}
}
// TODO(terry): Cleanup returning errors from CSS to common World error
// handler.
class ErrorMsgRedirector {
void displayError(String msg) {
if (world.printHandler != null) {
world.printHandler(msg);
} else {
print("Unhandler Error: ${msg}");
}
world.errors++;
}
}
/**
* A simple recursive descent parser for HTML.
*/
class Parser {
Tokenizer tokenizer;
var _fs; // If non-null filesystem to read files.
final SourceFile source;
Token _previousToken;
Token _peekToken;
PrintHandler printHandler;
Parser(this.source, [int start = 0, this._fs = null]) {
tokenizer = new Tokenizer(source, true, start);
_peekToken = tokenizer.next();
_previousToken = null;
}
// Main entry point for parsing an entire HTML file.
List<Template> parse([PrintHandler handler = null]) {
printHandler = handler;
List<Template> productions = [];
int start = _peekToken.start;
while (!_maybeEat(TokenKind.END_OF_FILE)) {
Template template = processTemplate();
if (template != null) {
productions.add(template);
}
}
return productions;
}
/** Generate an error if [source] has not been completely consumed. */
void checkEndOfFile() {
_eat(TokenKind.END_OF_FILE);
}
/** 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([bool inTag = true]) {
_previousToken = _peekToken;
_peekToken = tokenizer.next(inTag);
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);
}
bool _maybeEat(int kind) {
if (_peekToken.kind == kind) {
_previousToken = _peekToken;
_peekToken = tokenizer.next();
return true;
} else {
return false;
}
}
void _eat(int kind) {
if (!_maybeEat(kind)) {
_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 (final e) {
message = 'parsing error expected $expected';
}
_error(message, tok.span);
}
void _error(String message, [SourceSpan location=null]) {
if (location === null) {
location = _peekToken.span;
}
if (printHandler == null) {
world.fatal(message, location); // syntax errors are fatal for now
} else {
// TODO(terry): Need common World view for css and template parser.
// For now this is how we return errors from CSS - ugh.
printHandler(message);
}
}
void _warning(String message, [SourceSpan location=null]) {
if (location === null) {
location = _peekToken.span;
}
if (printHandler == null) {
world.warning(message, location);
} else {
// TODO(terry): Need common World view for css and template parser.
// For now this is how we return errors from CSS - ugh.
printHandler(message);
}
}
SourceSpan _makeSpan(int start) {
return new SourceSpan(source, start, _previousToken.end);
}
///////////////////////////////////////////////////////////////////
// Top level productions
///////////////////////////////////////////////////////////////////
Template processTemplate() {
var template;
int start = _peekToken.start;
// Handle the template keyword followed by template signature.
_eat(TokenKind.TEMPLATE_KEYWORD);
if (_peekIdentifier()) {
final templateName = identifier();
List<Map<Identifier, Identifier>> params =
new List<Map<Identifier, Identifier>>();
_eat(TokenKind.LPAREN);
start = _peekToken.start;
while (true) {
// TODO(terry): Need robust Dart argument parser (e.g.,
// List<String> arg1, etc).
var type = processAsIdentifier();
var paramName = processAsIdentifier();
if (type != null && paramName != null) {
params.add({'type': type, 'name' : paramName});
if (!_maybeEat(TokenKind.COMMA)) {
break;
}
} else {
_error("Template paramter missing type and name", _makeSpan(start));
break;
}
}
_eat(TokenKind.RPAREN);
TemplateSignature sig =
new TemplateSignature(templateName.name, params, _makeSpan(start));
TemplateContent content = processTemplateContent();
template = new Template(sig, content, _makeSpan(start));
}
return template;
}
// All tokens are identifiers tokenizer is geared to HTML if identifiers are
// HTML element or attribute names we need them as an identifier. Used by
// template signatures and expressions in ${...}
Identifier processAsIdentifier() {
int start = _peekToken.start;
if (_peekIdentifier()) {
return identifier();
} else if (TokenKind.validTagName(_peek())) {
var tok = _next();
return new Identifier(TokenKind.tagNameFromTokenId(tok.kind),
_makeSpan(start));
}
}
css.Stylesheet processCSS() {
// Is there a CSS block?
if (_peekIdentifier()) {
int start = _peekToken.start;
if (identifier().name == 'css') {
_eat(TokenKind.LBRACE);
css.Stylesheet cssCtx = processCSSContent(source, tokenizer.startIndex);
// TODO(terry): Hack, restart template parser where CSS parser stopped.
tokenizer.index = lastCSSIndexParsed;
_next(false);
_eat(TokenKind.RBRACE); // close } of css block
return cssCtx;
}
}
}
TemplateContent processTemplateContent() {
css.Stylesheet stylesheet;
_eat(TokenKind.LBRACE);
int start = _peekToken.start;
stylesheet = processCSS();
var elems = new TemplateElement.fragment(_makeSpan(_peekToken.start));
var templateDoc = processHTML(elems);
// TODO(terry): Should allow css { } to be at beginning or end of the
// template's content. Today css only allow at beginning
// because the css {...} is sucked in as a text node. We'll
// need a special escape for css maybe:
//
// ${#css}
// ${/css}
//
// uggggly!
_eat(TokenKind.RBRACE);
return new TemplateContent(stylesheet, templateDoc, _makeSpan(start));
}
int lastCSSIndexParsed; // TODO(terry): Hack, last good CSS parsed.
css.Stylesheet processCSSContent(var cssSource, int start) {
try {
css.Parser parser = new css.Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, cssSource.text), start);
css.Stylesheet stylesheet = parser.parse(false, new ErrorMsgRedirector());
var lastParsedChar = parser.tokenizer.startIndex;
lastCSSIndexParsed = lastParsedChar;
return stylesheet;
} catch (final cssParseException) {
// TODO(terry): Need SourceSpan from CSS parser to pass onto _error.
_error("Unexcepted CSS error: ${cssParseException.toString()}");
}
}
/* TODO(terry): Assume template { }, single close curley as a text node
* inside of the template would need to be escaped maybe \}
*/
processHTML(TemplateElement root) {
assert(root.isFragment);
TagStack stack = new TagStack(root);
int start = _peekToken.start;
bool done = false;
while (!done) {
if (_maybeEat(TokenKind.LESS_THAN)) {
// Open tag
start = _peekToken.start;
if (TokenKind.validTagName(_peek())) {
Token tagToken = _next();
Map<String, TemplateAttribute> attrs = processAttributes();
String varName;
if (attrs.containsKey('var')) {
varName = attrs['var'].value;
attrs.remove('var');
}
int scopeType; // 1 implies scoped, 2 implies non-scoped element.
if (_maybeEat(TokenKind.GREATER_THAN)) {
scopeType = 1;
} else if (_maybeEat(TokenKind.END_NO_SCOPE_TAG)) {
scopeType = 2;
}
if (scopeType > 0) {
var elem = new TemplateElement.attributes(tagToken.kind,
attrs.getValues(), varName, _makeSpan(start));
stack.top().add(elem);
if (scopeType == 1) {
// Maybe more nested tags/text?
stack.push(elem);
}
}
} else {
// Close tag
_eat(TokenKind.SLASH);
if (TokenKind.validTagName(_peek())) {
Token tagToken = _next();
_eat(TokenKind.GREATER_THAN);
var elem = stack.pop();
if (elem is TemplateElement && !elem.isFragment) {
if (elem.tagTokenId != tagToken.kind) {
_error('Tag doesn\'t match expected </${elem.tagName}> got ' +
'</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
}
} else {
// Too many end tags.
_error('Too many end tags at ' +
'</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
}
}
}
} else if (_maybeEat(TokenKind.START_COMMAND)) {
if (_peekIdentifier()) {
var commandName = identifier();
switch (commandName.name) {
case "each":
case "with":
if (_peekIdentifier()) {
var listName = identifier();
_eat(TokenKind.RBRACE);
var frag = new TemplateElement.fragment(
_makeSpan(_peekToken.start));
TemplateDocument docFrag = processHTML(frag);
if (docFrag != null) {
var span = _makeSpan(start);
var cmd;
if (commandName.name == "each") {
cmd = new TemplateEachCommand(listName, docFrag, span);
} else if (commandName.name == "with") {
cmd = new TemplateWithCommand(listName, docFrag, span);
}
stack.top().add(cmd);
stack.push(cmd);
}
// Process ${/commandName}
_eat(TokenKind.END_COMMAND);
// Close command ${/commandName}
if (_peekIdentifier()) {
commandName = identifier();
switch (commandName.name) {
case "each":
case "with":
case "if":
case "else":
break;
default:
_error('Unknown command \${#${commandName}}');
}
var elem = stack.pop();
if (elem is TemplateEachCommand &&
commandName.name == "each") {
} else if (elem is TemplateWithCommand &&
commandName.name == "with") {
} /*else if (elem is TemplateIfCommand && commandName == "if") {
}
*/else {
String expectedCmd;
if (elem is TemplateEachCommand) {
expectedCmd = "\${/each}";
} /* TODO(terry): else other commands as well */
_error('mismatched command expected ${expectedCmd} got...');
return;
}
_eat(TokenKind.RBRACE);
} else {
_error('Missing command name \${/commandName}');
}
} else {
_error("Missing listname for #each command");
}
break;
case "if":
break;
case "else":
break;
default:
_error("Unknown template command");
}
}
} else if (_peekKind(TokenKind.END_COMMAND)) {
break;
} else {
// Any text or expression nodes?
var nodes = processTextNodes();
if (nodes.length > 0) {
assert(stack.top() != null);
for (var node in nodes) {
stack.top().add(node);
}
} else {
break;
}
}
}
/*
if (elems.children.length != 1) {
print("ERROR: No closing end-tag for elems ${elems[elems.length - 1]}");
}
*/
var docChildren = new List<ASTNode>();
docChildren.add(stack.pop());
return new TemplateDocument(docChildren, _makeSpan(start));
}
/* Map is used so only last unique attribute name is remembered and to quickly
* find the var attribute.
*/
Map<String, TemplateAttribute> processAttributes() {
Map<String, TemplateAttribute> attrs = new Map();
int start = _peekToken.start;
String elemName;
while (_peekIdentifier() ||
(elemName = TokenKind.elementsToName(_peek())) != null) {
var attrName;
if (elemName == null) {
attrName = identifier();
} else {
attrName = new Identifier(elemName, _makeSpan(start));
_next();
}
var attrValue;
// Attribute value?
if (_peek() == TokenKind.ATTR_VALUE) {
var tok = _next();
attrValue = new StringValue(tok.value, _makeSpan(tok.start));
}
attrs[attrName.name] =
new TemplateAttribute(attrName, attrValue, _makeSpan(start));
start = _peekToken.start;
elemName = null;
}
return attrs;
}
identifier() {
var tok = _next();
if (!TokenKind.isIdentifier(tok.kind)) {
_error('expected identifier, but found $tok', tok.span);
}
return new Identifier(tok.text, _makeSpan(tok.start));
}
List<ASTNode> processTextNodes() {
// May contain TemplateText and TemplateExpression.
List<ASTNode> nodes = [];
int start = _peekToken.start;
bool inExpression = false;
StringBuffer stringValue = new StringBuffer();
// Gobble up everything until we hit <
int runningStart = _peekToken.start;
while (_peek() != TokenKind.LESS_THAN &&
(_peek() != TokenKind.RBRACE ||
(_peek() == TokenKind.RBRACE && inExpression)) &&
_peek() != TokenKind.END_OF_FILE) {
// Beginning of expression?
if (_peek() == TokenKind.START_EXPRESSION) {
if (stringValue.length > 0) {
// We have a real text node create the text node.
nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
stringValue = new StringBuffer();
start = _peekToken.start;
}
inExpression = true;
}
var tok = _next(false);
if (tok.kind == TokenKind.RBRACE && inExpression) {
// We have an expression create the expression node, don't save the }
inExpression = false;
nodes.add(new TemplateExpression(stringValue.toString(),
_makeSpan(start)));
stringValue = new StringBuffer();
start = _peekToken.start;
} else if (tok.kind != TokenKind.START_EXPRESSION) {
// Only save the the contents between ${ and }
stringValue.add(tok.text);
}
}
if (stringValue.length > 0) {
nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
}
return nodes;
}
}

186
utils/template/source.dart Normal file
View file

@ -0,0 +1,186 @@
// 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.
// TODO(jimhug): This should be an interface to work better with tools.
/**
* Represents a file of source code.
*/
class SourceFile implements Comparable {
// TODO(terry): This filename for in memory buffer. May need to rework if
// filename is used for more than informational.
static String IN_MEMORY_FILE = '<buffer>';
/** The name of the file. */
final String filename;
/** The text content of the file. */
String _text;
/**
* The order of the source file in a given library. This is used while we're
* writing code for a library. A single source file can be used
*/
// TODO(jmesserly): I don't like having properties that are only valid
// sometimes. An alternative would be to store it in a Map that's used by
// WorldGenerator while it's emitting code. This seems simpler.
int orderInLibrary;
List<int> _lineStarts;
SourceFile(this.filename, this._text);
String get text() => _text;
set text(String newText) {
if (newText != _text) {
_text = newText;
_lineStarts = null;
orderInLibrary = null;
}
}
List<int> get lineStarts() {
if (_lineStarts == null) {
var starts = [0];
var index = 0;
while (index < text.length) {
index = text.indexOf('\n', index) + 1;
if (index <= 0) break;
starts.add(index);
}
starts.add(text.length + 1);
_lineStarts = starts;
}
return _lineStarts;
}
int getLine(int position) {
// TODO(jimhug): Implement as binary search.
var starts = lineStarts;
for (int i=0; i < starts.length; i++) {
if (starts[i] > position) return i-1;
}
world.internalError('bad position');
}
int getColumn(int line, int position) {
return position - lineStarts[line];
}
/**
* Create a pretty string representation from a character position
* in the file.
*/
String getLocationMessage(String message, int start,
[int end, bool includeText=false]) {
var line = getLine(start);
var column = getColumn(line, start);
var buf = new StringBuffer(
'${filename}:${line + 1}:${column + 1}: $message');
if (includeText) {
buf.add('\n');
var textLine;
// +1 for 0-indexing, +1 again to avoid the last line of the file
if ((line + 2) < _lineStarts.length) {
textLine = text.substring(_lineStarts[line], _lineStarts[line+1]);
} else {
textLine = text.substring(_lineStarts[line]) + '\n';
}
int toColumn = Math.min(column + (end-start), textLine.length);
if (options.useColors) {
buf.add(textLine.substring(0, column));
buf.add(_RED_COLOR);
buf.add(textLine.substring(column, toColumn));
buf.add(_NO_COLOR);
buf.add(textLine.substring(toColumn));
} else {
buf.add(textLine);
}
int i = 0;
for (; i < column; i++) {
buf.add(' ');
}
if (options.useColors) buf.add(_RED_COLOR);
for (; i < toColumn; i++) {
buf.add('^');
}
if (options.useColors) buf.add(_NO_COLOR);
}
return buf.toString();
}
/** Compares two source files. */
int compareTo(SourceFile other) {
if (orderInLibrary != null && other.orderInLibrary != null) {
return orderInLibrary - other.orderInLibrary;
} else {
return filename.compareTo(other.filename);
}
}
}
/**
* A range of characters in a [SourceFile]. Used to represent the source
* positions of [Token]s and [Node]s for error reporting or other tooling
* work.
*/
// TODO(jmesserly): Rename to Span - but first write cool refactoring tool
class SourceSpan implements Comparable {
/** The [SourceFile] that contains this span. */
final SourceFile file;
/** The character position of the start of this span. */
final int start;
/** The character position of the end of this span. */
final int end;
SourceSpan(this.file, this.start, this.end);
/** Returns the source text corresponding to this [Span]. */
String get text() {
return file.text.substring(start, end);
}
toMessageString(String message) {
return file.getLocationMessage(message, start, end: end, includeText: true);
}
int get line() {
return file.getLine(start);
}
int get column() {
return file.getColumn(line, start);
}
int get endLine() {
return file.getLine(end);
}
int get endColumn() {
return file.getColumn(endLine, end);
}
String get locationText() {
var line = file.getLine(start);
var column = file.getColumn(line, start);
return '${file.filename}:${line + 1}:${column + 1}';
}
/** Compares two source spans by file and position. Handles nulls. */
int compareTo(SourceSpan other) {
if (file == other.file) {
int d = start - other.start;
return d == 0 ? (end - other.end) : d;
}
return file.compareTo(other.file);
}
}

78
utils/template/template Executable file
View file

@ -0,0 +1,78 @@
#!/bin/bash
# To process a template, run this script with the path to a .tmpl file, e.g.,
#
# $ $DART/utils/template/template srcfile [outfile]
#
# where:
# srcfile - the template file file.tmpl (if .tmpl missing .tmpl is assumed).
# outfile - the Dart class file to generate, if outfile not specified then
# outfile is srcfile.dart (srcfile name w/o ext).
#
# To use your Dart VM must be on your $PATH e.g.,
#
# export PATH=$PATH:/home/<your name>/dart-all/dart/out/Release_ia32/
# Minimum/Maximum number of arguments
MINARGS=1
MAXARGS=2
# get the number of command-line arguments given
ARGC=$#
SRCFILE=
OUTFILE=
# check to make sure enough arguments were given or exit
if [[ $ARGC -eq $MINARGS ]];
then
SRCFILE=$1
IDX=`expr index "$SRCFILE" .`
if [[ $IDX -eq 0 ]];
then
# No extension
FILENAME=$SRCFILE
EXT=
else
FILENAME=${SRCFILE:0:(IDX-1)}
EXT=${SRCFILE:IDX}
fi
TMPL_EXT='tmpl'
if [ "$EXT" = "$TMPL_EXT" ];
then
SRCFILE="$PWD/$1"
OUTFILE="$PWD/$FILENAME.dart"
else
SRCFILE="$PWD/$1.$TMPL_EXT"
OUTFILE="$PWD/$FILENAME.dart"
fi
elif [[ $ARGC -eq 2 ]]
then
SRCFILE="$PWD/$1"
OUTFILE="$PWD/$2"
elif [[ $ARGC -lt $MINARGS ]];
then
echo -e "\033[31mToo few arguments given (Minimum $MINARGS argument)\033[0m"
exit 1
elif [[ $ARGC -gt $MAXARGS ]];
then
echo -e "\033[31mToo many arguments\033[0m"
exit 1
fi
if [ "$SRCFILE" = "$OUTFILE" ];
then
echo -e "\033[31msource file must be different from the output file \033[0m"
echo -e "source file: $SRCFILE"
echo -e "output file: $OUTFILE"
exit 1
fi
# Path of this bash script.
BASE_DIR="$( cd "$( dirname "$0" )" && pwd )"
# Pre-process the file
dart $BASE_DIR/tool.dart $SRCFILE $OUTFILE

View file

@ -0,0 +1,46 @@
// 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.
#library('template');
#import('../css/css.dart', prefix:'css');
#import('../lib/file_system_memory.dart');
#source('tokenkind.dart');
#source('token.dart');
#source('source.dart');
#source('tokenizer_base.dart');
#source('tokenizer.dart');
#source('parser.dart');
#source('codegen.dart');
#source('tree.dart');
#source('htmltree.dart');
#source('utils.dart');
#source('temploptions.dart');
#source('world.dart');
void initHtmlWorld([bool commandLine = true]) {
var fs = new MemoryFileSystem();
// parseOptions([], fs);
initializeWorld(fs);
// TODO(terry): Should be set by arguments. When run as a tool these aren't
// set when run internaly set these so we can compile CSS and catch any
// problems programmatically.
// options.throwOnErrors = true;
// options.throwOnFatal = true;
// options.useColors = commandLine ? true : false;
}
// TODO(terry): Add obfuscation mapping file.
List<Template> templateParseAndValidate(String template) {
Parser parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
template));
return parser.parse();
// if (tree != null) {
// Validate.template(tree.selectors, world);
// }
}

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Template UITest</title>
</head>
<body>
<script type="application/dart" src="uitest.dart"></script>
</body>
</html>

View file

@ -0,0 +1,93 @@
// 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.
/** General options used by the compiler. */
TemplateOptions options;
/** Extracts options from command-line arguments. */
void parseOptions(List<String> args, var files) {
assert(options == null);
options = new TemplateOptions(args, files);
}
class TemplateOptions {
/** Location of corelib and other special dart libraries. */
String libDir;
/* The top-level dart script to compile. */
String dartScript;
/** Where to place the generated code. */
String outfile;
// Options that modify behavior significantly
bool warningsAsErrors = false;
bool checkOnly = false;
// Message support
bool throwOnErrors = false;
bool throwOnWarnings = false;
bool throwOnFatal = false;
bool showInfo = false;
bool showWarnings = true;
bool useColors = true;
/**
* Options to be used later for passing to the generated code. These are all
* the arguments after the first dart script, if any.
*/
List<String> childArgs;
TemplateOptions(List<String> args, var files) {
bool ignoreUnrecognizedFlags = false;
bool passedLibDir = false;
childArgs = [];
// Start from 2 to skip arguments representing the compiler command
// (node/python followed by frogsh/frog.py).
loop: for (int i = 2; i < args.length; i++) {
var arg = args[i];
switch (arg) {
case '--check-only':
checkOnly = true;
break;
case '--verbose':
showInfo = true;
break;
case '--suppress_warnings':
showWarnings = false;
break;
case '--warnings_as_errors':
warningsAsErrors = true;
break;
case '--throw_on_errors':
throwOnErrors = true;
break;
case '--throw_on_warnings':
throwOnWarnings = true;
break;
case '--no_colors':
useColors = false;
break;
case '--checked':
checkOnly = true;
break;
default:
if (!ignoreUnrecognizedFlags) {
print('unrecognized flag: "$arg"');
}
}
}
}
}

60
utils/template/token.dart Normal file
View file

@ -0,0 +1,60 @@
// 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.
/**
* A single token in the Dart language.
*/
class Token {
/** A member of [TokenKind] specifying what kind of token this is. */
final int kind;
/** The [SourceFile] this token came from. */
final SourceFile source;
/** The start and end indexes into the [SourceFile] of this [Token]. */
final int start, end;
Token(this.kind, this.source, this.start, this.end) {}
Token.fake(this.kind, span)
: this.source = span.file, this.start = span.start, this.end = span.end;
/** Returns the source text corresponding to this [Token]. */
String get text() {
return source.text.substring(start, end);
}
/** 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;
}
}
/** Returns a [SourceSpan] representing the source location. */
SourceSpan get span() {
return new SourceSpan(source, start, end);
}
}
/** A token containing a parsed literal value. */
class LiteralToken extends Token {
var value;
LiteralToken(kind, source, start, end, this.value)
: super(kind, source, start, end);
}
/** A token containing error information. */
class ErrorToken extends Token {
String message;
ErrorToken(kind, source, start, end, this.message)
: super(kind, source, start, end);
}

View file

@ -0,0 +1,302 @@
// 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.
class Tokenizer extends TokenizerBase {
TokenKind tmplTokens;
bool _selectorParsing;
Tokenizer(SourceFile source, bool skipWhitespace, [int index = 0])
: super(source, skipWhitespace, index), _selectorParsing = false {
tmplTokens = new TokenKind();
}
int get startIndex() => _startIndex;
void set index(int idx) {
_index = idx;
}
Token next([bool inTag = true]) {
// keep track of our starting position
_startIndex = _index;
if (_interpStack != null && _interpStack.depth == 0) {
var istack = _interpStack;
_interpStack = _interpStack.pop();
/* TODO(terry): Enable for variable and string interpolation.
* if (istack.isMultiline) {
* return finishMultilineStringBody(istack.quote);
* } else {
* return finishStringBody(istack.quote);
* }
*/
}
int ch;
ch = _nextChar();
switch(ch) {
case 0:
return _finishToken(TokenKind.END_OF_FILE);
case tmplTokens.tokens[TokenKind.SPACE]:
case tmplTokens.tokens[TokenKind.TAB]:
case tmplTokens.tokens[TokenKind.NEWLINE]:
case tmplTokens.tokens[TokenKind.RETURN]:
return finishWhitespace();
case tmplTokens.tokens[TokenKind.END_OF_FILE]:
return _finishToken(TokenKind.END_OF_FILE);
case tmplTokens.tokens[TokenKind.LPAREN]:
return _finishToken(TokenKind.LPAREN);
case tmplTokens.tokens[TokenKind.RPAREN]:
return _finishToken(TokenKind.RPAREN);
case tmplTokens.tokens[TokenKind.COMMA]:
return _finishToken(TokenKind.COMMA);
case tmplTokens.tokens[TokenKind.LBRACE]:
return _finishToken(TokenKind.LBRACE);
case tmplTokens.tokens[TokenKind.RBRACE]:
return _finishToken(TokenKind.RBRACE);
case tmplTokens.tokens[TokenKind.LESS_THAN]:
return _finishToken(TokenKind.LESS_THAN);
case tmplTokens.tokens[TokenKind.GREATER_THAN]:
return _finishToken(TokenKind.GREATER_THAN);
case tmplTokens.tokens[TokenKind.EQUAL]:
if (inTag) {
if (_maybeEatChar(tmplTokens.tokens[TokenKind.SINGLE_QUOTE])) {
return finishQuotedAttrValue(
tmplTokens.tokens[TokenKind.SINGLE_QUOTE]);
} else if (_maybeEatChar(tmplTokens.tokens[TokenKind.DOUBLE_QUOTE])) {
return finishQuotedAttrValue(
tmplTokens.tokens[TokenKind.DOUBLE_QUOTE]);
} else if (isAttributeValueStart(_peekChar())) {
return finishAttrValue();
}
}
return _finishToken(TokenKind.EQUAL);
case tmplTokens.tokens[TokenKind.SLASH]:
if (_maybeEatChar(tmplTokens.tokens[TokenKind.GREATER_THAN])) {
return _finishToken(TokenKind.END_NO_SCOPE_TAG); // />
} else {
return _finishToken(TokenKind.SLASH);
}
case tmplTokens.tokens[TokenKind.DOLLAR]:
if (_maybeEatChar(tmplTokens.tokens[TokenKind.LBRACE])) {
if (_maybeEatChar(tmplTokens.tokens[TokenKind.HASH])) {
return _finishToken(TokenKind.START_COMMAND); // ${#
} else if (_maybeEatChar(tmplTokens.tokens[TokenKind.SLASH])) {
return _finishToken(TokenKind.END_COMMAND); // ${/
} else {
return _finishToken(TokenKind.START_EXPRESSION); // ${
}
} else {
return _finishToken(TokenKind.DOLLAR);
}
default:
if (TokenizerHelpers.isIdentifierStart(ch)) {
return this.finishIdentifier();
} else if (isDigit(ch)) {
return this.finishNumber();
} else {
return _errorToken();
}
}
}
// TODO(jmesserly): we need a way to emit human readable error messages from
// the tokenizer.
Token _errorToken([String message = null]) {
return _finishToken(TokenKind.ERROR);
}
int getIdentifierKind() {
// Is the identifier an element?
int tokId = TokenKind.matchElements(_text, _startIndex,
_index - _startIndex);
if (tokId == -1) {
// No, is it an attribute?
// tokId = TokenKind.matchAttributes(_text, _startIndex, _index - _startIndex);
}
if (tokId == -1) {
tokId = TokenKind.matchKeywords(_text, _startIndex, _index - _startIndex);
}
return tokId >= 0 ? tokId : TokenKind.IDENTIFIER;
}
// Need to override so CSS version of isIdentifierPart is used.
Token finishIdentifier() {
while (_index < _text.length) {
// if (!TokenizerHelpers.isIdentifierPart(_text.charCodeAt(_index++))) {
if (!TokenizerHelpers.isIdentifierPart(_text.charCodeAt(_index))) {
// _index--;
break;
} else {
_index += 1;
}
}
if (_interpStack != null && _interpStack.depth == -1) {
_interpStack.depth = 0;
}
int kind = getIdentifierKind();
if (kind == TokenKind.IDENTIFIER) {
return _finishToken(TokenKind.IDENTIFIER);
} else {
return _finishToken(kind);
}
}
Token _makeAttributeValueToken(List<int> buf) {
final s = new String.fromCharCodes(buf);
return new LiteralToken(TokenKind.ATTR_VALUE, _source, _startIndex, _index,
s);
}
/* quote if -1 signals to read upto first whitespace otherwise read upto
* single or double quote char.
*/
Token finishQuotedAttrValue([int quote = -1]) {
var buf = new List<int>();
while (true) {
int ch = _nextChar();
if (ch == quote) {
return _makeAttributeValueToken(buf);
} else if (ch == 0) {
return _errorToken();
} else {
buf.add(ch);
}
}
}
Token finishAttrValue() {
var buf = new List<int>();
while (true) {
int ch = _peekChar();
if (isWhitespace(ch) || isSlash(ch) || isCloseTag(ch)) {
return _makeAttributeValueToken(buf);
} else if (ch == 0) {
return _errorToken();
} else {
buf.add(_nextChar());
}
}
}
Token finishNumber() {
eatDigits();
if (_peekChar() == 46/*.*/) {
// Handle the case of 1.toString().
_nextChar();
if (isDigit(_peekChar())) {
eatDigits();
return _finishToken(TokenKind.DOUBLE);
} else {
_index -= 1;
}
}
return _finishToken(TokenKind.INTEGER);
}
bool maybeEatDigit() {
if (_index < _text.length && isDigit(_text.charCodeAt(_index))) {
_index += 1;
return true;
}
return false;
}
void eatHexDigits() {
while (_index < _text.length) {
if (isHexDigit(_text.charCodeAt(_index))) {
_index += 1;
} else {
return;
}
}
}
bool maybeEatHexDigit() {
if (_index < _text.length && isHexDigit(_text.charCodeAt(_index))) {
_index += 1;
return true;
}
return false;
}
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 == tmplTokens.tokens[TokenKind.MINUS]) {
/* Check if close part of Comment Definition --> (CDC). */
if (_maybeEatChar(tmplTokens.tokens[TokenKind.MINUS])) {
if (_maybeEatChar(tmplTokens.tokens[TokenKind.GREATER_THAN])) {
if (_skipWhitespace) {
return next();
} else {
return _finishToken(TokenKind.HTML_COMMENT);
}
}
}
}
}
return _errorToken();
}
}
/** Static helper methods. */
class TokenizerHelpers {
static bool isIdentifierStart(int c) {
return ((c >= 97/*a*/ && c <= 122/*z*/) ||
(c >= 65/*A*/ && c <= 90/*Z*/) || c == 95/*_*/);
}
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 isWhitespace(int c) {
return (c == 32/*' '*/ || c == 9/*'\t'*/ || c == 10/*'\n'*/ ||
c == 13/*'\r'*/);
}
static bool isIdentifierPart(int c) {
return (isIdentifierStart(c) || isDigit(c) || c == 45/*-*/ ||
c == 58/*:*/ || c == 46/*.*/);
}
static bool isInterpIdentifierPart(int c) {
return (isIdentifierStart(c) || isDigit(c));
}
static bool isAttributeValueStart(int c) {
return !isWhitespace(c) && !isSlash(c) && !isCloseTag(c);
}
static bool isSlash(int c) {
return (c == 47/* / */);
}
static bool isCloseTag(int c) {
return (c == 62/* > */);
}
}

View file

@ -0,0 +1,464 @@
// 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.
// Generated by scripts/tokenizer_gen.py.
interface TokenSource {
Token next();
}
class InterpStack {
InterpStack next, previous;
final int quote;
final bool isMultiline;
int depth;
InterpStack(this.previous, this.quote, this.isMultiline): depth = -1;
InterpStack pop() {
return this.previous;
}
static InterpStack push(InterpStack stack, int quote, bool isMultiline) {
var newStack = new InterpStack(stack, quote, isMultiline);
if (stack != null) newStack.previous = stack;
return newStack;
}
}
/**
* The base class for our tokenizer. The hand coded parts are in this file, with
* the generated parts in the subclass Tokenizer.
*/
class TokenizerBase extends TokenizerHelpers implements TokenSource {
final SourceFile _source;
final bool _skipWhitespace;
String _text;
int _index;
int _startIndex;
/** Keeps track of string interpolation state. */
InterpStack _interpStack;
TokenizerBase(this._source, this._skipWhitespace, [index = 0])
: this._index = index {
_text = _source.text;
}
abstract Token next();
abstract int getIdentifierKind();
int _nextChar() {
if (_index < _text.length) {
return _text.charCodeAt(_index++);
} else {
return 0;
}
}
int _peekChar() {
if (_index < _text.length) {
return _text.charCodeAt(_index);
} else {
return 0;
}
}
bool _maybeEatChar(int ch) {
if (_index < _text.length) {
if (_text.charCodeAt(_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, _source, _startIndex, _index);
}
Token _errorToken([String message = null]) {
return new ErrorToken(
TokenKind.ERROR, _source, _startIndex, _index, message);
}
Token finishWhitespace() {
_index--;
while (_index < _text.length) {
final ch = _text.charCodeAt(_index++);
if (ch == 32/*' '*/ || ch == 9/*'\t'*/ || ch == 13/*'\r'*/) {
// do nothing
} else if (ch == 10/*'\n'*/) {
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 == 10/*'\n'*/ || ch == 13/*'\r'*/) {
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 == 42/*'*'*/) {
if (_maybeEatChar(47/*'/'*/)) {
nesting--;
}
} else if (ch == 47/*'/'*/) {
if (_maybeEatChar(42/*'*'*/)) {
nesting++;
}
}
} while (nesting > 0);
if (_skipWhitespace) {
return next();
} else {
return _finishToken(TokenKind.COMMENT);
}
}
void eatDigits() {
while (_index < _text.length) {
if (isDigit(_text.charCodeAt(_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.charCodeAt(_index));
if (digit == -1) {
if (hexLength === null) {
return result;
} else {
return -1;
}
}
_hexDigit(_text.charCodeAt(_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() == 46/*.*/) {
// Handle the case of 1.toString().
_nextChar();
if (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(45/*-*/);
_maybeEatChar(43/*+*/);
eatDigits();
}
if (_peekChar() != 0 && 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, _source, _startIndex, _index, s);
}
Token _makeRawStringToken(bool isMultiline) {
String s;
if (isMultiline) {
// Skip initial newline in multiline strings
int start = _startIndex + 4;
if (_source.text[start] == '\n') start++;
s = _source.text.substring(start, _index - 3);
} else {
s = _source.text.substring(_startIndex + 2, _index - 1);
}
return new LiteralToken(TokenKind.STRING, _source, _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 == 36/*$*/) {
// start of string interp
_interpStack = InterpStack.push(_interpStack, quote, true);
return _makeStringToken(buf, true);
} else if (ch == 92/*\*/) {
var escapeVal = readEscapeSequence();
if (escapeVal == -1) {
return _errorToken("invalid hex escape sequence");
} else {
buf.add(escapeVal);
}
} else {
buf.add(ch);
}
}
}
Token _finishOpenBrace() {
if (_interpStack != null) {
if (_interpStack.depth == -1) {
_interpStack.depth = 1;
} else {
assert(_interpStack.depth >= 0);
_interpStack.depth += 1;
}
}
return _finishToken(TokenKind.LBRACE);
}
Token _finishCloseBrace() {
if (_interpStack != null) {
_interpStack.depth -= 1;
assert(_interpStack.depth >= 0);
}
return _finishToken(TokenKind.RBRACE);
}
Token finishString(int quote) {
if (_maybeEatChar(quote)) {
if (_maybeEatChar(quote)) {
// skip an initial newline
_maybeEatChar(10/*'\n'*/);
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 == 36/*$*/) {
// start of string interp
_interpStack = InterpStack.push(_interpStack, quote, false);
return _makeStringToken(buf, true);
} else if (ch == 0) {
return _errorToken();
} else if (ch == 92/*\*/) {
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 0x0a/*'\n'*/;
case 114/*r*/:
return 0x0d/*'\r'*/;
case 102/*f*/:
return 0x0c/*'\f'*/;
case 98/*b*/:
return 0x08/*'\b'*/;
case 116/*t*/:
return 0x09/*'\t'*/;
case 118/*v*/:
return 0x0b/*'\v'*/;
case 120/*x*/:
hexValue = readHex(2);
break;
case 117/*u*/:
if (_maybeEatChar(123/*{*/)) {
hexValue = readHex();
if (!_maybeEatChar(125/*}*/)) {
return -1;
} else {
break;
}
} 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){
world.fatal('unicode values greater than 2 bytes not implemented yet');
return -1;
} else {
return -1;
}
}
Token finishDot() {
if (isDigit(_peekChar())) {
eatDigits();
return finishNumberExtra(TokenKind.DOUBLE);
} else {
return _finishToken(TokenKind.DOT);
}
}
Token finishIdentifier() {
if (_interpStack != null && _interpStack.depth == -1) {
_interpStack.depth = 0;
while (_index < _text.length) {
if (!isInterpIdentifierPart(_text.charCodeAt(_index++))) {
_index--;
break;
}
}
} else {
while (_index < _text.length) {
if (!isIdentifierPart(_text.charCodeAt(_index++))) {
_index--;
break;
}
}
}
int kind = getIdentifierKind();
if (kind == TokenKind.IDENTIFIER) {
return _finishToken(TokenKind.IDENTIFIER);
} else {
return _finishToken(kind);
}
}
}

View file

@ -0,0 +1,456 @@
// 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.
// 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 final int UNUSED = 0; // Unused place holder...
static final int END_OF_FILE = 1;
static final int LPAREN = 2; // (
static final int RPAREN = 3; // )
static final int LBRACK = 4; // [
static final int RBRACK = 5; // ]
static final int LBRACE = 6; // {
static final int RBRACE = 7; // }
static final int DOT = 8; // .
static final int SEMICOLON = 9; // ;
static final int SPACE = 10; // space character
static final int TAB = 11; // \t
static final int NEWLINE = 12; // \n
static final int RETURN = 13; // \r
static final int COMMA = 14; // ,
// Unique tokens.
static final int LESS_THAN = 15; // <
static final int GREATER_THAN = 16; // >
static final int SLASH = 17; // /
static final int DOLLAR = 18; // $
static final int HASH = 19; // #
static final int MINUS = 20; // -
static final int EQUAL = 21; // =
static final int DOUBLE_QUOTE = 22; // "
static final int SINGLE_QUOTE = 23; // '
// WARNING: END_TOKENS must be 1 greater than the last token above (last
// character in our list). Also add to kindToString function and the
// constructor for TokenKind.
static final int END_TOKENS = 24; // Marker for last token in list
// Synthesized tokens:
static final int END_NO_SCOPE_TAG = 50; // />
static final int START_EXPRESSION = 51; // ${
static final int START_COMMAND = 52; // ${#
static final int END_COMMAND = 53; // ${/
static final int EACH_COMMAND = 53; // ${#each list}
static final int WITH_COMMAND = 54; // ${#with object}
static final int IF_COMMAND = 55; // ${#if (expression)}
static final int ELSE_COMMAND = 56; // ${#else}
/** [TokenKind] representing integer tokens. */
static final int INTEGER = 60; // TODO(terry): must match base
/** [TokenKind] representing hex integer tokens. */
// static final int HEX_INTEGER = 61; // TODO(terry): must match base
/** [TokenKind] representing double tokens. */
static final int DOUBLE = 62; // TODO(terry): must match base
/** [TokenKind] representing whitespace tokens. */
static final int WHITESPACE = 63; // TODO(terry): must match base
/** [TokenKind] representing comment tokens. */
static final int COMMENT = 64; // TODO(terry): must match base
/** [TokenKind] representing error tokens. */
static final int ERROR = 65; // TODO(terry): must match base
/** [TokenKind] representing incomplete string tokens. */
static final int INCOMPLETE_STRING = 66; // TODO(terry): must match base
/** [TokenKind] representing incomplete comment tokens. */
static final int INCOMPLETE_COMMENT = 67; // TODO(terry): must match base
// Synthesized Tokens (no character associated with TOKEN).
// TODO(terry): Possible common names used by both Dart and CSS tokenizers.
static final int ATTR_VALUE = 500;
static final int NUMBER = 502;
static final int HEX_NUMBER = 503;
static final int HTML_COMMENT = 504; // <!--
static final int IDENTIFIER = 511;
static final int STRING = 512;
static final int STRING_PART = 513;
static final int TEMPLATE_KEYWORD = 595; // template keyword
// Elements
/* START_HTML_ELEMENT is first valid element tag name
* END_HTML_ELEMENT is the last valid element tag name
*
*/
static final int START_HTML_ELEMENT = 600; // First valid tag name.
static final int A_ELEMENT = 600;
static final int ABBR_ELEMENT = 601;
static final int ACRONYM_ELEMENT = 602;
static final int ADDRESS_ELEMENT = 603;
static final int APPLET_ELEMENT = 604;
static final int AREA_ELEMENT = 605;
static final int B_ELEMENT = 606;
static final int BASE_ELEMENT = 607;
static final int BASEFONT_ELEMENT = 608;
static final int BDO_ELEMENT = 609;
static final int BIG_ELEMENT = 610;
static final int BLOCKQUOTE_ELEMENT = 611;
static final int BODY_ELEMENT = 612;
static final int BR_ELEMENT = 613;
static final int BUTTON_ELEMENT = 614;
static final int CAPTION_ELEMENT = 615;
static final int CENTER_ELEMENT = 616;
static final int CITE_ELEMENT = 617;
static final int CODE_ELEMENT = 618;
static final int COL_ELEMENT = 619;
static final int COLGROUP_ELEMENT = 620;
static final int DD_ELEMENT = 621;
static final int DEL_ELEMENT = 622;
static final int DFN_ELEMENT = 623;
static final int DIR_ELEMENT = 624;
static final int DIV_ELEMENT = 625;
static final int DL_ELEMENT = 626;
static final int DT_ELEMENT = 627;
static final int EM_ELEMENT = 628;
static final int FIELDSET_ELEMENT = 629;
static final int FONT_ELEMENT = 630;
static final int FORM_ELEMENT = 631;
static final int FRAME_ELEMENT = 632;
static final int FRAMESET_ELEMENT = 633;
static final int H1_ELEMENT = 634;
static final int H2_ELEMENT = 635;
static final int H3_ELEMENT = 636;
static final int H4_ELEMENT = 637;
static final int H5_ELEMENT = 638;
static final int H6_ELEMENT = 639;
static final int HEAD_ELEMENT = 640;
static final int HR_ELEMENT = 641;
static final int HTML_ELEMENT = 642;
static final int I_ELEMENT = 643;
static final int IFRAME_ELEMENT = 644;
static final int IMG_ELEMENT = 645;
static final int INPUT_ELEMENT = 646;
static final int INS_ELEMENT = 647;
static final int ISINDEX_ELEMENT = 648;
static final int KBD_ELEMENT = 649;
static final int LABEL_ELEMENT = 650;
static final int LEGEND_ELEMENT = 651;
static final int LI_ELEMENT = 652;
static final int LINK_ELEMENT = 653;
static final int MAP_ELEMENT = 654;
static final int MENU_ELEMENT = 645;
static final int META_ELEMENT = 656;
static final int NOFRAMES_ELEMENT = 657;
static final int NOSCRIPT_ELEMENT = 658;
static final int OBJECT_ELEMENT = 659;
static final int OL_ELEMENT = 660;
static final int OPTGROUP_ELEMENT = 661;
static final int OPTION_ELEMENT = 662;
static final int P_ELEMENT = 663;
static final int PARAM_ELEMENT = 664;
static final int PRE_ELEMENT = 665;
static final int Q_ELEMENT = 666;
static final int S_ELEMENT = 667;
static final int SAMP_ELEMENT = 668;
static final int SCRIPT_ELEMENT = 669;
static final int SELECT_ELEMENT = 670;
static final int SMALL_ELEMENT = 671;
static final int SPAN_ELEMENT = 672;
static final int STRIKE_ELEMENT = 673;
static final int STRONG_ELEMENT = 674;
static final int STYLE_ELEMENT = 675;
static final int SUB_ELEMENT = 676;
static final int SUP_ELEMENT = 677;
static final int TABLE_ELEMENT = 678;
static final int TBODY_ELEMENT = 679;
static final int TD_ELEMENT = 680;
static final int TEXTAREA_ELEMENT = 681;
static final int TFOOT_ELEMENT = 682;
static final int TH_ELEMENT = 683;
static final int THEAD_ELEMENT = 684;
static final int TITLE_ELEMENT = 685;
static final int TR_ELEMENT = 686;
static final int TT_ELEMENT = 687;
static final int U_ELEMENT = 688;
static final int UL_ELEMENT = 689;
static final int VAR_ELEMENT = 690;
static final int END_HTML_ELEMENT = VAR_ELEMENT; // Last valid tag name.
static bool validTagName(int tokId) {
return tokId >= TokenKind.START_HTML_ELEMENT &&
tokId <= TokenKind.END_HTML_ELEMENT;
}
static final List<Map<int, String>> _KEYWORDS = const [
const {'type': TokenKind.TEMPLATE_KEYWORD, 'value' : 'template'},
];
static final List<Map<int, String>> _ELEMENTS = const [
const {'type': TokenKind.A_ELEMENT, 'value' : 'a'},
const {'type': TokenKind.ABBR_ELEMENT, 'value' : 'abbr'},
const {'type': TokenKind.ACRONYM_ELEMENT, 'value' : 'acronym'},
const {'type': TokenKind.ADDRESS_ELEMENT, 'value' : 'address'},
const {'type': TokenKind.APPLET_ELEMENT, 'value' : 'applet'},
const {'type': TokenKind.AREA_ELEMENT, 'value' : 'area'},
const {'type': TokenKind.B_ELEMENT, 'value' : 'b'},
const {'type': TokenKind.BASE_ELEMENT, 'value' : 'base'},
const {'type': TokenKind.BASEFONT_ELEMENT, 'value' : 'basefont'},
const {'type': TokenKind.BDO_ELEMENT, 'value' : 'bdo'},
const {'type': TokenKind.BIG_ELEMENT, 'value' : 'big'},
const {'type': TokenKind.BLOCKQUOTE_ELEMENT, 'value' : 'blockquote'},
const {'type': TokenKind.BODY_ELEMENT, 'value' : 'body'},
const {'type': TokenKind.BR_ELEMENT, 'value' : 'br'},
const {'type': TokenKind.BUTTON_ELEMENT, 'value' : 'button'},
const {'type': TokenKind.CAPTION_ELEMENT, 'value' : 'caption'},
const {'type': TokenKind.CENTER_ELEMENT, 'value' : 'center'},
const {'type': TokenKind.CITE_ELEMENT, 'value' : 'cite'},
const {'type': TokenKind.CODE_ELEMENT, 'value' : 'code'},
const {'type': TokenKind.COL_ELEMENT, 'value' : 'col'},
const {'type': TokenKind.COLGROUP_ELEMENT, 'value' : 'colgroup'},
const {'type': TokenKind.DD_ELEMENT, 'value' : 'dd'},
const {'type': TokenKind.DEL_ELEMENT, 'value' : 'del'},
const {'type': TokenKind.DFN_ELEMENT, 'value' : 'dfn'},
const {'type': TokenKind.DIR_ELEMENT, 'value' : 'dir'},
const {'type': TokenKind.DIV_ELEMENT, 'value' : 'div'},
const {'type': TokenKind.DL_ELEMENT, 'value' : 'dl'},
const {'type': TokenKind.DT_ELEMENT, 'value' : 'dt'},
const {'type': TokenKind.EM_ELEMENT, 'value' : 'em'},
const {'type': TokenKind.FIELDSET_ELEMENT, 'value' : 'fieldset'},
const {'type': TokenKind.FONT_ELEMENT, 'value' : 'font'},
const {'type': TokenKind.FORM_ELEMENT, 'value' : 'form'},
const {'type': TokenKind.FRAME_ELEMENT, 'value' : 'frame'},
const {'type': TokenKind.FRAMESET_ELEMENT, 'value' : 'frameset'},
const {'type': TokenKind.H1_ELEMENT, 'value' : 'h1'},
const {'type': TokenKind.H2_ELEMENT, 'value' : 'h2'},
const {'type': TokenKind.H3_ELEMENT, 'value' : 'h3'},
const {'type': TokenKind.H4_ELEMENT, 'value' : 'h4'},
const {'type': TokenKind.H5_ELEMENT, 'value' : 'h5'},
const {'type': TokenKind.H6_ELEMENT, 'value' : 'h6'},
const {'type': TokenKind.HEAD_ELEMENT, 'value' : 'head'},
const {'type': TokenKind.HR_ELEMENT, 'value' : 'hr'},
const {'type': TokenKind.HTML_ELEMENT, 'value' : 'html'},
const {'type': TokenKind.I_ELEMENT, 'value' : 'i'},
const {'type': TokenKind.IFRAME_ELEMENT, 'value' : 'iframe'},
const {'type': TokenKind.IMG_ELEMENT, 'value' : 'img'},
const {'type': TokenKind.INPUT_ELEMENT, 'value' : 'input'},
const {'type': TokenKind.INS_ELEMENT, 'value' : 'ins'},
const {'type': TokenKind.ISINDEX_ELEMENT, 'value' : 'isindex'},
const {'type': TokenKind.KBD_ELEMENT, 'value' : 'kbd'},
const {'type': TokenKind.LABEL_ELEMENT, 'value' : 'label'},
const {'type': TokenKind.LEGEND_ELEMENT, 'value' : 'legend'},
const {'type': TokenKind.LI_ELEMENT, 'value' : 'li'},
const {'type': TokenKind.LINK_ELEMENT, 'value' : 'link'},
const {'type': TokenKind.MAP_ELEMENT, 'value' : 'map'},
const {'type': TokenKind.MENU_ELEMENT, 'value' : 'menu'},
const {'type': TokenKind.META_ELEMENT, 'value' : 'meta'},
const {'type': TokenKind.NOFRAMES_ELEMENT, 'value' : 'noframes'},
const {'type': TokenKind.NOSCRIPT_ELEMENT, 'value' : 'noscript'},
const {'type': TokenKind.OBJECT_ELEMENT, 'value' : 'object'},
const {'type': TokenKind.OL_ELEMENT, 'value' : 'ol'},
const {'type': TokenKind.OPTGROUP_ELEMENT, 'value' : 'optgroup'},
const {'type': TokenKind.OPTION_ELEMENT, 'value' : 'option'},
const {'type': TokenKind.P_ELEMENT, 'value' : 'p'},
const {'type': TokenKind.PARAM_ELEMENT, 'value' : 'param'},
const {'type': TokenKind.PRE_ELEMENT, 'value' : 'pre'},
const {'type': TokenKind.Q_ELEMENT, 'value' : 'q'},
const {'type': TokenKind.S_ELEMENT, 'value' : 's'},
const {'type': TokenKind.SAMP_ELEMENT, 'value' : 'samp'},
const {'type': TokenKind.SCRIPT_ELEMENT, 'value' : 'script'},
const {'type': TokenKind.SELECT_ELEMENT, 'value' : 'select'},
const {'type': TokenKind.SMALL_ELEMENT, 'value' : 'small'},
const {'type': TokenKind.SPAN_ELEMENT, 'value' : 'span'},
const {'type': TokenKind.STRIKE_ELEMENT, 'value' : 'strike'},
const {'type': TokenKind.STRONG_ELEMENT, 'value' : 'strong'},
const {'type': TokenKind.STYLE_ELEMENT, 'value' : 'style'},
const {'type': TokenKind.SUB_ELEMENT, 'value' : 'sub'},
const {'type': TokenKind.SUP_ELEMENT, 'value' : 'sup'},
const {'type': TokenKind.TABLE_ELEMENT, 'value' : 'table'},
const {'type': TokenKind.TBODY_ELEMENT, 'value' : 'tbody'},
const {'type': TokenKind.TD_ELEMENT, 'value' : 'td'},
const {'type': TokenKind.TEXTAREA_ELEMENT, 'value' : 'textarea'},
const {'type': TokenKind.TFOOT_ELEMENT, 'value' : 'tfoot'},
const {'type': TokenKind.TH_ELEMENT, 'value' : 'th'},
const {'type': TokenKind.THEAD_ELEMENT, 'value' : 'thead'},
const {'type': TokenKind.TITLE_ELEMENT, 'value' : 'title'},
const {'type': TokenKind.TR_ELEMENT, 'value' : 'tr'},
const {'type': TokenKind.TT_ELEMENT, 'value' : 'tt'},
const {'type': TokenKind.U_ELEMENT, 'value' : 'u'},
const {'type': TokenKind.UL_ELEMENT, 'value' : 'ul'},
const {'type': TokenKind.VAR_ELEMENT, 'value' : 'var'},
];
// Some more constants:
static final int ASCII_UPPER_A = 65; // ASCII value for uppercase A
static final int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z
List<int> tokens;
/*
* 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 identIdx = 0; identIdx < ident.length; identIdx++) {
int identChar = ident.charCodeAt(identIdx);
int char = text.charCodeAt(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.
}
/* Map _ELEMENTS type to the real name. */
static String elementsToName(int kind) {
for (final entry in TokenKind._ELEMENTS) {
if (kind == entry['type']) {
return entry['value'];
}
}
}
/*
* Return the token that matches the element ident found.
*/
static int matchElements(String text, int offset, int length) {
return matchList(_ELEMENTS, 'type', text, offset, length);
}
static String tagNameFromTokenId(int tagTokenId) {
if (tagTokenId >= TokenKind.START_HTML_ELEMENT &&
tagTokenId <= TokenKind.END_HTML_ELEMENT) {
for (final tag in TokenKind._ELEMENTS) {
if (tag['type'] == tagTokenId) {
return tag['value'];
}
}
}
}
static int matchKeywords(String text, int offset, int length) {
return matchList(_KEYWORDS, 'type', text, offset, length);
}
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.SPACE: return " ";
case TokenKind.TAB: return "\t";
case TokenKind.NEWLINE: return "\n";
case TokenKind.RETURN: return "\r";
case TokenKind.COMMA: return ",";
case TokenKind.LESS_THAN: return "<";
case TokenKind.GREATER_THAN: return ">";
case TokenKind.SLASH: return "/";
case TokenKind.DOLLAR: return "\$";
case TokenKind.HASH: return "#";
case TokenKind.MINUS: return '-';
case TokenKind.EQUAL: return '=';
case TokenKind.DOUBLE_QUOTE: return '"';
case TokenKind.SINGLE_QUOTE: return "'";
case TokenKind.END_NO_SCOPE_TAG: return '/>';
case TokenKind.START_EXPRESSION: return '\${';
case TokenKind.START_COMMAND: return '\${#';
case TokenKind.END_COMMAND: return '\${/';
case TokenKind.EACH_COMMAND: return '\${#each list}';
case TokenKind.WITH_COMMAND: return '\${with object}';
case TokenKind.IF_COMMAND: return '\${#if (expression)}';
case TokenKind.ELSE_COMMAND: return '\${#end}';
case TokenKind.INTEGER: return 'integer';
case TokenKind.DOUBLE: return 'double';
case TokenKind.WHITESPACE: return 'whitespace';
case TokenKind.COMMENT: return 'comment';
case TokenKind.ERROR: return 'error';
case TokenKind.INCOMPLETE_STRING : return 'incomplete string';
case TokenKind.INCOMPLETE_COMMENT: return 'incomplete comment';
case TokenKind.ATTR_VALUE: return 'attribute value';
case TokenKind.NUMBER: return 'number';
case TokenKind.HEX_NUMBER: return 'hex number';
case TokenKind.HTML_COMMENT: return 'HTML comment <!-- -->';
case TokenKind.IDENTIFIER: return 'identifier';
case TokenKind.STRING: return 'string';
case TokenKind.STRING_PART: return 'string part';
case TokenKind.TEMPLATE_KEYWORD: return 'template';
default:
throw "Unknown TOKEN";
}
}
TokenKind() {
tokens = [];
// All tokens must be in TokenKind order.
tokens.add(-1); // TokenKind.UNUSED
tokens.add(0); // TokenKind.END_OF_FILE match base
tokens.add(TokenKind.kindToString(TokenKind.LPAREN).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.RPAREN).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.LBRACK).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.RBRACK).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.LBRACE).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.RBRACE).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.DOT).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.SEMICOLON).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.SPACE).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.TAB).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.NEWLINE).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.RETURN).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.COMMA).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.LESS_THAN).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.GREATER_THAN).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.SLASH).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.DOLLAR).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.HASH).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.MINUS).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.EQUAL).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.DOUBLE_QUOTE).charCodeAt(0));
tokens.add(TokenKind.kindToString(TokenKind.SINGLE_QUOTE).charCodeAt(0));
assert(tokens.length == TokenKind.END_TOKENS);
}
static bool isIdentifier(int kind) {
return kind == IDENTIFIER;
}
}
class NoElementMatchException implements Exception {
String _tagName;
NoElementMatchException(this._tagName);
String get name() => _tagName;
}

109
utils/template/tool.dart Normal file
View file

@ -0,0 +1,109 @@
// 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.
#library('templatetool');
#import('dart:io');
#import('template.dart');
#import('../lib/file_system.dart');
#import('../lib/file_system_vm.dart');
FileSystem files;
/** Invokes [callback] and returns how long it took to execute in ms. */
num time(callback()) {
final watch = new Stopwatch();
watch.start();
callback();
watch.stop();
return watch.elapsedInMs();
}
String GREEN_COLOR = '\u001b[32m';
String NO_COLOR = '\u001b[0m';
printStats(String phase, num elapsed, [String filename = '']) {
print('${phase} ${GREEN_COLOR}${filename}${NO_COLOR} in ${elapsed} msec.');
}
/**
* Run from the `utils/css` directory.
*/
void main() {
// argument 0 - sourcefile full path
// argument 1 - outputfile full path
var optionArgs = new Options().arguments;
String sourceFullFn = optionArgs[0];
String outputFullFn = optionArgs[1];
String sourcePath;
String sourceFilename;
int idxBeforeFilename = sourceFullFn.lastIndexOf('/');
if (idxBeforeFilename >= 0) {
sourcePath = sourceFullFn.substring(0, idxBeforeFilename + 1);
sourceFilename = sourceFullFn.substring(idxBeforeFilename + 1);
}
String outPath;
String outFilename;
idxBeforeFilename = outputFullFn.lastIndexOf('/');
if (idxBeforeFilename >= 0) {
outPath = outputFullFn.substring(0, idxBeforeFilename + 1);
outFilename = outputFullFn.substring(idxBeforeFilename + 1);
}
if (sourceFilename.length == 0 || outFilename.length == 0) {
print("Unknown command:\r");
print(" Format: sourcefile outputfile [--options]");
print(" outputfile - template file filename.tmpl");
print(" outputfile - generated dart source file filename.dart");
return;
}
// files = new NodeFileSystem();
files = new VMFileSystem();
// TODO(terry): Cleanup options handling need common options between template
// and CSS parsers also cleanup above cruft.
// TODO(terry): Pass on switches.
var args = [];
parseOptions(args, files);
initHtmlWorld(false);
if (!files.fileExists(sourceFullFn)) {
// Display colored error message if file is missing.
print(world.fatal("CSS source file missing - ${sourceFullFn}"));
} else {
String source = files.readAll(sourceFullFn);
List<Template> templates;
final parsedElapsed = time(() {
templates = templateParseAndValidate(source);
});
StringBuffer code = new StringBuffer();
num codegenElapsed;
if (world.errors == 0) {
// Generate the Dart class(es) for all template(s).
codegenElapsed = time(() {
code.add(Codegen.generate(templates, outFilename));
});
}
printStats("Parsed", parsedElapsed, sourceFullFn);
printStats("Codegen", codegenElapsed, sourceFullFn);
final outputElapsed = time(() {
files.writeString(outputFullFn, code.toString());
});
printStats("Wrote file", codegenElapsed, outputFullFn);
}
}

108
utils/template/tree.dart Normal file
View file

@ -0,0 +1,108 @@
// 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.
/**
* The base type for all nodes in a dart abstract syntax tree.
*/
class ASTNode {
/** The source code this [ASTNode] represents. */
SourceSpan span;
ASTNode(this.span) {}
/** Classic double-dispatch visitor for implementing passes. */
abstract visit(TreeVisitor visitor);
/** A multiline string showing the node and its children. */
String toDebugString() {
var to = new TreeOutput();
var tp = new TreePrinter(to);
this.visit(tp);
return to.buf.toString();
}
}
// TODO(jimhug): Clean-up and factor out of core.
/** Simple class to provide a textual dump of trees for debugging. */
class TreeOutput {
int depth;
StringBuffer buf;
var printer;
static void dump(ASTNode node) {
var o = new TreeOutput();
node.visit(new TreePrinter(o));
print(o.buf);
}
TreeOutput(): this.depth = 0, this.buf = new StringBuffer() {
}
void write(String s) {
for (int i=0; i < depth; i++) {
buf.add(' ');
}
buf.add(s);
}
void writeln(String s) {
write(s);
buf.add('\n');
}
void heading(String name, span) {
write(name);
buf.add(' (${span.locationText})');
buf.add('\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, ASTNode 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.add('null');
buf.add('\n');
} else {
for (var item in list) {
buf.add(item.toString());
buf.add(', ');
}
buf.add('\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(']');
}
}
}

572
utils/template/uitest.dart Normal file
View file

@ -0,0 +1,572 @@
// 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.
#import('dart:html');
#import('template.dart');
#import('../lib/file_system_memory.dart');
String currSampleTemplate;
void changeTemplate() {
final Document doc = window.document;
final SelectElement samples = doc.query('#templateSamples');
final TextAreaElement template = doc.query('#template');
template.value = sample(samples.value);
}
String sample(String sampleName) {
final String each = '\${#each';
final String endEach = '\${/each}';
final String with = '\${#with';
final String endWith = '\${/with}';
final String simpleTemplate = '''
template NameEntry(String name, int age) {
<div var=topDiv attr="test" attr1=test1 attr2='test2' attr3=test3>
<span var=spanElem>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
}
''';
final String simpleTemplate2 = '''
template NameEntry(String name, int age) {
<div var=topDiv attr="test" attr1=test1 attr2='test2' attr3=test3>
<h1>
<h2>
<h3>
<span var=spanElem>\${name}</span>
<span>-</span>
<span>\${age}</span>
</h3>
</h2>
</h1>
</div>
}
''';
final String simpleTemplateCSS = '''
template NameEntry(String name, int age) {
css {
.foo {
left: 10px;
}
}
<div var=topDiv attr="test" attr1=test1 attr2='test2' attr3=test3>
<span var=spanElem>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
}
''';
final String eachTemplate = '''
template Applications(var products) {
<div>
${each} products}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${users}</span>
</div>
${endEach}
</div>
}
''';
final String withTemplate = '''
template Product(Person person) {
<div>
${with} person}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
${endWith}
</div>
}
''';
final String withTemplate2 = '''
template Product(Person person) {
<div>
<span var=a1>
<h1>
${with} person}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
${endWith}
</h1>
</span>
</div>
}
''';
final String complexTemplate = '''
template ProductsForPerson(Person person, var products) {
<div>
${with} person}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
${each} products}
<div>
<span>product=\${name},users=\${users}</span>
</div>
${endEach}
${endWith}
</div>
}
''';
final String complexTemplate2 = '''
template ProductsForPerson(Person person, var products) {
<div>
${with} person}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
<div>
${each} products}
<span>product=\${name},users=\${users}</span>
${endEach}
</div>
${endWith}
</div>
}
''';
final String complexTemplate3 = '''
template ProductsForPerson(Person person, var products) {
css {
.sales-item {
font-family: arial;
background-color: lightgray;
margin-left: 10px;
border-bottom: 1px solid white;
}
.ytd-sales {
position: absolute;
left: 100px;
}
}
<div>
${with} person}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${age}</span>
</div>
<div>
${each} products}
<div>product=\${name},users=\${users}</div>
${each} products.sales}
<div class="sales-item">
<span>\${country}</span>
<span class="ytd-sales">\\\$\${yearly}</span>
</div>
${endEach}
${endEach}
</div>
${endWith}
</div>
}
template NameEntry(String name, int age) {
css {
.name-item {
font-size: 18pt;
font-weight: bold;
}
}
<div var=topDiv class="name-item" attr="test" attr1=test1 attr2='test2' attr3=test3>
<span var=spanElem>\${name}</span>
<span> - </span>
<span>\${age}</span>
</div>
}
''';
// Test #each in a #each where the nested #each is a top-level child of the
// outer #each.
final String complexTemplate4 = '''
template DivisionSales(var divisions) {
<div>
\${#each divisions}
<div>
<span>\${name}</span>
<span>-</span>
<span>\${id}</span>
</div>
<div>
\${#each divisions.products}
<div>
<span var=productItem>&#9654;</span>
<span>Product</span>
<span>\${name}</span>
<span>\${users}&nbsp;users</span>
</div>
\${#each products.sales}
<div>
<span>\${country}</span>
<span>\\\$\${yearly}</span>
</div>
\${/each}
\${/each}
</div>
\${/each}
</div>
}
''';
final String realWorldList = '''
template DivisionSales(var divisions) {
css {
.division-item {
background-color: #bbb;
border-top: 2px solid white;
line-height: 20pt;
padding-left: 5px;
}
.product-item {
background-color: lightgray;
margin-left: 10px;
border-top: 2px solid white;
line-height: 20pt;
}
.product-title {
position: absolute;
left: 45px;
}
.product-name {
font-weight: bold;
position: absolute;
left: 100px;
}
.product-users {
position: absolute;
left: 150px;
font-style: italic;
color: gray;
width: 110px;
}
.expand-collapse {
margin-left: 5px;
margin-right: 5px;
vertical-align: top;
cursor: pointer;
}
.expand {
font-size: 9pt;
}
.collapse {
font-size: 8pt;
}
.show-sales {
display: inherit;
}
.hide-sales {
display: none;
}
.sales-item {
font-family: arial;
background-color: lightgray;
margin-left: 10px;
border-top: 1px solid white;
line-height: 18pt;
padding-left: 5px;
}
.ytd-sales {
position: absolute;
left: 100px;
}
}
<div>
\${#each divisions}
<div class="division-item">
<span>\${name}</span>
<span>-</span>
<span>\${id}</span>
</div>
<div>
\${#each divisions.products}
<div class="product-item">
<span var=productZippy class="expand-collapse expand">&#9660;</span>
<span class='product-title'>Product</span>
<span class="product-name">\${name}</span>
<span class="product-users" align=right>\${users}&nbsp;users</span>
<div class="show-sales">
\${#each products.sales}
<div class="sales-item">
<span>\${country}</span>
<span class="ytd-sales">\\\$\${yearly}</span>
</div>
\${/each}
</div>
</div>
\${/each}
</div>
\${/each}
</div>
}
template Header(String company, Date date) {
css {
.header {
background-color: slateGray;
font-family: arial;
color: lightgray;
font-weight: bold;
padding-top: 20px;
}
}
<div class='header' align=center>
<h2>\${company}</h2>
<div align=right>\${date}</div>
</div>
}
''';
switch (sampleName) {
case "simple":
return simpleTemplate;
case "simple2":
return simpleTemplate2;
case "simpleCSS":
return simpleTemplateCSS;
case "with":
return withTemplate;
case "with2":
return withTemplate2;
case "list":
return eachTemplate;
case "complex":
return complexTemplate;
case "complex2":
return complexTemplate2;
case "complex3":
return complexTemplate3;
case "complex4":
return complexTemplate4;
case "realWorldList":
return realWorldList;
default:
print("ERROR: Unknown sample template");
}
}
void runTemplate([bool debug = false, bool parseOnly = false]) {
final Document doc = window.document;
final TextAreaElement dartClass = doc.query("#dart");
final TextAreaElement template = doc.query('#template');
final TableCellElement validity = doc.query('#validity');
final TableCellElement result = doc.query('#result');
bool templateValid = true;
StringBuffer dumpTree = new StringBuffer();
StringBuffer code = new StringBuffer();
String htmlTemplate = template.value;
if (debug) {
try {
List<Template> templates = templateParseAndValidate(htmlTemplate);
for (var tmpl in templates) {
dumpTree.add(tmpl.toDebugString());
}
// Generate the Dart class(es) for all template(s).
// Pass in filename of 'foo' for testing in UITest.
code.add(Codegen.generate(templates, 'foo'));
} catch (final htmlException) {
// TODO(terry): TBD
print("ERROR unhandled EXCEPTION");
}
}
/*
if (!debug) {
try {
cssParseAndValidate(cssExpr, cssWorld);
} catch (final cssException) {
templateValid = false;
dumpTree = cssException.toString();
}
} else if (parseOnly) {
try {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, cssExpr));
Stylesheet stylesheet = parser.parse();
StringBuffer stylesheetTree = new StringBuffer();
String prettyStylesheet = stylesheet.toString();
stylesheetTree.add("${prettyStylesheet}\n");
stylesheetTree.add("\n============>Tree Dump<============\n");
stylesheetTree.add(stylesheet.toDebugString());
dumpTree = stylesheetTree.toString();
} catch (final cssParseException) {
templateValid = false;
dumpTree = cssParseException.toString();
}
} else {
try {
dumpTree = cssParseAndValidateDebug(cssExpr, cssWorld);
} catch (final cssException) {
templateValid = false;
dumpTree = cssException.toString();
}
}
*/
final bgcolor = templateValid ? "white" : "red";
final color = templateValid ? "black" : "white";
final valid = templateValid ? "VALID" : "NOT VALID";
String resultStyle = "resize: none; margin: 0; height: 100%; width: 100%;"
"padding: 5px 7px;";
result.innerHTML = '''
<textarea style="${resultStyle}">${dumpTree.toString()}</textarea>
''';
dartClass.value = code.toString();
}
void main() {
final element = new Element.tag('div');
element.innerHTML = '''
<table style="width: 100%; height: 100%;">
<tbody>
<tr>
<td style="vertical-align: top; width: 50%; padding-right: 7px;">
<table style="height: 100%; width: 100%;" cellspacing=0 cellpadding=0 border=0>
<tbody>
<tr style="vertical-align: top; height: 1em;">
<td>
<span style="font-weight:bold;">Generated Dart</span>
</td>
</tr>
<tr>
<td>
<textarea id="dart" style="resize: none; width: 100%; height: 100%; padding: 5px 7px;"></textarea>
</td>
</tr>
</tbody>
</table>
</td>
<td>
<table style="width: 100%; height: 100%;" cellspacing=0 cellpadding=0 border=0>
<tbody>
<tr style="vertical-align: top; height: 50%;">
<td>
<table style="width: 100%; height: 100%;" cellspacing=0 cellpadding=0 border=0>
<tbody>
<tr>
<td>
<span style="font-weight:bold;">HTML Template</span>
</td>
</tr>
<tr style="height: 100%;">
<td>
<textarea id="template" style="resize: none; width: 100%; height: 100%; padding: 5px 7px;">${sample("simple")}</textarea>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr style="vertical-align: top; height: 50px;">
<td>
<table>
<tbody>
<tr>
<td>
<button id=generate>Generate</button>
</td>
<td align="right">
<select id=templateSamples>
<option value="simple">Simple Template</option>
<option value="simple2">Simple Template #2</option>
<option value="simpleCSS">Simple Template w/ CSS</option>
<option value="with">With Template</option>
<option value="with2">With Template #2</option>
<option value="list">List Template</option>
<option value="complex">Complex Template</option>
<option value="complex2">Complex Template #2</option>
<option value="complex3">Complex Template #3 w/ CSS</option>
<option value="complex4">Complex Template #4</option>
<option value="realWorldList">Real world</option>
</select>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr style="vertical-align: top;">
<td>
<table style="width: 100%; height: 100%;" border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr style="vertical-align: top; height: 1em;">
<td>
<span style="font-weight:bold;">Parse Tree</span>
</td>
</tr>
<tr style="vertical-align: top; height: 1em;">
<td id="validity">
</td>
</tr>
<tr>
<td id="result">
<textarea style="resize: none; width: 100%; height: 100%; border: black solid 1px; padding: 5px 7px;"></textarea>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
''';
document.body.style.setProperty("background-color", "lightgray");
document.body.elements.add(element);
ButtonElement genElem = window.document.query('#generate');
genElem.on.click.add((MouseEvent e) {
runTemplate(true, true);
});
SelectElement cannedTemplates = window.document.query('#templateSamples');
cannedTemplates.on.change.add((e) {
changeTemplate();
});
parseOptions([], null);
initHtmlWorld(false);
// Don't display any colors in the UI.
options.useColors = false;
// Replace error handler bring up alert for any problems.
world.printHandler = (String msg) {
window.alert(msg);
};
}

52
utils/template/utils.dart Normal file
View file

@ -0,0 +1,52 @@
// 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.
// Collection<T> supports most of the ES 5 Array methods, but it's missing
// map and reduce.
// TODO(jmesserly): we might want a version of this that return an iterable,
// however JS, Python and Ruby versions are all eager.
List map(Iterable source, mapper(source)) {
List result = new List();
if (source is List) {
List list = source; // TODO: shouldn't need this
result.length = list.length;
for (int i = 0; i < list.length; i++) {
result[i] = mapper(list[i]);
}
} else {
for (final item in source) {
result.add(mapper(item));
}
}
return result;
}
reduce(Iterable source, callback, [initialValue]) {
final i = source.iterator();
var current = initialValue;
if (current == null && i.hasNext()) {
current = i.next();
}
while (i.hasNext()) {
current = callback(current, i.next());
}
return current;
}
List zip(Iterable left, Iterable right, mapper(left, right)) {
List result = new List();
var x = left.iterator();
var y = right.iterator();
while (x.hasNext() && y.hasNext()) {
result.add(mapper(x.next(), y.next()));
}
if (x.hasNext() || y.hasNext()) {
throw new IllegalArgumentException();
}
return result;
}

169
utils/template/world.dart Normal file
View file

@ -0,0 +1,169 @@
// 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.
/** The one true [World]. */
World world;
typedef void MessageHandler(String prefix, String message, SourceSpan span);
typedef void PrintHandler(String message);
/**
* Should be called exactly once to setup singleton world.
* Can use world.reset() to reinitialize.
*/
void initializeWorld(var files) {
assert(world == null);
world = new World(files);
world.init();
}
/** Can be thrown on any compiler error and includes source location. */
class CompilerException implements Exception {
final String _message;
final SourceSpan _location;
CompilerException(this._message, this._location);
String toString() {
if (_location != null) {
return 'CompilerException: ${_location.toMessageString(_message)}';
} else {
return 'CompilerException: $_message';
}
}
}
/** Represents a Dart template "world". */
class World {
String template;
var files;
int errors = 0, warnings = 0;
bool seenFatal = false;
MessageHandler messageHandler;
PrintHandler printHandler;
World(this.files);
void reset() {
errors = warnings = 0;
seenFatal = false;
init();
}
init() {
}
// ********************** Message support ***********************
void _message(String color, String prefix, String message,
SourceSpan span, SourceSpan span1, SourceSpan span2, bool throwing) {
if (messageHandler != null) {
// TODO(jimhug): Multiple spans cleaner...
messageHandler(prefix, message, span);
if (span1 != null) {
messageHandler(prefix, message, span1);
}
if (span2 != null) {
messageHandler(prefix, message, span2);
}
} else {
final messageWithPrefix = options.useColors
? (color + prefix + _NO_COLOR + message) : (prefix + message);
var text = messageWithPrefix;
if (span != null) {
text = span.toMessageString(messageWithPrefix);
}
String span1Text = span1 != null ?
span1.toMessageString(messageWithPrefix) : "";
String span2Text = span2 != null ?
span2.toMessageString(messageWithPrefix) : "";
if (printHandler == null) {
print(text);
if (span1 != null) {
print(span1Text);
}
if (span2 != null) {
print(span2Text);
}
} else {
printHandler("${text}\r${span1Text}\r${span2Text}");
}
}
if (throwing) {
throw new CompilerException(prefix + message, span);
}
}
/** [message] is considered a static compile-time error by the Dart lang. */
void error(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
errors++;
_message(_RED_COLOR, 'error: ', message,
span, span1, span2, options.throwOnErrors);
}
/** [message] is considered a type warning by the Dart lang. */
void warning(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
if (options.warningsAsErrors) {
error(message, span, span1, span2);
return;
}
warnings++;
if (options.showWarnings) {
_message(_MAGENTA_COLOR, 'warning: ', message,
span, span1, span2, options.throwOnWarnings);
}
}
/** [message] at [location] is so bad we can't generate runnable code. */
void fatal(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
errors++;
seenFatal = true;
_message(_RED_COLOR, 'fatal: ', message,
span, span1, span2, options.throwOnFatal || options.throwOnErrors);
}
/** [message] at [location] is about a bug in the compiler. */
void internalError(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
_message(_NO_COLOR,
'We are sorry, but...', message, span, span1, span2, true);
}
/**
* [message] at [location] will tell the user about what the compiler
* is doing.
*/
void info(String message,
[SourceSpan span, SourceSpan span1, SourceSpan span2]) {
if (options.showInfo) {
_message(_GREEN_COLOR, 'info: ', message, span, span1, span2, false);
}
}
bool get hasErrors() => errors > 0;
withTiming(String name, f()) {
final sw = new Stopwatch();
sw.start();
var result = f();
sw.stop();
info('$name in ${sw.elapsedInMs()}msec');
return result;
}
}
String _GREEN_COLOR = '\u001b[32m';
String _RED_COLOR = '\u001b[31m';
String _MAGENTA_COLOR = '\u001b[35m';
String _NO_COLOR = '\u001b[0m';

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
#import("../../../css/css.dart");
#import('../../../../frog/lang.dart', prefix:'lang');
class DeclarationTest {
@ -46,7 +45,7 @@ class DeclarationTest {
"}\n";
Parser parser =
new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE, input));
new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE, input));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -80,7 +79,7 @@ class DeclarationTest {
"}\n";
Parser parser =
new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE, input));
new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE, input));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -111,7 +110,7 @@ class DeclarationTest {
"}\n";
Parser parser =
new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE, input));
new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE, input));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -143,7 +142,7 @@ class DeclarationTest {
" }\n" +
"}\n";
Parser parser =
new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE, input));
new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE, input));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -183,7 +182,7 @@ class DeclarationTest {
"}\n";
Parser parser =
new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE, input));
new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE, input));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -242,7 +241,7 @@ class DeclarationTest {
"}\n";
Parser parser =
new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE, scss));
new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE, scss));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
#import("../../../css/css.dart");
#import('../../../../frog/lang.dart', prefix:'lang');
class ExpressionTest {
@ -23,8 +22,8 @@ class ExpressionTest {
}
static void testClass() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, ".foobar {}"));
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, ".foobar {}"));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -45,7 +44,7 @@ class ExpressionTest {
Expect.equals(simpSelector.name, "foobar");
}
parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
".foobar .bar .no-story {}"));
stylesheet = parser.parse();
@ -85,8 +84,8 @@ class ExpressionTest {
}
static void testId() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, "#elemId {}"));
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, "#elemId {}"));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -109,8 +108,8 @@ class ExpressionTest {
}
static void testElement() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, "div {}"));
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, "div {}"));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
Expect.equals(stylesheet.topLevels.length, 1);
@ -130,7 +129,7 @@ class ExpressionTest {
Expect.equals(simpSelector.name, "div");
}
parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
"div div span {}"));
stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -168,8 +167,8 @@ class ExpressionTest {
}
static void testNamespace() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, "ns1|div {}"));
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, "ns1|div {}"));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
Expect.equals(stylesheet.topLevels.length, 1);
@ -194,7 +193,7 @@ class ExpressionTest {
Expect.equals(elementSelector.name, "div");
}
parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
"ns1|div div ns2|span .foobar {}"));
stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -246,8 +245,8 @@ class ExpressionTest {
}
static void testSelectorGroups() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE,
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE,
"div, .foobar ,#elemId, .xyzzy .test, ns1|div div #elemId .foobar {}"));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -328,8 +327,8 @@ class ExpressionTest {
}
static void testCombinator() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE,
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE,
".foobar > .bar + .no-story ~ myNs|div #elemId {}"));
Stylesheet stylesheet = parser.parse();
@ -381,8 +380,8 @@ class ExpressionTest {
}
static void testWildcard() {
Parser parser = new Parser(new lang.SourceFile(
lang.SourceFile.IN_MEMORY_FILE, "* {}"));
Parser parser = new Parser(new SourceFile(
SourceFile.IN_MEMORY_FILE, "* {}"));
Stylesheet stylesheet = parser.parse();
Expect.isNotNull(stylesheet);
@ -404,7 +403,7 @@ class ExpressionTest {
Expect.equals(simpSelector.name, "*");
}
parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
"*.foobar {}"));
stylesheet = parser.parse();
@ -438,7 +437,7 @@ class ExpressionTest {
idx++;
}
parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
"myNs|*.foobar {}"));
stylesheet = parser.parse();
@ -475,7 +474,7 @@ class ExpressionTest {
idx++;
}
parser = new Parser(new lang.SourceFile(lang.SourceFile.IN_MEMORY_FILE,
parser = new Parser(new SourceFile(SourceFile.IN_MEMORY_FILE,
"*|*.foobar {}"));
stylesheet = parser.parse();

View file

@ -3,14 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
#import("../../../css/css.dart");
#import('../../../../frog/lang.dart', prefix:'lang');
class SelectorLiteralTest {
static final String ERROR = 'CompilerException: <buffer>:';
static testMain() {
initCssWorld();
lang.options.useColors = false;
options.useColors = false;
testSimpleClassSelectorSuccesses();
testSimpleClassSelectorFailures();
@ -90,7 +89,7 @@ class SelectorLiteralTest {
cssParseAndValidate('${css}', cssWorld);
Expect.fail("${css} should not succeed.");
} catch (final e) {
Expect.equals("${ERROR}1:11: fatal: expected }, but found double(.1)\n" +
Expect.equals("${ERROR}1:11: fatal: parsing error expected }\n" +
"${css}\n ^^", e.toString());
}

View file

@ -0,0 +1,58 @@
Simple Tests:
test_simple.dart (main application)
Works with templates:
name_entry.tmpl - simple template expression with attributes
name_entry2.tmpl - simple template expression inside nested tags
name_entry_css.tmpl - simple template expression with CSS
With Tests:
test_with.dart (main application)
Works with templates:
productview.tmpl - simplest with template
productview2.tmpl - with tested with var
List Tests:
test_list.dart (main application)
Works with templates:
applications.tmpl - simple list
Complex Tests:
test_complex.dart (main application)
Works with templates:
top_searches.tmpl - #each inside of a #with
top_searches_css.tmpl - #each inside of a #with with CSS
Complex #2 Tests:
test_complex2.dart (main application)
Works with templates:
top_searches2.tmpl - #each inside of a #with w/ CSS and data model
Real World Application - Lists w/ events
real_app.dart (main application)
Works with templates:
realviews.tmpl - more complex app with event hookup (using var)
To build and run the above tests with frog in the browser. Each .tmpl maps to
a test name:
simple1 => name_entry.tmpl
simple2 => name_entry2.tmpl
simple3 => name_entry_css.tmpl
with1 => productview.tmpl
with2 => productview2.tmpl
list => applications.tmpl
complex => top_searches.tmpl
complexcss => top_searches_css.tmpl
complex2 => top_searches2.tmpl
real => realviews.tmpl
e.g. to run the Real World Application do:
cd utils/test/templates
./run real

View file

@ -0,0 +1,12 @@
template Applications(var products) {
<div>
${#each products}
<div>
<span>${name}</span>
<span>-</span>
<span>${users}</span>
</div>
${/each}
</div>
}

View file

@ -0,0 +1,122 @@
// Data model for complex tests.
class Person {
String name;
int age;
List<Search> searches;
Person(this.name, this.age, this.searches);
}
class Search {
String query;
int rank;
int total;
List<Metric> metrics;
Search(this.query, this.rank, this.total, this.metrics);
}
class Metric {
String country;
int quantity;
Metric(this.country, this.quantity);
static int grandTotal(List<Metric> metrics) {
int total = 0;
for (final metric in metrics) {
total += metric.quantity;
}
return total;
}
}
List<Person> get dataModel() {
List<Person> persons = [];
List<Search> searches = [];
List<Metric> metrics = [];
// Snooki data
metrics = [];
metrics.add(new Metric("USA", 200300312));
searches.add(new Search("intellect", 6, Metric.grandTotal(metrics), metrics));
metrics.add(new Metric("USA", 75000000));
metrics.add(new Metric("China", 5));
metrics.add(new Metric("EU", 110000));
metrics.add(new Metric("Canada", 3400000));
metrics.add(new Metric("Mexico", 20000));
searches.add(new Search("breading", 5, Metric.grandTotal(metrics), metrics));
metrics = [];
metrics.add(new Metric("USA", 5000000));
metrics.add(new Metric("China", 3));
metrics.add(new Metric("EU", 90000));
metrics.add(new Metric("Canada", 3100000));
metrics.add(new Metric("Mexico", 24000));
searches.add(new Search("booze", 8, Metric.grandTotal(metrics), metrics));
metrics = [];
metrics.add(new Metric("USA", 5000000));
metrics.add(new Metric("EU", 90000));
metrics.add(new Metric("Canada", 300000));
searches.add(new Search("turpitude", 10, Metric.grandTotal(metrics), metrics));
persons.add(new Person("Snooki", 24, searches));
// Lady Gaga
searches = [];
metrics = [];
metrics.add(new Metric("USA", 11000000));
metrics.add(new Metric("China", 5000000000));
metrics.add(new Metric("EU", 8700000));
metrics.add(new Metric("Canada", 3400000));
metrics.add(new Metric("Mexico", 24349898));
searches.add(new Search("bad romance", 3, Metric.grandTotal(metrics), metrics));
metrics = [];
metrics.add(new Metric("USA", 980000));
metrics.add(new Metric("China", 187000000));
searches.add(new Search("fashion", 7, Metric.grandTotal(metrics), metrics));
metrics = [];
metrics.add(new Metric("USA", 7630000));
searches.add(new Search("outrageous", 9, Metric.grandTotal(metrics), metrics));
persons.add(new Person("Lady Gaga", 25, searches));
// Uggie (The Artist dog)
searches = [];
metrics = [];
metrics.add(new Metric("USA", 1000000));
metrics.add(new Metric("China", 34000));
metrics.add(new Metric("EU", 11000000000));
metrics.add(new Metric("Canada", 5023));
metrics.add(new Metric("Mexico", 782));
searches.add(new Search("smart", 2, Metric.grandTotal(metrics), metrics));
metrics = [];
metrics.add(new Metric("USA", 18900000));
metrics.add(new Metric("China", 34000));
metrics.add(new Metric("EU", 990000000));
metrics.add(new Metric("Canada", 6739920));
searches.add(new Search("cute", 4, Metric.grandTotal(metrics), metrics));
metrics = [];
metrics.add(new Metric("USA", 1));
metrics.add(new Metric("China", 1500000000000));
metrics.add(new Metric("EU", 50));
metrics.add(new Metric("Canada", 0));
metrics.add(new Metric("Mexico", 7801));
searches.add(new Search("tasty", 1, Metric.grandTotal(metrics), metrics));
persons.add(new Person("Uggie (Artist dog)", 10, searches));
return persons;
}

View file

@ -0,0 +1,8 @@
template NameEntry(String name, int age) {
<div var=topDiv attr="test" attr1=test1 attr2='test2' attr3=test3>
<span var=spanElem>${name}</span>
<span>-</span>
<span>${age}</span>
</div>
}

View file

@ -0,0 +1,14 @@
template NameEntry(String name, int age) {
<div var=topDiv attr="test" attr1=test1 attr2='test2' attr3=test3>
<h1>
<h2>
<h3>
<span var=spanElem>${name}</span>
<span>-</span>
<span>${age}</span>
</h3>
</h2>
</h1>
</div>
}

View file

@ -0,0 +1 @@
template NameEntry(String name, int age) { css { .foo { color: red; font-weight: bold; } } <div var=topDiv attr="test" attr1=test1 attr2='test2' attr3=test3> <span var=spanElem class="foo">${name}</span> <span>-</span> <span>${age}</span> </div> }

View file

@ -0,0 +1,12 @@
template ProductView(Person person) {
<div>
${#with person}
<div>
<span>${name}</span>
<span>-</span>
<span>${age}</span>
</div>
${/with}
</div>
}

View file

@ -0,0 +1,16 @@
template ProductView(Person person) {
<div>
<span var=a1>
<h1>
${#with person}
<div>
<span>${name}</span>
<span>-</span>
<span>${age}</span>
</div>
${/with}
</h1>
</span>
</div>
}

View file

@ -0,0 +1,108 @@
#import('dart:html');
#source('realviews.dart');
class Division {
String name;
int id;
List<Product> products;
Division(this.name, this.id, this.products);
}
class Product {
int id;
String name;
int users;
List<YTD_Sales> sales;
Product(this.id, this.name, this.users, this.sales);
}
class YTD_Sales {
int yearly;
String country;
YTD_Sales(this.yearly, this.country);
}
// TODO(terry): Remove use for debug only.
void debug() {
try {
throw "DEBUG";
} catch (var e) {
print("DEBUGBREAK");
}
}
void main() {
List<Division> divisions = [];
List<Product> products;
List<YTD_Sales> sales;
products = [];
sales = [];
sales.add(new YTD_Sales(52000000, "USA"));
sales.add(new YTD_Sales(550000, "China"));
sales.add(new YTD_Sales(56000000, "EU"));
sales.add(new YTD_Sales(510000, "Canada"));
sales.add(new YTD_Sales(58700028, "Mexico"));
products.add(new Product(100, "Gmail", 75000000, sales));
sales = [];
sales.add(new YTD_Sales(12000000, "USA"));
sales.add(new YTD_Sales(50000, "China"));
sales.add(new YTD_Sales(6000000, "EU"));
sales.add(new YTD_Sales(10000, "Canada"));
sales.add(new YTD_Sales(8700028, "Mexico"));
products.add(new Product(101, "Talk", 5000000, sales));
divisions.add(new Division("Apps", 1, products));
products = [];
sales = [];
sales.add(new YTD_Sales(200000, "USA"));
sales.add(new YTD_Sales(20000, "China"));
sales.add(new YTD_Sales(2200000, "EU"));
sales.add(new YTD_Sales(10000, "Canada"));
sales.add(new YTD_Sales(100, "Mexico"));
products.add(new Product(200, "iGoogle", 2000000, sales));
divisions.add(new Division("Misc", 3, products));
products = [];
sales = [];
sales.add(new YTD_Sales(1200, "USA"));
sales.add(new YTD_Sales(50, "China"));
sales.add(new YTD_Sales(600, "EU"));
sales.add(new YTD_Sales(10, "Canada"));
sales.add(new YTD_Sales(8, "Mexico"));
products.add(new Product(300, "Dart", 2000, sales));
divisions.add(new Division("Chrome", 2, products));
products = [];
sales = [];
divisions.add(new Division("Search", 4, products));
var header = new Header("Google World Wide", new Date.now());
var listView = new DivisionSales(divisions);
document.body.elements.add(header.root); // Add top view.
document.body.elements.add(listView.root); // Add list view.
// Hookup events.
for (var elem in listView.productZippy) {
elem.on.click.add((MouseEvent e) {
var expandCollapseElem = e.toElement;
DivElement salesDiv = expandCollapseElem.parent.elements.last();
bool showSales = (salesDiv.classes.contains(DivisionSales.showSales));
expandCollapseElem.innerHTML = showSales ? "&#9654;": "&#9660;";
expandCollapseElem.classes.remove(showSales ? DivisionSales.expand : DivisionSales.collapse);
expandCollapseElem.classes.add(showSales ? DivisionSales.collapse : DivisionSales.expand);
salesDiv.classes.clear();
salesDiv.classes.add(showSales ? DivisionSales.hideSales : DivisionSales.showSales);
});
}
}

View file

@ -0,0 +1,106 @@
template DivisionSales(var divisions) {
css {
.division-item {
background-color: #bbb;
border-top: 2px solid white;
line-height: 20pt;
padding-left: 5px;
}
.product-item {
background-color: lightgray;
margin-left: 10px;
border-top: 2px solid white;
line-height: 20pt;
}
.product-title {
position: absolute;
left: 45px;
}
.product-name {
font-weight: bold;
position: absolute;
left: 100px;
}
.product-users {
position: absolute;
left: 150px;
font-style: italic;
color: gray;
width: 110px;
}
.expand-collapse {
margin-left: 5px;
margin-right: 5px;
vertical-align: top;
cursor: pointer;
}
.expand {
font-size: 9pt;
}
.collapse {
font-size: 8pt;
}
.show-sales {
display: inherit;
}
.hide-sales {
display: none;
}
.sales-item {
font-family: arial;
background-color: lightgray;
margin-left: 10px;
border-top: 1px solid white;
line-height: 18pt;
padding-left: 5px;
}
.ytd-sales {
position: absolute;
left: 100px;
}
}
<div>
${#each divisions}
<div class="division-item">
<span>${name}</span>
<span>-</span>
<span>${id}</span>
</div>
<div>
${#each divisions.products}
<div class="product-item">
<span var=productZippy class="expand-collapse expand">&#9660;</span>
<span class='product-title'>Product</span>
<span class="product-name">${name}</span>
<span class="product-users" align=right>${users}&nbsp;users</span>
<div class="show-sales">
${#each products.sales}
<div class="sales-item">
<span>${country}</span>
<span class="ytd-sales">\$${yearly}</span>
</div>
${/each}
</div>
</div>
${/each}
</div>
${/each}
</div>
}
template Header(String company, Date date) {
css {
.header {
background-color: slateGray;
font-family: arial;
color: lightgray;
font-weight: bold;
padding-top: 20px;
}
}
<div class='header' align=center>
<h2>${company}</h2>
<div align=right>${date}</div>
</div>
}

91
utils/tests/template/run Executable file
View file

@ -0,0 +1,91 @@
#!/bin/bash
# get the number of command-line arguments given
ARGC=$#
if [[ $ARGC -eq 1 ]];
then
if [[ $1 = "simple1" ]];
then
./../../template/template name_entry.tmpl name_entry.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_simple.dart
elif [[ $1 == "simple2" ]];
then
./../../template/template name_entry2.tmpl name_entry.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_simple.dart
elif [[ $1 == "simple3" ]];
then
./../../template/template name_entry_css.tmpl name_entry.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_simple.dart
elif [[ $1 == "with1" ]];
then
./../../template/template productview.tmpl productview.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_with.dart
elif [[ $1 == "with2" ]];
then
./../../template/template productview2.tmpl productview.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_with.dart
elif [[ $1 == "list" ]];
then
./../../template/template applications.tmpl applications.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_list.dart
elif [[ $1 == "complex" ]];
then
./../../template/template top_searches.tmpl top_searches.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_complex.dart
elif [[ $1 == "complexcss" ]];
then
./../../template/template top_searches_css.tmpl top_searches.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_complex.dart
elif [[ $1 == "complex2" ]];
then
./../../template/template top_searches2.tmpl top_searches2.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/test_complex2.dart
elif [[ $1 == "real" ]];
then
./../../template/template realviews.tmpl realviews.dart
cd ../../../frog
./frog.py --html -- ../utils/tests/template/real_app.dart
elif [[ $1 == "clean" ]];
then
# remove all generated templates
echo "rm name_entry.dart"
rm -f name_entry.dart
echo "rm productview.dart"
rm -f productview.dart
echo "rm applications.dart"
rm -f applications.dart
echo "rm top_searches.dart"
rm -f top_searches.dart
echo "rm top_searches2.dart"
rm -f top_searches2.dart
echo "rm realviews.dart"
rm -f realviews.dart
fi
else
echo -e "\033[31mUnknown test\033[0m"
echo -e ""
echo -e "Known tests are:"
echo -e " simple1"
echo -e " simple2"
echo -e " simple3"
echo -e " with1"
echo -e " with2"
echo -e " list"
echo -e " complex"
echo -e " complexcss"
echo -e " complex2"
echo -e " real"
echo -e ""
echo -e "clean - removes all template generated Dart classes"
fi

243
utils/tests/template/sample.dart Executable file
View file

@ -0,0 +1,243 @@
// 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.
#import('dart:html');
// Utility functions:
String safeHTML(String value) {
// TODO(terry): TBD
return value;
}
/* Data Model */
class Friend {
String name;
String phone;
int age;
int friendsCircle, familyCircle, workCircle;
Friend(this.name, this.phone, this.age,
this.friendsCircle, this.familyCircle, this.workCircle);
}
/* Simple template:
================
template HTML FriendList(List<Friend> friends) {
<div>Friends:
<ul>
${#each friends} // Iterates over each friend
<li>${name}</li> // Scope of block is Friend type
${/each}
</ul>
</div>
}
*/
class FriendList /*extends Template*/ {
Element _fragment;
// ${#each friends}
each_0(List items, Element parent) {
for (var item in items) {
var e0 = new Element.html('<li></li>'); // Node before injection
e0.innerHTML = inject_0(item);
parent.elements.add(e0);
}
}
// ${name}
String inject_0(item) {
return safeHTML('''${item.name}''');
}
FriendList(List<Friend> friends) {
_fragment = new Element.tag('div');
Element e0 = new Element.html('<div>Friends:</div>');
_fragment.elements.add(e0);
Element e0_0 = new Element.html('<ul></ul>'); // Node before #each
e0.elements.add(e0_0);
each_0(friends, e0_0);
}
Element get root() => _fragment.nodes.first;
String toString(){
return _fragment.innerHTML;
}
}
/* More template control:
======================
template HTML FriendEntry(Friend friend) {
<li>
${#with friend}
<span>${name}</span>
${#if (age < 18)}
<span class=left-space>child</span>
${#else}
<span class=left-space>adult</span>
${/if}
<span class=left-space>circles = ${friendCircle + familyCircle + workCircle}</span>
${/friend}
</li>
}
*/
class FriendEntry /*extends Template*/ {
Element _fragment;
// ${#with friend}
with_0(Friend item, Element parent) {
var e0 = new Element.html('<span></span>'); // Node before injection
e0.innerHTML = inject_0(item);
parent.elements.add(e0);
// ${#if expression}
if (if_0(item)) {
var e1 = new Element.html('<span class="left-space">child</span>');
parent.elements.add(e1);
} else {
var e2 = new Element.html('<span class="left-space">adult</span>');
parent.elements.add(e2);
}
// Node before injection.
var e3 = new Element.html('<span class="left-space"></span>');
e3.innerHTML = inject_1(item);
parent.elements.add(e3);
}
// expression (age < 18)}
bool if_0(var item) {
return (item.age < 18);
}
// ${name}
String inject_0(item) {
return safeHTML('''${item.name}''');
}
// ${friendCircle + family.Circle + workCircle
String inject_1(item) {
return safeHTML('circles = ${item.friendsCircle + item.familyCircle + item.workCircle}');
}
FriendEntry(Friend friend) {
_fragment = new Element.tag('div');
Element e0 = new Element.html('<li></li>'); // Node before #with
_fragment.elements.add(e0);
with_0(friend, e0);
}
Element get root() => _fragment.nodes.first;
String toString(){
return _fragment.innerHTML;
}
}
/* Template with events:
=====================
template HTML FriendEntryEvents(Friend friend) {
<li>
${#with friend}
<span var=friendElem style="cursor: pointer;">${name}</span>
${#if (age < 18)}
<span class=left-space>child</span>
${#else}
<span class=left-space>adult</span>
${/if}
<span class=left-space>circles = ${friendCircle + familyCircle + workCircle}</span>
${/friend}
</li>
}
*/
class FriendEntryEvents /*extends Template*/ {
Element _fragment;
var _friendElem;
get friendElem() => _friendElem;
// ${#with friend}
with_0(Friend item, Element parent) {
_friendElem = new Element.html('<span style="cursor: pointer;"></span>'); // Node before injection
_friendElem.innerHTML = inject_0(item);
parent.elements.add(_friendElem);
// ${#if expression}
if (if_0(item)) {
var e1 = new Element.html('<span class="left-space">child</span>');
parent.elements.add(e1);
} else {
var e2 = new Element.html('<span class="left-space">adult</span>');
parent.elements.add(e2);
}
// Node before injection.
var e3 = new Element.html('<span class="left-space"></span>');
e3.innerHTML = inject_1(item);
parent.elements.add(e3);
}
// expression (age < 18)}
bool if_0(var item) {
return (item.age < 18);
}
// ${name}
String inject_0(item) {
return safeHTML('''${item.name}''');
}
// ${friendCircle + family.Circle + workCircle
String inject_1(item) {
return safeHTML('circles = ${item.friendsCircle + item.familyCircle + item.workCircle}');
}
FriendEntryEvents(Friend friend) {
_fragment = new Element.tag('div');
Element e0 = new Element.html('<li></li>'); // Node before #with
_fragment.elements.add(e0);
with_0(friend, e0);
}
Element get root() => _fragment.nodes.first;
String toString(){
return _fragment.innerHTML;
}
}
void main() {
// Setup style sheet for page.
document.head.elements.add(new Element.html('<style>.left-space { margin-left: 10px; }</style>'));
// Create data model.
List<Friend> friends = new List<Friend>();
friends.add(new Friend('Tom','425.123.4567', 35, 20, 10, 40));
friends.add(new Friend('Sue','802.987.6543', 23, 53, 25, 80));
friends.add(new Friend('Bill','617.123.4444', 50, 10, 5, 110));
// Simple template.
document.body.elements.add(new FriendList(friends).root);
// Use control template.
document.body.elements.add(new FriendEntry(friends[0]).root);
// Template with Events:
var clickableFriend = new FriendEntryEvents(friends[0]);
document.body.elements.add(clickableFriend.root);
clickableFriend.friendElem.on.click.add((e) {
var elemStyle = e.srcElement.style;
String toggleColor = elemStyle.getPropertyValue("background-color") == "red" ? "white" : "red";
elemStyle.setProperty("background-color", "${toggleColor}");
});
// Calling template inside of a template:
// document.body.elements.add(new Templates(friends).root);
}

View file

@ -0,0 +1,17 @@
// Sample for complex templates:
//
// top_searches_css.tmpl
#import('dart:html');
#source('complex_datamodel.dart');
#source('top_searches.dart');
void main() {
List<Person> persons = dataModel;
Person whichPerson = persons[2];
var searchesView = new TopSearches(whichPerson, whichPerson.searches);
document.body.elements.add(searchesView.root);
}

View file

@ -0,0 +1,16 @@
// Sample for complex templates:
//
// top_searches2.tmpl
#import('dart:html');
#source('complex_datamodel.dart');
#source('top_searches2.dart');
void main() {
List<Person> persons = dataModel;
var searchesView = new TopSearches(persons);
document.body.elements.add(searchesView.root);
}

View file

@ -0,0 +1,25 @@
// Sample for #each templates:
//
// applications.tmpl
#import('dart:html');
#source('applications.dart');
class Product {
String name;
int users;
Product(this.name, this.users);
}
void main() {
List<Product> products = [];
products.add(new Product("Gmail", 75000000));
products.add(new Product("Talk", 5000000));
products.add(new Product("iGoogle", 2000000));
products.add(new Product("Dart", 2000));
var form = new Applications(products);
document.body.elements.add(form.root);
}

View file

@ -0,0 +1,16 @@
// Sample for testing templates:
//
// name_entry.tmpl
// name_entry2.tmpl
// name_entry_css.tmpl
#import('dart:html');
#source('name_entry.dart');
void main() {
// Simple template.
var x = new NameEntry("Terry Lucas", 52);
var y = x.root;
document.body.elements.add(y);
}

View file

@ -0,0 +1,23 @@
// Sample for #with templates:
//
// productview.tmpl
// productview2.tmpl
#import('dart:html');
#source('productview.dart');
class Person {
String name;
int age;
Person(this.name, this.age);
}
void main() {
// Simple template.
Person person = new Person("Terry Lucas", 52);
ProductView view = new ProductView(person);
document.body.elements.add(view.root);
}

View file

@ -0,0 +1,18 @@
template TopSearches(Person person, var searches) {
<div>
${#with person}
<div>
<span>${name},&nbsp;</span>
<span>age&nbsp;=&nbsp;</span>
<span>${age}</span>
</div>
${#each searches}
<div>
<span style="margin-left: 10px;">query=${query}</span>
<span style="margin-left: 20px;">hits=${total}</span>
</div>
${/each}
${/with}
</div>
}

View file

@ -0,0 +1,64 @@
template TopSearches(var persons) {
css {
.all-list {
font-family: arial;
}
.person-item {
padding: 4px 0px 4px 5px;
background-color: slateGray;
color: white;
font-weight: bold;
}
.search-item {
font-size: 11pt;
padding: 3px 0px 3px 15px;
background-color: #aaa;
font-weight: bold;
}
.metrics-item {
font-size: 10pt;
padding: 2px 0px 2px 25px;
background-color: lightgray;
border-bottom: 1px solid white;
}
.total-hits {
position: absolute;
left: 100px;
}
}
<div class="all-list">
${#each persons}
<div class="person-item">
<span>${name},&nbsp;</span>
<span>age&nbsp;=&nbsp;</span>
<span>${age}</span>
</div>
<div>
${#each persons.searches}
<div class="search-item">query=${query},&nbsp;rank=${rank},&nbsp;total=${total}</div>
${#each searches.metrics}
<div class="metrics-item">
<span>${country}</span>
<span class="total-hits">${quantity}</span>
</div>
${/each}
${/each}
</div>
${/each}
</div>
}
template NameEntry(String name, int age) {
css {
.name-item {
font-size: 18pt;
font-weight: bold;
}
}
<div var=topDiv class="name-item" attr="test" attr1=test1 attr2='test2' attr3=test3>
<span var=spanElem>${name}</span>
<span> - </span>
<span>${age}</span>
</div>
}

View file

@ -0,0 +1,49 @@
template TopSearches(Person person, var searches) {
css {
.metrics-item {
font-family: arial;
background-color: lightgray;
margin-left: 10px;
border-bottom: 1px solid white;
}
.total-hits {
position: absolute;
left: 100px;
}
}
<div>
${#with person}
<div>
<span>${name},&nbsp;</span>
<span>age&nbsp;=&nbsp;</span>
<span>${age}</span>
</div>
<div>
${#each searches}
<div>query=${query},&nbsp;rank=${rank},&nbsp;total=${total}</div>
${#each searches.metrics}
<div class="metrics-item">
<span>${country}</span>
<span class="total-hits">${quantity}</span>
</div>
${/each}
${/each}
</div>
${/with}
</div>
}
template NameEntry(String name, int age) {
css {
.name-item {
font-size: 18pt;
font-weight: bold;
}
}
<div var=topDiv class="name-item" attr="test" attr1=test1 attr2='test2' attr3=test3>
<span var=spanElem>${name}</span>
<span> - </span>
<span>${age}</span>
</div>
}