| Index: pkg/front_end/lib/src/scanner/errors.dart | 
| diff --git a/pkg/front_end/lib/src/scanner/errors.dart b/pkg/front_end/lib/src/scanner/errors.dart | 
| index bcf0c56585f52f7fcf62353e86605a15427dc998..10853c1f946b76d91b6a0654d8442b082c4438a9 100644 | 
| --- a/pkg/front_end/lib/src/scanner/errors.dart | 
| +++ b/pkg/front_end/lib/src/scanner/errors.dart | 
| @@ -3,6 +3,9 @@ | 
| // BSD-style license that can be found in the LICENSE file. | 
|  | 
| import 'package:front_end/src/base/errors.dart'; | 
| +import 'package:front_end/src/fasta/scanner/error_token.dart'; | 
| +import 'package:front_end/src/fasta/scanner/token.dart'; | 
| +import 'package:front_end/src/fasta/scanner/token_constants.dart'; | 
|  | 
| /** | 
| * The error codes used for errors detected by the scanner. | 
| @@ -57,3 +60,78 @@ class ScannerErrorCode extends ErrorCode { | 
| @override | 
| ErrorType get type => ErrorType.SYNTACTIC_ERROR; | 
| } | 
| + | 
| +/** | 
| + * Used to report a scan error at the given offset. | 
| + * The [errorCode] is the error code indicating the nature of the error. | 
| + * The [arguments] are any arguments needed to complete the error message | 
| + */ | 
| +typedef ReportError( | 
| +    ScannerErrorCode errorCode, int offset, List<Object> arguments); | 
| + | 
| +/// Translates the given error [token] into an analyzer error and reports it | 
| +/// using [reportError]. | 
| +void translateErrorToken(ErrorToken token, ReportError reportError) { | 
| +  int charOffset = token.charOffset; | 
| +  // TODO(paulberry,ahe): why is endOffset sometimes null? | 
| +  int endOffset = token.endOffset ?? charOffset; | 
| +  void _makeError(ScannerErrorCode errorCode, List<Object> arguments) { | 
| +    if (_isAtEnd(token, charOffset)) { | 
| +      // Analyzer never generates an error message past the end of the input, | 
| +      // since such an error would not be visible in an editor. | 
| +      // TODO(paulberry,ahe): would it make sense to replicate this behavior | 
| +      // in fasta, or move it elsewhere in analyzer? | 
| +      charOffset--; | 
| +    } | 
| +    reportError(errorCode, charOffset, arguments); | 
| +  } | 
| + | 
| +  var errorCode = token.errorCode; | 
| +  switch (errorCode) { | 
| +    case ErrorKind.UnterminatedString: | 
| +      // TODO(paulberry,ahe): Fasta reports the error location as the entire | 
| +      // string; analyzer expects the end of the string. | 
| +      charOffset = endOffset; | 
| +      return _makeError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL, null); | 
| +    case ErrorKind.UnmatchedToken: | 
| +      return null; | 
| +    case ErrorKind.UnterminatedComment: | 
| +      // TODO(paulberry,ahe): Fasta reports the error location as the entire | 
| +      // comment; analyzer expects the end of the comment. | 
| +      charOffset = endOffset; | 
| +      return _makeError(ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT, null); | 
| +    case ErrorKind.MissingExponent: | 
| +      // TODO(paulberry,ahe): Fasta reports the error location as the entire | 
| +      // number; analyzer expects the end of the number. | 
| +      charOffset = endOffset; | 
| +      return _makeError(ScannerErrorCode.MISSING_DIGIT, null); | 
| +    case ErrorKind.ExpectedHexDigit: | 
| +      // TODO(paulberry,ahe): Fasta reports the error location as the entire | 
| +      // number; analyzer expects the end of the number. | 
| +      charOffset = endOffset; | 
| +      return _makeError(ScannerErrorCode.MISSING_HEX_DIGIT, null); | 
| +    case ErrorKind.NonAsciiIdentifier: | 
| +    case ErrorKind.NonAsciiWhitespace: | 
| +      return _makeError(ScannerErrorCode.ILLEGAL_CHARACTER, [token.character]); | 
| +    case ErrorKind.UnexpectedDollarInString: | 
| +      return null; | 
| +    default: | 
| +      throw new UnimplementedError('$errorCode'); | 
| +  } | 
| +} | 
| + | 
| +/// Determines whether the given [charOffset], which came from the non-EOF token | 
| +/// [token], represents the end of the input. | 
| +bool _isAtEnd(Token token, int charOffset) { | 
| +  while (true) { | 
| +    // Skip to the next token. | 
| +    token = token.next; | 
| +    // If we've found an EOF token, its charOffset indicates where the end of | 
| +    // the input is. | 
| +    if (token.isEof) return token.charOffset == charOffset; | 
| +    // If we've found a non-error token, then we know there is additional input | 
| +    // text after [charOffset]. | 
| +    if (token.info.kind != BAD_INPUT_TOKEN) return false; | 
| +    // Otherwise keep looking. | 
| +  } | 
| +} | 
|  |