Chromium Code Reviews| Index: frog/leg/string_validator.dart |
| diff --git a/frog/leg/string_validator.dart b/frog/leg/string_validator.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7815d36b64ab5bf6b64d0479a13cb105d87cd412 |
| --- /dev/null |
| +++ b/frog/leg/string_validator.dart |
| @@ -0,0 +1,196 @@ |
| +// 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. |
| + |
| +// Checks a tree structure for whether the string literals are valid. |
| + |
| +class StringValidatorTask extends CompilerTask { |
| + // Validator has no state except the compiler, so we just create one |
| + // per task. |
| + final StringValidator validator; |
| + |
| + StringValidatorTask(Compiler compiler) |
| + : super(compiler), |
| + validator = new StringValidator(compiler); |
| + |
| + void validate(Node tree) { |
| + tree.accept(validator); |
| + } |
| +} |
| + |
| +class StringValidator extends AbstractVisitor { |
| + final Compiler compiler; |
| + |
| + StringValidator(this.compiler); |
| + |
| + void visitNode(Node node) { |
| + node.visitChildren(this); |
| + } |
| + |
| + visitLiteralString(LiteralString node) { |
| + // This is a string literal where the source contains both start and |
| + // end quotes. |
| + // String literals that are part of string interpolations are |
| + // handled there to account for their varying degree of quotedness. |
| + SourceString source = node.value; |
| + StringQuoting quoting = quotingFromString(source); |
| + int leftQuote = quoting.leftQuoteLength; |
| + int rightQuote = quoting.rightQuoteLength; |
| + SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote); |
| + validateString(node, node.token.charOffset + leftQuote, content, quoting); |
| + } |
| + |
| + // String interpolation validates the immediate strings and traverses |
| + // the sub-expressions. |
| + visitStringInterpolation(StringInterpolation node) { |
| + LiteralString literalString = node.string; |
| + SourceString source = literalString.value; |
| + StringQuoting quoting = quotingFromString(source); |
| + int offset = literalString.token.charOffset + quoting.leftQuoteLength; |
| + SourceString string = source.copyWithoutQuotes(quoting.leftQuoteLength, 0); |
| + for (StringInterpolationPart part in node.parts) { |
| + validateString(literalString, offset, string, quoting); |
| + part.expression.accept(this); |
| + literalString = part.string; |
| + offset = literalString.token.charOffset; |
| + string = literalString.value; |
| + } |
| + string = string.copyWithoutQuotes(0, quoting.rightQuoteLength); |
| + validateString(literalString, offset, string, quoting); |
| + } |
| + |
| + static StringQuoting quotingFromString(SourceString sourceString) { |
| + Iterator<int> source = sourceString.iterator(); |
| + bool raw = false; |
| + int quoteChar = source.next(); |
| + if (quoteChar == $AT) { |
| + raw = true; |
| + quoteChar = source.next(); |
| + } |
| + assert(quoteChar === $SQ || quoteChar === $DQ); |
| + // String has one quote. Check it if has three. |
|
karlklose
2012/01/25 08:47:26
one -> at least one
Lasse Reichstein Nielsen
2012/01/26 10:14:20
Done.
|
| + // If it only have two, the string must be an empty string literal, |
| + // and end after the second quote. |
| + bool multiline = false; |
| + if (source.hasNext() && source.next() == quoteChar && source.hasNext()) { |
| + assert(source.next() == quoteChar); |
| + multiline = true; |
| + } |
| + return StringQuoting.get(quoteChar, raw, multiline); |
| + } |
| + |
| + void stringParseError(String message, LiteralString node, int offset) { |
| + assert(node.quotedString === null); |
| + compiler.cancel("$message @ $offset", node); |
| + } |
| + |
| + /** |
| + * Validates the escape sequences and special characters of a string literal. |
| + * Returns the number of actual scalar values in the corresponding Dart |
|
karlklose
2012/01/25 08:47:26
It returns whether the string is valid or not, doe
Lasse Reichstein Nielsen
2012/01/26 10:14:20
True. Old comment is old.
|
| + * string, or a negative value if the string is invalid. |
| + */ |
| + bool validateString(LiteralString node, |
| + int startOffset, |
| + SourceString string, |
| + StringQuoting quoting) { |
| + // We only need to check for invalid x and u escapes, for line |
| + // terminators in non-multiline strings, and for invalid Unicode |
| + // scalar values (either directly or as u-escape values). |
| + int length = 0; |
| + int index = startOffset; |
| + for(Iterator<int> iter = string.iterator(); iter.hasNext(); length++) { |
| + index++; |
| + int code = iter.next(); |
| + if (code === $BACKSLASH) { |
| + if (quoting.raw) continue; |
| + if (!iter.hasNext()) { |
| + stringParseError("Incomplete escape sequence", node, index); |
| + return false; |
| + } |
| + index++; |
| + code = iter.next(); |
| + if (code === $x) { |
| + for (int i = 0; i < 2; i++) { |
| + if (!iter.hasNext()) { |
| + stringParseError("Incomplete escape sequence", node, index); |
| + return false; |
| + } |
| + index++; |
| + code = iter.next(); |
| + if (!isHexDigit(code)) { |
| + stringParseError("Invalid character in escape sequence", |
| + node, index); |
| + return false; |
| + } |
| + } |
| + continue; |
| + } else if (code === $u) { |
| + int escapeStart = index - 1; |
| + index++; |
| + code = iter.next(); |
| + int value = 0; |
| + if (code == $OPEN_CURLY_BRACKET) { |
| + // expect 1-7 hex digits. |
| + int count = 0; |
| + index++; |
| + code = iter.next(); |
| + do { |
| + if (!isHexDigit(code)) { |
| + stringParseError("Invalid character in escape sequence", |
| + node, index); |
| + return false; |
| + } |
| + count++; |
| + value = value * 16 + hexDigitValue(code); |
| + index++; |
| + code = iter.next(); |
| + } while (code != $CLOSE_CURLY_BRACKET); |
| + if (count > 7) { |
| + stringParseError("Invalid character in escape sequence", |
| + node, index - (count - 7)); |
| + return false; |
| + } |
| + } else { |
| + // Expect four hex digits, including the one just tread. |
| + for (int i = 0; i < 4; i++) { |
| + if (i > 0) { |
| + index++; |
| + code = iter.next(); |
| + } |
| + if (!isHexDigit(code)) { |
| + stringParseError("Invalid character in escape sequence", |
| + node, index); |
| + return false; |
| + } |
| + value = value * 16 + hexDigitValue(code); |
| + } |
| + } |
| + if (0xd800 <= value && ( value <= 0xdfff || value > 0x10ffff)) { |
| + stringParseError( |
| + "Invalid unicode scalar value U+${value.toRadixString(16)}", |
| + node, index); |
| + return false; |
| + } |
| + continue; |
| + } |
| + } |
| + // This handles borth unescaped characters as well as those |
| + // characters after a backslash that doesn't have a special |
| + // meaning. |
| + if (code >= 0xd800 && (code <= 0xdfff || code > 0x10ffff)) { |
| + stringParseError( |
| + "Invalid unicode scalar value U+${code.toRadixString(16)}", |
| + node, index); |
| + return false; |
| + } |
| + if (!quoting.multiline && (code === $LF || code === $CR)) { |
| + stringParseError("Line terminator in single-line string", |
| + node, index); |
| + return false; |
| + } |
| + } |
| + // String literal successfully validated. |
| + node.quotedString = new QuotedString(string, quoting, length); |
| + return true; |
| + } |
| +} |