blob: 44fc8accfcdb982743f1f6ff5f288c36ce415dfa [file] [log] [blame]
// Copyright (c) 2016, 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 '../base/errors.dart';
import '../messages/codes.dart';
import 'error_token.dart';
import 'token.dart' show Token, TokenType;
import 'token_constants.dart';
/**
* 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 cfe, or move it elsewhere in analyzer?
charOffset--;
}
reportError(errorCode, charOffset, arguments);
}
Code<dynamic> errorCode = token.errorCode;
switch (errorCode.analyzerCodes?.first) {
case "UNTERMINATED_STRING_LITERAL":
// TODO(paulberry,ahe): Fasta reports the error location as the entire
// string; analyzer expects the end of the string.
reportError(
ScannerErrorCode.UNTERMINATED_STRING_LITERAL,
endOffset - 1,
null,
);
return;
case "UNTERMINATED_MULTI_LINE_COMMENT":
// TODO(paulberry,ahe): Fasta reports the error location as the entire
// comment; analyzer expects the end of the comment.
reportError(
ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT,
endOffset - 1,
null,
);
return;
case "MISSING_DIGIT":
// TODO(paulberry,ahe): Fasta reports the error location as the entire
// number; analyzer expects the end of the number.
charOffset = endOffset - 1;
return _makeError(ScannerErrorCode.MISSING_DIGIT, null);
case "MISSING_HEX_DIGIT":
// TODO(paulberry,ahe): Fasta reports the error location as the entire
// number; analyzer expects the end of the number.
charOffset = endOffset - 1;
return _makeError(ScannerErrorCode.MISSING_HEX_DIGIT, null);
case "ILLEGAL_CHARACTER":
// We can safely assume `token.character` is non-`null` because this error
// is only reported when there is a character associated with the token.
return _makeError(ScannerErrorCode.ILLEGAL_CHARACTER, [token.character!]);
case "UNEXPECTED_SEPARATOR_IN_NUMBER":
return _makeError(ScannerErrorCode.UNEXPECTED_SEPARATOR_IN_NUMBER, null);
case "UNSUPPORTED_OPERATOR":
return _makeError(ScannerErrorCode.UNSUPPORTED_OPERATOR, [
(token as UnsupportedOperator).token.lexeme,
]);
default:
if (errorCode == codeUnmatchedToken) {
charOffset = token.begin!.endToken!.charOffset;
TokenType type = token.begin!.type;
if (type == TokenType.OPEN_CURLY_BRACKET ||
type == TokenType.STRING_INTERPOLATION_EXPRESSION) {
return _makeError(ScannerErrorCode.EXPECTED_TOKEN, ['}']);
}
if (type == TokenType.OPEN_SQUARE_BRACKET) {
return _makeError(ScannerErrorCode.EXPECTED_TOKEN, [']']);
}
if (type == TokenType.OPEN_PAREN) {
return _makeError(ScannerErrorCode.EXPECTED_TOKEN, [')']);
}
if (type == TokenType.LT) {
return _makeError(ScannerErrorCode.EXPECTED_TOKEN, ['>']);
}
} else if (errorCode == codeUnexpectedDollarInString) {
return _makeError(ScannerErrorCode.MISSING_IDENTIFIER, null);
}
throw new UnimplementedError(
'$errorCode "${errorCode.analyzerCodes?.first}"',
);
}
}
/**
* 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.type.kind != BAD_INPUT_TOKEN) return false;
// Otherwise keep looking.
}
}
/**
* 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,
);
/**
* The error codes used for errors detected by the scanner.
*/
class ScannerErrorCode extends DiagnosticCode {
/**
* Parameters:
* 0: the token that was expected but not found
*/
static const ScannerErrorCode EXPECTED_TOKEN = const ScannerErrorCode(
'EXPECTED_TOKEN',
"Expected to find '{0}'.",
);
/**
* Parameters:
* 0: the illegal character
*/
static const ScannerErrorCode ILLEGAL_CHARACTER = const ScannerErrorCode(
'ILLEGAL_CHARACTER',
"Illegal character '{0}'.",
);
static const ScannerErrorCode MISSING_DIGIT = const ScannerErrorCode(
'MISSING_DIGIT',
"Decimal digit expected.",
);
static const ScannerErrorCode MISSING_HEX_DIGIT = const ScannerErrorCode(
'MISSING_HEX_DIGIT',
"Hexadecimal digit expected.",
);
static const ScannerErrorCode MISSING_IDENTIFIER = const ScannerErrorCode(
'MISSING_IDENTIFIER',
"Expected an identifier.",
);
static const ScannerErrorCode MISSING_QUOTE = const ScannerErrorCode(
'MISSING_QUOTE',
"Expected quote (' or \").",
);
/**
* Parameters:
* 0: the path of the file that cannot be read
*/
static const ScannerErrorCode UNABLE_GET_CONTENT = const ScannerErrorCode(
'UNABLE_GET_CONTENT',
"Unable to get content of '{0}'.",
);
static const ScannerErrorCode UNEXPECTED_DOLLAR_IN_STRING =
const ScannerErrorCode(
'UNEXPECTED_DOLLAR_IN_STRING',
"A '\$' has special meaning inside a string, and must be followed by "
"an identifier or an expression in curly braces ({}).",
correctionMessage: "Try adding a backslash (\\) to escape the '\$'.",
);
static const ScannerErrorCode UNEXPECTED_SEPARATOR_IN_NUMBER =
const ScannerErrorCode(
'UNEXPECTED_SEPARATOR_IN_NUMBER',
"Digit separators ('_') in a number literal can only be placed "
"between two digits.",
correctionMessage: "Try removing the '_'.",
);
/**
* Parameters:
* 0: the unsupported operator
*/
static const ScannerErrorCode UNSUPPORTED_OPERATOR = const ScannerErrorCode(
'UNSUPPORTED_OPERATOR',
"The '{0}' operator is not supported.",
);
static const ScannerErrorCode UNTERMINATED_MULTI_LINE_COMMENT =
const ScannerErrorCode(
'UNTERMINATED_MULTI_LINE_COMMENT',
"Unterminated multi-line comment.",
correctionMessage:
"Try terminating the comment with '*/', or "
"removing any unbalanced occurrences of '/*'"
" (because comments nest in Dart).",
);
static const ScannerErrorCode UNTERMINATED_STRING_LITERAL =
const ScannerErrorCode(
'UNTERMINATED_STRING_LITERAL',
"Unterminated string literal.",
);
/**
* Initialize a newly created error code to have the given [name]. The message
* associated with the error will be created from the given [problemMessage]
* template. The correction associated with the error will be created from the
* given [correctionMessage] template.
*/
const ScannerErrorCode(
String name,
String problemMessage, {
super.correctionMessage,
}) : super(
problemMessage: problemMessage,
name: name,
uniqueName: 'ScannerErrorCode.$name',
);
@override
DiagnosticSeverity get severity => DiagnosticSeverity.ERROR;
@override
DiagnosticType get type => DiagnosticType.SYNTACTIC_ERROR;
}