// Increments the line number and updates the "characters seen before // current line" count in `parseError`, iff `peek()` is a newline void Parser::maybeAdvanceLine() { if (peek() == LF) {
parseError.line++; // add 1 to index to get the number of characters seen so far // (including the newline)
parseError.lengthBeforeCurrentLine = index + 1;
}
}
/* static */ void Parser::setParseError(MessageParseError &parseError, uint32_t index) { // Translate absolute to relative offset
parseError.offset = index // Start with total number of characters seen
- parseError.lengthBeforeCurrentLine; // Subtract all characters before the current line // TODO: Fill this in with actual pre and post-context
parseError.preContext[0] = 0;
parseError.postContext[0] = 0;
}
// ------------------------------------- // Initialization of UnicodeSets
Allbutthreeoftheexceptionsinvolveambiguitiesaboutthemeaningofwhitespace. Oneambiguitynotinvolvingwhitespaceis: identifier->namespace":"name vs. identifier->name
// The loop exits either when we consume all the input, // or when we see a non-whitespace character. while (true) { // Check if all input has been consumed if (!inBounds()) { // If whitespace isn't required -- or if we saw it already -- // then the caller is responsible for checking this case and // setting an error if necessary. if (sawWhitespace) { // Not an error. return;
} // Otherwise, whitespace is required; the end of the input has // been reached without whitespace. This is an error.
ERROR(errorCode); return;
}
// Input remains; process the next character if it's whitespace, // exit the loop otherwise if (isWhitespace(peek())) {
sawWhitespace = true; // Increment line number in parse error if we consume a newline
maybeAdvanceLine();
next();
} else { break;
}
}
if (!sawWhitespace) {
ERROR(errorCode);
}
}
void Parser::parseOptionalBidi() { while (true) { if (!inBounds()) { return;
} if (isBidiControl(peek())) {
next();
} else { break;
}
}
}
/* Nopre,nopost,forthesamereasonas`parseWhitespaceMaybeRequired()`.
*/ void Parser::parseOptionalWhitespace() { while (true) { if (!inBounds()) { return;
} auto cp = peek(); if (isWhitespace(cp) || isBidiControl(cp)) {
maybeAdvanceLine();
next();
} else { break;
}
}
}
// Consumes a single character, signaling an error if `peek()` != `c` // No postcondition -- a message can end with a '}' token void Parser::parseToken(UChar32 c, UErrorCode& errorCode) {
CHECK_BOUNDS(errorCode);
if (peek() == c) {
next();
normalizedInput += c; return;
} // Next character didn't match -- error out
ERROR(errorCode);
}
UnicodeString result; // The following is a hack to get around ambiguity in the grammar: // identifier -> namespace ":" name // vs. // identifier -> name // can't be distinguished without arbitrary lookahead. // Instead, we treat the production as: // identifier -> namespace *(":"name) // and then check for multiple colons.
// Parse namespace
result += parseName(errorCode);
int32_t firstColon = -1; while (inBounds() && peek() == COLON) { // Parse ':' separator if (firstColon == -1) {
firstColon = index;
}
parseToken(COLON, errorCode);
result += COLON; // Check for message ending with something like "foo:" if (!inBounds()) {
ERROR(errorCode);
} else { // Parse name part
result += parseName(errorCode);
}
}
// If there's at least one ':', scan from the first ':' // to the end of the name to check for multiple ':'s if (firstColon != -1) { for (int32_t i = firstColon + 1; i < result.length(); i++) { if (result[i] == COLON) {
ERROR_AT(errorCode, i); return {};
}
}
}
normalizedInput += peek();
next(); // Consume the function start character if (!inBounds()) {
ERROR(errorCode); return FunctionName();
} return parseIdentifier(errorCode);
}
Nopostcondition(amessagecanendwithanescapedchar)
*/
UnicodeString Parser::parseEscapeSequence(UErrorCode& errorCode) {
U_ASSERT(inBounds());
U_ASSERT(peek() == BACKSLASH);
normalizedInput += BACKSLASH;
next(); // Skip the initial backslash
UnicodeString str; if (inBounds()) { // Expect a '{', '|' or '}' switch (peek()) { case LEFT_CURLY_BRACE: case RIGHT_CURLY_BRACE: case PIPE: case BACKSLASH: { /* Append to the output string */
str += peek(); /* Update normalizedInput */
normalizedInput += peek(); /* Consume the character */
next(); return str;
} default: { // No other characters are allowed here break;
}
}
} // If control reaches here, there was an error
ERROR(errorCode); return str;
}
// Prepare to "backtrack" to resolve ambiguity // about whether whitespace precedes another // attribute, or the '=' sign
int32_t savedIndex = index;
parseOptionalWhitespace();
UnicodeString rhsStr; // Parse RHS, which must be a literal // attribute = "@" identifier [o "=" o literal]
rand = Operand(parseLiteral(errorCode));
} else { // attribute -> "@" identifier [[s] "=" [s]] // Use null operand, which `rand` is already set to // "Backtrack" by restoring the whitespace (if there was any)
index = savedIndex;
}
UnicodeString rhsStr;
Operand rand; // Parse RHS, which is either a literal or variable switch (peek()) { case DOLLAR: {
rand = Operand(parseVariableName(errorCode)); break;
} default: { // Must be a literal
rand = Operand(parseLiteral(errorCode)); break;
}
}
U_ASSERT(!rand.isNull());
// Finally, add the key=value mapping // Use a local error code, check for duplicate option error and // record it as with other errors
UErrorCode status = U_ZERO_ERROR;
addOption.addOption(lhs, std::move(rand), status); if (U_FAILURE(status)) {
U_ASSERT(status == U_MF_DUPLICATE_OPTION_NAME_ERROR);
errors.setDuplicateOptionName(errorCode);
}
}
/* Consumeoptionalwhitespacefollowedbyasequenceofoptions (possiblyempty),separatedbywhitespace
*/ template <class T> void Parser::parseOptions(OptionAdder<T>& addOption, UErrorCode& errorCode) { // Early exit if out of bounds -- no more work is possible
CHECK_BOUNDS(errorCode);
while(true) { // If the next character is not whitespace, that means we've already // parsed the entire options list (which may have been empty) and there's // no trailing whitespace. In that case, exit. if (!isWhitespace(peek())) { break;
}
int32_t firstWhitespace = index;
// In any case other than an empty options list, there must be at least // one whitespace character.
parseRequiredWhitespace(errorCode); // Restore precondition
CHECK_BOUNDS(errorCode);
// If a name character follows, then at least one more option remains // in the list. // Otherwise, we've consumed all the options and any trailing whitespace, // and can exit. // Note that exiting is sort of like backtracking: "(s option)" doesn't apply, // so we back out to [s]. if (!isNameStart(peek())) { // We've consumed all the options (meaning that either we consumed non-empty // whitespace, or consumed at least one option.) // Done. // Remove the required whitespace from normalizedInput
normalizedInput.truncate(normalizedInput.length() - 1); // "Backtrack" so as to leave the optional whitespace there // when parsing attributes
index = firstWhitespace; break;
}
parseOption(addOption, errorCode);
}
}
while(true) { // If the next character is not whitespace, that means we've already // parsed the entire attributes list (which may have been empty) and there's // no trailing whitespace. In that case, exit. if (!isWhitespace(peek())) { break;
}
// In any case other than an empty attributes list, there must be at least // one whitespace character.
parseRequiredWhitespace(errorCode); // Restore precondition if (!inBounds()) {
ERROR(errorCode); break;
}
// If an '@' follows, then at least one more attribute remains // in the list. // Otherwise, we've consumed all the attributes and any trailing whitespace, // and can exit. // Note that exiting is sort of like backtracking: "(s attributes)" doesn't apply, // so we back out to [s]. if (peek() != AT) { // We've consumed all the attributes (meaning that either we consumed non-empty // whitespace, or consumed at least one attribute.) // Done. // Remove the whitespace from normalizedInput
normalizedInput.truncate(normalizedInput.length() - 1); break;
}
parseAttribute(attrAdder, errorCode);
}
}
if (isWhitespace(peek())) {
int32_t firstWhitespace = index;
// If the next character is whitespace, either [s annotation] or [s] applies // (the character is either the required space before an annotation, or optional // trailing space after the literal or variable). It's still ambiguous which // one does apply.
parseOptionalWhitespace(); // Restore precondition
CHECK_BOUNDS(status);
// This next check resolves the ambiguity between [s annotation] and [s] bool isSAnnotation = isAnnotationStart(peek());
if (isSAnnotation) {
normalizedInput += SPACE;
}
if (isSAnnotation) { // The previously consumed whitespace precedes an annotation
builder.setOperator(parseAnnotation(status));
} else { // Either there's a right curly brace (will be consumed by the caller), // or there's an error and the trailing whitespace should be // handled by the caller. However, this is not an error // here because we're just parsing `literal [s annotation]`.
index = firstWhitespace;
}
} else { // Either there was never whitespace, or // the previously consumed whitespace is the optional trailing whitespace; // either the next character is '}' or the error will be handled by parseExpression. // Do nothing, since the operand was already set
}
// At the end of this code, the next character should either be '}', // whitespace followed by a '}', // or end-of-input
}
static Expression exprFallback(UErrorCode& status) {
Expression result; if (U_SUCCESS(status)) {
Expression::Builder exprBuilder(status); if (U_SUCCESS(status)) { // Construct a literal consisting just of The U+FFFD REPLACEMENT CHARACTER // per https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution
exprBuilder.setOperand(Operand(Literal(false, UnicodeString(REPLACEMENT))));
UErrorCode status = U_ZERO_ERROR;
result = exprBuilder.build(status); // An operand was set, so there can't be an error
U_ASSERT(U_SUCCESS(status));
}
} return result;
}
Expression Parser::parseExpression(UErrorCode& status) { if (U_FAILURE(status)) { return {};
}
// Early return if out of input -- no more work is possible
U_ASSERT(inBounds());
// Parse optional space // (the last [s] in e.g. "{" [s] literal [s annotation] *(s attribute) [s] "}")
parseOptionalWhitespace();
// Either an operand or operator (or both) must have been set already, // so there can't be an error
UErrorCode localStatus = U_ZERO_ERROR;
Expression result = exprBuilder.build(localStatus);
U_ASSERT(U_SUCCESS(localStatus));
// Check for end-of-input and missing '}' if (!inBounds()) {
ERROR(status);
} else { // Otherwise, it's safe to check for the '}'
parseToken(RIGHT_CURLY_BRACE, status);
} return result;
}
/* Parsea.localdeclaration,matchingthe`local-declaration` productioninthegrammar
*/ void Parser::parseLocalDeclaration(UErrorCode& status) { // End-of-input here would be an error; even empty // declarations must be followed by a body
CHECK_BOUNDS(status);
// Add binding from lhs to rhs, unless there was an error // (This ensures that if there was a correct lhs but a // parse error in rhs, the fallback for uses of the // lhs will be its own name rather than the rhs) /* This affects the behavior of this test case, which the spec isambiguousabout:
.local$bar{|foo|}{{{$bar}}}
Should`$bar`stillbeboundtoavaluealthough itsdeclarationissyntacticallyincorrect(missingthe'=')? Thiscodesaysno,butitneedstochangeif https://github.com/unicode-org/message-format-wg/issues/703 isresolveddifferently.
*/
CHECK_ERROR(status); if (!errors.hasSyntaxError()) {
dataModel.addBinding(Binding(std::move(lhs), std::move(rhs)), status); // Check if status is U_DUPLICATE_DECLARATION_ERROR // and add that as an internal error if so if (status == U_MF_DUPLICATE_DECLARATION_ERROR) {
status = U_ZERO_ERROR;
errors.addError(StaticErrorType::DuplicateDeclarationError, status);
}
}
}
/* Parsean.inputdeclaration,matchingthe`local-declaration` productioninthegrammar
*/ void Parser::parseInputDeclaration(UErrorCode& status) { // End-of-input here would be an error; even empty // declarations must be followed by a body
CHECK_BOUNDS(status);
// Restore precondition before calling parseExpression()
CHECK_BOUNDS(status);
// Save the index for error diagnostics
int32_t exprIndex = index;
Expression rhs = parseExpression(status);
// Here we have to check that the rhs is a variable-expression if (!rhs.getOperand().isVariable()) { // This case is a syntax error; report it at the beginning // of the expression
ERROR_AT(status, exprIndex); return;
}
VariableName lhs = rhs.getOperand().asVariable();
// Add binding from lhs to rhs // This just adds a new local variable that shadows the message // argument referred to, which is harmless. // When evaluating the RHS, the new local is not in scope // and the message argument will be correctly referred to.
CHECK_ERROR(status); if (!errors.hasSyntaxError()) {
dataModel.addBinding(Binding::input(std::move(lhs), std::move(rhs), status), status); // Check if status is U_MF_DUPLICATE_DECLARATION_ERROR // and add that as an internal error if so if (status == U_MF_DUPLICATE_DECLARATION_ERROR) {
status = U_ZERO_ERROR;
errors.addError(StaticErrorType::DuplicateDeclarationError, status);
}
}
}
Buildsupanenvironmentrepresentingthosedeclarations
*/ void Parser::parseDeclarations(UErrorCode& status) { // End-of-input here would be an error; even empty // declarations must be followed by a body
CHECK_BOUNDS(status);
SelectorKeys::Builder keysBuilder(status); if (U_FAILURE(status)) { return result;
}
// Since the first key is required, it's simplest to parse it separately.
keysBuilder.add(parseKey(status), status);
// Restore precondition if (!inBounds()) {
ERROR(status); return result;
}
// We've seen at least one whitespace-key pair, so now we can parse // *(s key) [s] while (peek() != LEFT_CURLY_BRACE || isWhitespace(peek()) || isBidiControl(peek())) { bool wasWhitespace = isWhitespace(peek()) || isBidiControl(peek());
parseRequiredWhitespace(status); if (!wasWhitespace) { // Avoid infinite loop when parsing something like: // when * @{!...
next();
}
// Restore precondition if (!inBounds()) {
ERROR(status); return result;
}
// At this point, it's ambiguous whether we are inside (s key) or [s]. // This check resolves that ambiguity. if (peek() == LEFT_CURLY_BRACE) { // A pattern follows, so what we just parsed was the optional // trailing whitespace. All the keys have been parsed.
// Unpush the whitespace from `normalizedInput`
normalizedInput.truncate(normalizedInput.length() - 1); break;
}
keysBuilder.add(parseKey(status), status);
}
Markup::Builder builder(status); if (U_FAILURE(status)) { return {};
}
// Consume the '{'
next();
normalizedInput += LEFT_CURLY_BRACE;
parseOptionalWhitespace(); bool closing = false; switch (peek()) { case NUMBER_SIGN: { // Open or standalone; consume the '#'
normalizedInput += peek();
next(); break;
} case SLASH: { // Closing
normalizedInput += peek();
closing = true;
next(); break;
} default: {
ERROR(status); return {};
}
}
// Parse the markup identifier
builder.setName(parseIdentifier(status));
// Parse the options, which must begin with a ' ' // if present if (inBounds() && (isWhitespace(peek()) || isBidiControl(peek()))) {
OptionAdder<Markup::Builder> optionAdder(builder);
parseOptions(optionAdder, status);
}
// Parse the attributes, which also must begin // with a ' ' if (inBounds() && (isWhitespace(peek()) || isBidiControl(peek()))) {
AttributeAdder<Markup::Builder> attrAdder(builder);
parseAttributes(attrAdder, status);
}
parseOptionalWhitespace();
bool standalone = false; // Check if this is a standalone or not if (!closing) { if (inBounds() && peek() == SLASH) {
standalone = true;
normalizedInput += SLASH;
next();
}
}
if (!inBounds()) {
ERROR(status); return exprFallback(status);
}
// Need to look ahead arbitrarily since whitespace // can appear before the '{' and '#' // in markup
int32_t tempIndex = 1; bool isMarkup = false; while (inBounds(1)) {
UChar32 c = peek(tempIndex); if (c == NUMBER_SIGN || c == SLASH) {
isMarkup = true; break;
} if (!(isWhitespace(c) || isBidiControl(c))) { break;
}
tempIndex++;
}
if (isMarkup) { return parseMarkup(status);
} return parseExpression(status);
}
bool empty = true; // Parse selectors // "Backtracking" is required here. It's not clear if whitespace is // (`[s]` selector) or (`[s]` variant) while (isWhitespace(peek()) || peek() == DOLLAR) {
int32_t whitespaceStart = index;
parseRequiredWhitespace(status); // Restore precondition
CHECK_BOUNDS(status); if (peek() != DOLLAR) { // This is not necessarily an error, but rather, // means the whitespace we parsed was the optional // whitespace preceding the first variant, not the // required whitespace preceding a subsequent variable. // In that case, "push back" the whitespace.
normalizedInput.truncate(normalizedInput.length() - 1);
index = whitespaceStart; break;
}
VariableName var = parseVariableName(status);
empty = false;
// Parse first variant
parseRequiredWhitespace(status); if (!inBounds()) {
ERROR(status); return;
}
parseVariant(status); if (!inBounds()) { // Not an error; there might be only one variant return;
}
while (isWhitespace(peek()) || isBidiControl(peek()) || isKeyStart(peek())) {
parseOptionalWhitespace(); // Restore the precondition. // Trailing whitespace is allowed. if (!inBounds()) { return;
}
parseVariant(status);
// Restore the precondition, *without* erroring out if we've // reached the end of input. That's because it's valid for the // message to end with a variant that has no trailing whitespace. // Why do we need to check this condition twice inside the loop? // Because if we don't check it here, the `isWhitespace()` call in // the loop head will read off the end of the input string.
CHECK_END_OF_INPUT
if (errors.hasSyntaxError() || U_FAILURE(status)) { break;
}
}
}
void Parser::errorPattern(UErrorCode& status) {
errors.addSyntaxError(status); // Set to empty pattern
Pattern::Builder result = Pattern::Builder(status);
CHECK_ERROR(status);
// If still in bounds, then add the remaining input as a single text part // to the pattern /* TODO:thisbehaviorisn'tdocumentedinthespec,butitcomesfrom https://github.com/messageformat/messageformat/blob/e0087bff312d759b67a9129eac135d318a1f0ce7/packages/mf2-messageformat/src/__fixtures/test-messages.json#L236 andapendingpullrequesthttps://github.com/unicode-org/message-format-wg/pull/462 will clarify whetherthisistheintentbehindthespec
*/
UnicodeString partStr(LEFT_CURLY_BRACE); while (inBounds()) {
partStr += peek();
next();
} // Add curly braces around the entire output (same comment as above)
partStr += RIGHT_CURLY_BRACE;
result.add(std::move(partStr), status);
dataModel.setPattern(result.build(status));
}
// Out-of-input is a syntax warning if (!inBounds()) {
errorPattern(status); return;
}
// Body must be either a pattern or selectors switch (peek()) { case LEFT_CURLY_BRACE: { // Pattern
dataModel.setPattern(parseQuotedPattern(status)); break;
} case ID_MATCH[0]: { // Selectors
parseSelectors(status); return;
} default: {
ERROR(status);
errorPattern(status); return;
}
}
}
// ------------------------------------- // Parses the source pattern.
bool complex = false; // First, "look ahead" to determine if this is a simple or complex // message. To do that, check the first non-whitespace character. while (inBounds(index) && (isWhitespace(peek()) || isBidiControl(peek()))) {
next();
}
// Message can be empty, so we need to only look ahead // if we know it's non-empty if (inBounds()) { if (peek() == PERIOD
|| (inBounds(1)
&& peek() == LEFT_CURLY_BRACE
&& peek(1) == LEFT_CURLY_BRACE)) {
complex = true;
}
} // Reset index
index = 0;
// Message can be empty, so we need to only look ahead // if we know it's non-empty if (complex) {
parseOptionalWhitespace();
parseDeclarations(status);
parseBody(status);
parseOptionalWhitespace();
} else { // Simple message // For normalization, quote the pattern
normalizedInput += LEFT_CURLY_BRACE;
normalizedInput += LEFT_CURLY_BRACE;
dataModel.setPattern(parseSimpleMessage(status));
normalizedInput += RIGHT_CURLY_BRACE;
normalizedInput += RIGHT_CURLY_BRACE;
}
CHECK_ERROR(status);
// There are no errors; finally, check that the entire input was consumed if (!allConsumed()) {
ERROR(status);
}
// Finally, copy the relevant fields of the internal `MessageParseError` // into the `UParseError` argument
translateParseError(parseError, parseErrorResult);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.