ICU-22902 Remove support for Unsupported, Private & Reserved constructs

Matching PR  in the message-format-wg repo.

Also move spec tests for unsupported statements and expressions into new files
to serve as syntax error tests.
This commit is contained in:
Tim Chevalier 2024-09-19 15:22:14 -07:00
parent 5f9f8b2053
commit 2f348f4c7a
20 changed files with 109 additions and 1577 deletions

View file

@ -597,15 +597,13 @@ typedef enum UErrorCode {
U_MF_MISSING_SELECTOR_ANNOTATION_ERROR, /**< A selector expression evaluates to an unannotated operand. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_DUPLICATE_DECLARATION_ERROR, /**< The same variable is declared in more than one .local or .input declaration. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_OPERAND_MISMATCH_ERROR, /**< An operand provided to a function does not have the required form for that function @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_UNSUPPORTED_STATEMENT_ERROR, /**< A message includes a reserved statement. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_UNSUPPORTED_EXPRESSION_ERROR, /**< A message includes syntax reserved for future standardization or private implementation use. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_DUPLICATE_VARIANT_ERROR, /**< A message includes a variant with the same key list as another variant. @internal ICU 76 technology preview @deprecated This API is for technology preview only. */
#ifndef U_HIDE_DEPRECATED_API
/**
* One more than the highest normal formatting API error code.
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
*/
U_FMT_PARSE_ERROR_LIMIT = 0x10122,
U_FMT_PARSE_ERROR_LIMIT = 0x10120,
#endif // U_HIDE_DEPRECATED_API
/*

View file

@ -140,8 +140,6 @@ _uFmtErrorName[U_FMT_PARSE_ERROR_LIMIT - U_FMT_PARSE_ERROR_START] = {
"U_MF_MISSING_SELECTOR_ANNOTATION_ERROR",
"U_MF_DUPLICATE_DECLARATION_ERROR",
"U_MF_OPERAND_MISMATCH_ERROR",
"U_MF_UNSUPPORTED_STATEMENT_ERROR",
"U_MF_UNSUPPORTED_EXPRESSION_ERROR",
"U_MF_DUPLICATE_VARIANT_ERROR"
};

View file

@ -241,24 +241,6 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
return FormattedPlaceholder(fallback);
}
// Per https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution
static UnicodeString reservedFallback (const Expression& e) {
UErrorCode localErrorCode = U_ZERO_ERROR;
const Operator* rator = e.getOperator(localErrorCode);
U_ASSERT(U_SUCCESS(localErrorCode));
const Reserved& r = rator->asReserved();
// An empty Reserved isn't representable in the syntax
U_ASSERT(r.numParts() > 0);
const UnicodeString& contents = r.getPart(0).unquoted();
// Parts should never be empty
U_ASSERT(contents.length() > 0);
// Return first character of string
return UnicodeString(contents, 0, 1);
}
// Formats an expression using `globalEnv` for the values of variables
[[nodiscard]] FormattedPlaceholder MessageFormatter::formatExpression(const Environment& globalEnv,
const Expression& expr,
@ -268,12 +250,6 @@ static UnicodeString reservedFallback (const Expression& e) {
return {};
}
// Formatting error
if (expr.isReserved()) {
context.getErrors().setReservedError(status);
return FormattedPlaceholder(reservedFallback(expr));
}
const Operand& rand = expr.getOperand();
// Format the operand (formatOperand handles the case of a null operand)
FormattedPlaceholder randVal = formatOperand(globalEnv, rand, context, status);
@ -675,12 +651,6 @@ ResolvedSelector MessageFormatter::resolveVariables(const Environment& env,
return {};
}
// A `reserved` is an error
if (expr.isReserved()) {
context.getErrors().setReservedError(status);
return ResolvedSelector(FormattedPlaceholder(reservedFallback(expr)));
}
// Function call -- resolve the operand and options
if (expr.isFunctionCall()) {
const Operator* rator = expr.getOperator(status);

View file

@ -136,9 +136,7 @@ void Checker::addFreeVars(TypeEnvironment& t, const OptionMap& opts, UErrorCode&
void Checker::addFreeVars(TypeEnvironment& t, const Operator& rator, UErrorCode& status) {
CHECK_ERROR(status);
if (!rator.isReserved()) {
addFreeVars(t, rator.getOptionsInternal(), status);
}
addFreeVars(t, rator.getOptionsInternal(), status);
}
void Checker::addFreeVars(TypeEnvironment& t, const Expression& rhs, UErrorCode& status) {
@ -213,12 +211,10 @@ void Checker::requireAnnotated(const TypeEnvironment& t, const Expression& selec
if (selectorExpr.isFunctionCall()) {
return; // No error
}
if (!selectorExpr.isReserved()) {
const Operand& rand = selectorExpr.getOperand();
if (rand.isVariable()) {
if (t.get(rand.asVariable()) == TypeEnvironment::Type::Annotated) {
return; // No error
}
const Operand& rand = selectorExpr.getOperand();
if (rand.isVariable()) {
if (t.get(rand.asVariable()) == TypeEnvironment::Type::Annotated) {
return; // No error
}
}
// If this code is reached, an error was detected
@ -240,9 +236,6 @@ TypeEnvironment::Type typeOf(TypeEnvironment& t, const Expression& expr) {
if (expr.isFunctionCall()) {
return TypeEnvironment::Type::Annotated;
}
if (expr.isReserved()) {
return TypeEnvironment::Type::Unannotated;
}
const Operand& rand = expr.getOperand();
U_ASSERT(!rand.isNull());
if (rand.isLiteral()) {
@ -296,11 +289,6 @@ void Checker::checkDeclarations(TypeEnvironment& t, UErrorCode& status) {
// Next, extend the type environment with a binding from lhs to its type
t.extend(lhs, typeOf(t, rhs), status);
}
// Check for unsupported statements
if (dataModel.unsupportedStatementsLen > 0) {
errors.addError(StaticErrorType::UnsupportedStatementError, status);
}
}
void Checker::check(UErrorCode& status) {

View file

@ -199,77 +199,6 @@ const Literal& Key::asLiteral() const {
Key::~Key() {}
// ------------ Reserved
// Copy constructor
Reserved::Reserved(const Reserved& other) : len(other.len) {
U_ASSERT(!other.bogus);
UErrorCode localErrorCode = U_ZERO_ERROR;
if (len == 0) {
parts.adoptInstead(nullptr);
} else {
parts.adoptInstead(copyArray(other.parts.getAlias(), len, localErrorCode));
}
if (U_FAILURE(localErrorCode)) {
bogus = true;
}
}
Reserved& Reserved::operator=(Reserved other) noexcept {
swap(*this, other);
return *this;
}
Reserved::Reserved(const UVector& ps, UErrorCode& status) noexcept : len(ps.size()) {
if (U_FAILURE(status)) {
return;
}
parts = LocalArray<Literal>(copyVectorToArray<Literal>(ps, status));
}
int32_t Reserved::numParts() const {
U_ASSERT(!bogus);
return len;
}
const Literal& Reserved::getPart(int32_t i) const {
U_ASSERT(!bogus);
U_ASSERT(i < numParts());
return parts[i];
}
Reserved::Builder::Builder(UErrorCode& status) {
parts = createUVector(status);
}
Reserved Reserved::Builder::build(UErrorCode& status) const noexcept {
if (U_FAILURE(status)) {
return {};
}
U_ASSERT(parts != nullptr);
return Reserved(*parts, status);
}
Reserved::Builder& Reserved::Builder::add(Literal&& part, UErrorCode& status) noexcept {
U_ASSERT(parts != nullptr);
if (U_SUCCESS(status)) {
Literal* l = create<Literal>(std::move(part), status);
parts->adoptElement(l, status);
}
return *this;
}
Reserved::Builder::~Builder() {
if (parts != nullptr) {
delete parts;
}
}
Reserved::~Reserved() {
len = 0;
}
//------------------------ Operator
OptionMap::OptionMap(const UVector& opts, UErrorCode& status) : len(opts.size()) {
@ -379,14 +308,8 @@ OptionMap::Builder::~Builder() {
}
}
const Reserved& Operator::asReserved() const {
U_ASSERT(isReserved());
return *(std::get_if<Reserved>(&contents));
}
const OptionMap& Operator::getOptionsInternal() const {
U_ASSERT(!isReserved());
return std::get_if<Callable>(&contents)->getOptions();
return options;
}
Option::Option(const Option& other): name(other.name), rand(other.rand) {}
@ -400,62 +323,28 @@ Option::~Option() {}
Operator::Builder::Builder(UErrorCode& status) : options(OptionMap::Builder(status)) {}
Operator::Builder& Operator::Builder::setReserved(Reserved&& reserved) {
isReservedSequence = true;
hasFunctionName = false;
hasOptions = false;
asReserved = std::move(reserved);
return *this;
}
Operator::Builder& Operator::Builder::setFunctionName(FunctionName&& func) {
isReservedSequence = false;
hasFunctionName = true;
functionName = std::move(func);
return *this;
}
const FunctionName& Operator::getFunctionName() const {
U_ASSERT(!isReserved());
return std::get_if<Callable>(&contents)->getName();
return name;
}
Operator::Builder& Operator::Builder::addOption(const UnicodeString &key, Operand&& value, UErrorCode& errorCode) noexcept {
THIS_ON_ERROR(errorCode);
isReservedSequence = false;
hasOptions = true;
options.add(Option(key, std::move(value)), errorCode);
return *this;
}
Operator Operator::Builder::build(UErrorCode& errorCode) {
Operator result;
if (U_FAILURE(errorCode)) {
return result;
}
// Must be either reserved or function, not both; enforced by methods
if (isReservedSequence) {
// Methods enforce that the function name and options are unset
// if `setReserved()` is called, so if they were valid, that
// would indicate a bug.
U_ASSERT(!hasOptions && !hasFunctionName);
result = Operator(asReserved);
} else {
if (!hasFunctionName) {
// Neither function name nor reserved was set
// There is no default, so this case could occur if the
// caller creates a builder and doesn't make any calls
// before calling build().
errorCode = U_INVALID_STATE_ERROR;
return result;
}
result = Operator(functionName, options.build(errorCode));
}
return result;
return Operator(functionName, options.build(errorCode));
}
Operator::Operator(const Operator& other) noexcept : contents(other.contents) {}
Operator::Operator(const Operator& other) noexcept
: name(other.name), options(other.options) {}
Operator& Operator::operator=(Operator other) noexcept {
swap(*this, other);
@ -463,23 +352,13 @@ Operator& Operator::operator=(Operator other) noexcept {
}
// Function call
Operator::Operator(const FunctionName& f, const UVector& optsVector, UErrorCode& status) : contents(Callable(f, OptionMap(optsVector, status))) {}
Operator::Operator(const FunctionName& f, const OptionMap& opts) : contents(Callable(f, opts)) {}
Operator::Operator(const FunctionName& f, const OptionMap& opts) : name(f), options(opts) {}
Operator::Builder::~Builder() {}
Operator::~Operator() {}
Callable& Callable::operator=(Callable other) noexcept {
swap(*this, other);
return *this;
}
Callable::Callable(const Callable& other) : name(other.name), options(other.options) {}
Callable::~Callable() {}
// ------------ Markup
Markup::Builder::Builder(UErrorCode& status)
@ -538,19 +417,14 @@ UBool Expression::isStandaloneAnnotation() const {
// Returns true for function calls with operands as well as
// standalone annotations.
// Reserved sequences are not function calls
UBool Expression::isFunctionCall() const {
return (rator.has_value() && !rator->isReserved());
}
UBool Expression::isReserved() const {
return (rator.has_value() && rator->isReserved());
return rator.has_value();
}
const Operator* Expression::getOperator(UErrorCode& status) const {
NULL_ON_ERROR(status);
if (!(isReserved() || isFunctionCall())) {
if (!isFunctionCall()) {
status = U_INVALID_STATE_ERROR;
return nullptr;
}
@ -617,92 +491,6 @@ Expression::Builder::~Builder() {}
Expression::~Expression() {}
// ----------- UnsupportedStatement
UnsupportedStatement::Builder::Builder(UErrorCode& status) {
expressions = createUVector(status);
}
UnsupportedStatement::Builder& UnsupportedStatement::Builder::setKeyword(const UnicodeString& k) {
keyword = k;
return *this;
}
UnsupportedStatement::Builder& UnsupportedStatement::Builder::setBody(Reserved&& r) {
body.emplace(r);
return *this;
}
UnsupportedStatement::Builder& UnsupportedStatement::Builder::addExpression(Expression&& e, UErrorCode& status) {
U_ASSERT(expressions != nullptr);
if (U_SUCCESS(status)) {
Expression* expr = create<Expression>(std::move(e), status);
expressions->adoptElement(expr, status);
}
return *this;
}
UnsupportedStatement UnsupportedStatement::Builder::build(UErrorCode& status) const {
if (U_SUCCESS(status)) {
U_ASSERT(expressions != nullptr);
if (keyword.length() <= 0) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else if (expressions->size() < 1) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
return UnsupportedStatement(keyword, body, *expressions, status);
}
}
return {};
}
const Reserved* UnsupportedStatement::getBody(UErrorCode& errorCode) const {
if (U_SUCCESS(errorCode)) {
if (body.has_value()) {
return &(*body);
}
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
}
return nullptr;
}
UnsupportedStatement::UnsupportedStatement(const UnicodeString& k,
const std::optional<Reserved>& r,
const UVector& es,
UErrorCode& status)
: keyword(k), body(r), expressionsLen(es.size()) {
CHECK_ERROR(status);
U_ASSERT(expressionsLen >= 1);
Expression* result = copyVectorToArray<Expression>(es, status);
CHECK_ERROR(status);
expressions.adoptInstead(result);
}
UnsupportedStatement::UnsupportedStatement(const UnsupportedStatement& other) {
keyword = other.keyword;
body = other.body;
expressionsLen = other.expressionsLen;
U_ASSERT(expressionsLen > 0);
UErrorCode localErrorCode = U_ZERO_ERROR;
expressions.adoptInstead(copyArray(other.expressions.getAlias(), expressionsLen, localErrorCode));
if (U_FAILURE(localErrorCode)) {
expressionsLen = 0;
}
}
UnsupportedStatement& UnsupportedStatement::operator=(UnsupportedStatement other) noexcept {
swap(*this, other);
return *this;
}
UnsupportedStatement::Builder::~Builder() {
if (expressions != nullptr) {
delete expressions;
}
}
UnsupportedStatement::~UnsupportedStatement() {}
// ----------- PatternPart
// PatternPart needs a copy constructor in order to make Pattern deeply copyable
@ -844,7 +632,7 @@ const Expression& Binding::getValue() const {
if (hasOperator) {
rator = b.getValue().getOperator(errorCode);
U_ASSERT(U_SUCCESS(errorCode));
b.annotation = &rator->contents;
b.annotation = rator;
} else {
b.annotation = nullptr;
}
@ -856,18 +644,17 @@ const Expression& Binding::getValue() const {
const OptionMap& Binding::getOptionsInternal() const {
U_ASSERT(annotation != nullptr);
U_ASSERT(std::holds_alternative<Callable>(*annotation));
return std::get_if<Callable>(annotation)->getOptions();
return annotation->getOptionsInternal();
}
void Binding::updateAnnotation() {
UErrorCode localErrorCode = U_ZERO_ERROR;
const Operator* rator = expr.getOperator(localErrorCode);
if (U_FAILURE(localErrorCode) || rator->isReserved()) {
if (U_FAILURE(localErrorCode)) {
return;
}
U_ASSERT(U_SUCCESS(localErrorCode) && !rator->isReserved());
annotation = &rator->contents;
U_ASSERT(U_SUCCESS(localErrorCode));
annotation = rator;
}
Binding::Binding(const Binding& other) : var(other.var), expr(other.expr), local(other.local) {
@ -949,17 +736,8 @@ const Variant* MFDataModel::getVariantsInternal() const {
return std::get_if<Matcher>(&body)->variants.getAlias();
}
// Returns nullptr if no unsupported statements
const UnsupportedStatement* MFDataModel::getUnsupportedStatementsInternal() const {
U_ASSERT(!bogus);
U_ASSERT(unsupportedStatementsLen == 0 || unsupportedStatements != nullptr);
return unsupportedStatements.getAlias();
}
MFDataModel::Builder::Builder(UErrorCode& status) {
bindings = createUVector(status);
unsupportedStatements = createUVector(status);
}
// Invalidate pattern and create selectors/variants if necessary
@ -1008,14 +786,6 @@ MFDataModel::Builder& MFDataModel::Builder::addBinding(Binding&& b, UErrorCode&
return *this;
}
MFDataModel::Builder& MFDataModel::Builder::addUnsupportedStatement(UnsupportedStatement&& s, UErrorCode& status) {
if (U_SUCCESS(status)) {
U_ASSERT(unsupportedStatements != nullptr);
unsupportedStatements->adoptElement(create<UnsupportedStatement>(std::move(s), status), status);
}
return *this;
}
/*
selector must be non-null
*/
@ -1077,10 +847,6 @@ MFDataModel::MFDataModel(const MFDataModel& other) : body(Pattern()) {
if (bindingsLen > 0) {
bindings.adoptInstead(copyArray(other.bindings.getAlias(), bindingsLen, localErrorCode));
}
unsupportedStatementsLen = other.unsupportedStatementsLen;
if (unsupportedStatementsLen > 0) {
unsupportedStatements.adoptInstead(copyArray(other.unsupportedStatements.getAlias(), unsupportedStatementsLen, localErrorCode));
}
if (U_FAILURE(localErrorCode)) {
bogus = true;
}
@ -1110,11 +876,6 @@ MFDataModel::MFDataModel(const MFDataModel::Builder& builder, UErrorCode& errorC
if (bindingsLen > 0) {
bindings.adoptInstead(copyVectorToArray<Binding>(*builder.bindings, errorCode));
}
unsupportedStatementsLen = builder.unsupportedStatements->size();
if (unsupportedStatementsLen > 0) {
unsupportedStatements.adoptInstead(copyVectorToArray<UnsupportedStatement>(*builder.unsupportedStatements,
errorCode));
}
if (U_FAILURE(errorCode)) {
bogus = true;
}
@ -1149,9 +910,6 @@ MFDataModel::Builder::~Builder() {
if (bindings != nullptr) {
delete bindings;
}
if (unsupportedStatements != nullptr) {
delete unsupportedStatements;
}
}
} // namespace message2

View file

@ -19,10 +19,6 @@ namespace message2 {
// Errors
// -----------
void DynamicErrors::setReservedError(UErrorCode& status) {
addError(DynamicError(DynamicErrorType::ReservedError), status);
}
void DynamicErrors::setFormattingError(const FunctionName& formatterName, UErrorCode& status) {
addError(DynamicError(DynamicErrorType::FormattingError, formatterName), status);
}
@ -143,10 +139,6 @@ namespace message2 {
status = U_MF_OPERAND_MISMATCH_ERROR;
break;
}
case DynamicErrorType::ReservedError: {
status = U_MF_UNSUPPORTED_EXPRESSION_ERROR;
break;
}
case DynamicErrorType::SelectorError: {
status = U_MF_SELECTOR_ERROR;
break;
@ -196,10 +188,6 @@ namespace message2 {
dataModelError = true;
break;
}
case StaticErrorType::UnsupportedStatementError: {
dataModelError = true;
break;
}
}
syntaxAndDataModelErrors->adoptElement(errorP, status);
}
@ -228,10 +216,6 @@ namespace message2 {
resolutionAndFormattingErrors->adoptElement(errorP, status);
break;
}
case DynamicErrorType::ReservedError: {
resolutionAndFormattingErrors->adoptElement(errorP, status);
break;
}
case DynamicErrorType::SelectorError: {
selectorError = true;
resolutionAndFormattingErrors->adoptElement(errorP, status);
@ -279,9 +263,6 @@ namespace message2 {
status = U_MF_SYNTAX_ERROR;
break;
}
case StaticErrorType::UnsupportedStatementError: {
status = U_MF_UNSUPPORTED_STATEMENT_ERROR;
}
}
}
}

View file

@ -58,7 +58,6 @@ namespace message2 {
MissingSelectorAnnotation,
NonexhaustivePattern,
SyntaxError,
UnsupportedStatementError,
VariantKeyMismatchError
};
@ -66,7 +65,6 @@ namespace message2 {
UnresolvedVariable,
FormattingError,
OperandMismatchError,
ReservedError,
SelectorError,
UnknownFunction,
};
@ -123,7 +121,6 @@ namespace message2 {
int32_t count() const;
void setSelectorError(const FunctionName&, UErrorCode&);
void setReservedError(UErrorCode&);
void setUnresolvedVariable(const VariableName&, UErrorCode&);
void setUnknownFunction(const FunctionName&, UErrorCode&);
void setFormattingError(const FunctionName&, UErrorCode&);

View file

@ -104,8 +104,6 @@ static bool inRange(UChar32 c, UChar32 first, UChar32 last) {
`isContentChar()` : `content-char`
`isTextChar()` : `text-char`
`isReservedStart()` : `reserved-start`
`isReservedChar()` : `reserved-char`
`isAlpha()` : `ALPHA`
`isDigit()` : `DIGIT`
`isNameStart()` : `name-start`
@ -149,35 +147,6 @@ static bool isTextChar(UChar32 c) {
|| c == PIPE;
}
// Note: this doesn't distinguish between private-use
// and reserved, since the data model doesn't
static bool isReservedStart(UChar32 c) {
switch (c) {
case BANG:
case PERCENT:
case ASTERISK:
case PLUS:
case LESS_THAN:
case GREATER_THAN:
case QUESTION:
case TILDE:
// Private-use
case CARET:
case AMPERSAND:
return true;
default:
return false;
}
}
static bool isReservedChar(UChar32 c) {
return isContentChar(c) || c == PERIOD;
}
static bool isReservedBodyStart(UChar32 c) {
return isReservedChar(c) || c == BACKSLASH || c == PIPE;
}
static bool isAlpha(UChar32 c) { return inRange(c, 0x0041, 0x005A) || inRange(c, 0x0061, 0x007A); }
static bool isDigit(UChar32 c) { return inRange(c, 0x0030, 0x0039); }
@ -230,24 +199,7 @@ static bool isFunctionStart(UChar32 c) {
// Returns true iff `c` can begin an `annotation` nonterminal
static bool isAnnotationStart(UChar32 c) {
return isFunctionStart(c) || isReservedStart(c);
}
// Returns true iff `c` can begin either a `reserved-char` or `reserved-escape`
// literal
static bool reservedChunkFollows(UChar32 c) {
switch(c) {
// reserved-escape
case BACKSLASH:
// literal
case PIPE: {
return true;
}
default: {
// reserved-char
return (isReservedChar(c));
}
}
return isFunctionStart(c);
}
// Returns true iff `c` can begin a `literal` nonterminal
@ -1121,189 +1073,7 @@ Arbitrary lookahead is required to parse attribute lists, similarly to option li
}
/*
Consumes a non-empty sequence of reserved-chars, reserved-escapes, and
literals (as in 1*(reserved-char / reserved-escape / literal) in the `reserved-body` rule)
Appends it to `str`
*/
void Parser::parseReservedChunk(Reserved::Builder& result, UErrorCode& status) {
CHECK_ERROR(status);
bool empty = true;
UnicodeString chunk;
while(reservedChunkFollows(peek())) {
empty = false;
// reserved-char
if (isReservedChar(peek())) {
chunk += peek();
normalizedInput += peek();
// consume the char
next();
// Restore precondition
CHECK_BOUNDS(status);
continue;
}
if (chunk.length() > 0) {
result.add(Literal(false, chunk), status);
chunk.setTo(u"", 0);
}
if (peek() == BACKSLASH) {
// reserved-escape
result.add(Literal(false, parseEscapeSequence(status)), status);
chunk.setTo(u"", 0);
} else if (peek() == PIPE || isUnquotedStart(peek())) {
result.add(parseLiteral(status), status);
} else {
// The reserved chunk ends here
break;
}
CHECK_ERROR(status); // Avoid looping infinitely
}
// Add the last chunk if necessary
if (chunk.length() > 0) {
result.add(Literal(false, chunk), status);
}
if (empty) {
ERROR(status);
}
}
/*
Consume a `reserved-start` character followed by a possibly-empty sequence
of non-empty sequences of reserved characters, separated by whitespace.
Matches the `reserved` nonterminal in the grammar
*/
Reserved Parser::parseReserved(UErrorCode& status) {
Reserved::Builder builder(status);
if (U_FAILURE(status)) {
return {};
}
U_ASSERT(inBounds());
// Require a `reservedStart` character
if (!isReservedStart(peek())) {
ERROR(status);
return Reserved();
}
// Add the start char as a separate text chunk
UnicodeString firstCharString(peek());
builder.add(Literal(false, firstCharString), status);
if (U_FAILURE(status)) {
return {};
}
// Consume reservedStart
normalizedInput += peek();
next();
return parseReservedBody(builder, status);
}
Reserved Parser::parseReservedBody(Reserved::Builder& builder, UErrorCode& status) {
if (U_FAILURE(status)) {
return {};
}
/*
Arbitrary lookahead is required to parse a `reserved`, for similar reasons
to why it's required for parsing function annotations.
In the grammar:
annotation = (function *(s option)) / reserved
expression = "{" [s] (((literal / variable) [s annotation]) / annotation) [s] "}"
reserved = reserved-start reserved-body
reserved-body = *( [s] 1*(reserved-char / reserved-escape / literal))
When reading a whitespace character, it's ambiguous whether it's the optional
whitespace in this rule, or the optional whitespace that precedes a '}' in an
expression.
The ambiguity is resolved using the same grammar refactoring as shown in
the comment in `parseOptions()`.
*/
// Consume reserved characters / literals / reserved escapes
// until a character that can't be in a `reserved-body` is seen
while (true) {
/*
First, if there is whitespace, it means either a chunk follows it,
or this is the trailing whitespace before the '}' that terminates an
expression.
Next, if the next character can start a reserved-char, reserved-escape,
or literal, then parse a "chunk" of reserved things.
In any other case, we exit successfully, since per the refactored
grammar rule:
annotation = (function *(s option) [s]) / (reserved [s])
it's valid to consume whitespace after a `reserved`.
(`parseExpression()` is responsible for checking that the next
character is in fact a '}'.)
*/
if (!inBounds()) {
break;
}
int32_t numWhitespaceChars = 0;
int32_t savedIndex = index;
if (isWhitespace(peek())) {
parseOptionalWhitespace(status);
numWhitespaceChars = index - savedIndex;
// Restore precondition
if (!inBounds()) {
break;
}
}
if (reservedChunkFollows(peek())) {
parseReservedChunk(builder, status);
// Avoid looping infinitely
if (U_FAILURE(status) || !inBounds()) {
break;
}
} else {
if (numWhitespaceChars > 0) {
if (peek() == LEFT_CURLY_BRACE) {
// Resolve even more ambiguity (space preceding another piece of
// a `reserved-body`, vs. space preceding an expression in `reserved-statement`
// "Backtrack"
index -= numWhitespaceChars;
break;
}
if (peek() == RIGHT_CURLY_BRACE) {
// Not an error: just means there's no trailing whitespace
// after this `reserved`
break;
}
if (peek() == AT) {
// Not an error, but we have to "backtrack" due to the ambiguity
// between an `s` preceding another reserved chunk
// and an `s` preceding an attribute list
index -= numWhitespaceChars;
break;
}
// Error: if there's whitespace, it must either be followed
// by a non-empty sequence or by '}'
ERROR(status);
break;
}
// If there was no whitespace, it's not an error,
// just the end of the reserved string
break;
}
}
return builder.build(status);
}
/*
Consume a function call or reserved string, matching the `annotation`
Consume a function call, matching the `annotation`
nonterminal in the grammar
Returns an `Operator` representing this (a reserved is a parse error)
@ -1323,17 +1093,9 @@ Operator Parser::parseAnnotation(UErrorCode& status) {
// Consume the options (which may be empty)
parseOptions(addOptions, status);
} else {
// Must be reserved
// A reserved sequence is not a parse error, but might be a formatting error
Reserved rator = parseReserved(status);
ratorBuilder.setReserved(std::move(rator));
ERROR(status);
}
UErrorCode localStatus = U_ZERO_ERROR;
Operator result = ratorBuilder.build(localStatus);
// Either `setReserved` or `setFunctionName` was called,
// so there shouldn't be an error.
U_ASSERT(U_SUCCESS(localStatus));
return result;
return ratorBuilder.build(status);
}
/*
@ -1613,95 +1375,6 @@ void Parser::parseInputDeclaration(UErrorCode& status) {
}
}
/*
Parses a `reserved-statement` per the grammar
*/
void Parser::parseUnsupportedStatement(UErrorCode& status) {
U_ASSERT(inBounds() && peek() == PERIOD);
UnsupportedStatement::Builder builder(status);
CHECK_ERROR(status);
// Parse the keyword
UnicodeString keyword(PERIOD);
normalizedInput += UnicodeString(PERIOD);
next();
keyword += parseName(status);
builder.setKeyword(keyword);
// Parse the body, which is optional
// Lookahead is required to distinguish the `s` in reserved-body
// from the `s` in `[s] expression`
// Next character may be:
// * whitespace (followed by either a reserved-body start or
// a '{')
// * a '{'
CHECK_BOUNDS(status);
if (peek() != LEFT_CURLY_BRACE) {
if (!isWhitespace(peek())) {
ERROR(status);
return;
}
// Expect a reserved-body start
int32_t savedIndex = index;
parseRequiredWhitespace(status);
CHECK_BOUNDS(status);
if (isReservedBodyStart(peek())) {
// There is a reserved body
Reserved::Builder r(status);
builder.setBody(parseReservedBody(r, status));
} else {
// No body -- backtrack so we can parse 1*([s] expression)
index = savedIndex;
normalizedInput.truncate(normalizedInput.length() - 1);
}
// Otherwise, the next character must be a '{'
// to open the required expression (or optional whitespace)
if (peek() != LEFT_CURLY_BRACE && !isWhitespace(peek())) {
ERROR(status);
return;
}
}
// Finally, parse the expressions
// Need to look ahead to disambiguate a '{' beginning
// an expression from one beginning with a quoted pattern
int32_t expressionCount = 0;
while (peek() == LEFT_CURLY_BRACE || isWhitespace(peek())) {
parseOptionalWhitespace(status);
bool nextIsLbrace = peek() == LEFT_CURLY_BRACE;
bool nextIsQuotedPattern = nextIsLbrace && inBounds(1)
&& peek(1) == LEFT_CURLY_BRACE;
if (nextIsQuotedPattern) {
break;
}
builder.addExpression(parseExpression(status), status);
expressionCount++;
}
if (expressionCount <= 0) {
// At least one expression is required
ERROR(status);
return;
}
dataModel.addUnsupportedStatement(builder.build(status), status);
}
// Terrible hack to get around the ambiguity between unsupported keywords
// and supported keywords
bool Parser::nextIs(const std::u16string_view &keyword) const {
for (int32_t i = 0; i < static_cast<int32_t>(keyword.length()); i++) {
if (!inBounds(i) || peek(i) != keyword[i]) {
return false;
}
}
return true;
}
/*
Consume a possibly-empty sequence of declarations separated by whitespace;
each declaration matches the `declaration` nonterminal in the grammar
@ -1715,11 +1388,7 @@ void Parser::parseDeclarations(UErrorCode& status) {
while (peek() == PERIOD) {
CHECK_BOUNDS_1(status);
// Check for unsupported statements first
// Lookahead is needed to disambiguate keyword from known keywords
if (!nextIs(ID_MATCH) && !nextIs(ID_LOCAL) && !nextIs(ID_INPUT)) {
parseUnsupportedStatement(status);
} else if (peek(1) == ID_LOCAL[1]) {
if (peek(1) == ID_LOCAL[1]) {
parseLocalDeclaration(status);
} else if (peek(1) == ID_INPUT[1]) {
parseInputDeclaration(status);

View file

@ -127,9 +127,6 @@ namespace message2 {
void parseOption(OptionAdder<T>&, UErrorCode&);
template<class T>
void parseOptions(OptionAdder<T>&, UErrorCode&);
void parseReservedChunk(Reserved::Builder&, UErrorCode&);
Reserved parseReserved(UErrorCode&);
Reserved parseReservedBody(Reserved::Builder&, UErrorCode&);
Operator parseAnnotation(UErrorCode&);
void parseLiteralOrVariableWithAnnotation(bool, Expression::Builder&, UErrorCode&);
Markup parseMarkup(UErrorCode&);

View file

@ -134,36 +134,10 @@ void Serializer::emitAttributes(const OptionMap& attributes) {
}
}
void Serializer::emit(const Reserved& reserved) {
// Re-escape '\' / '{' / '|' / '}'
for (int32_t i = 0; i < reserved.numParts(); i++) {
const Literal& l = reserved.getPart(i);
if (l.isQuoted()) {
emit(l);
} else {
const UnicodeString& s = l.unquoted();
for (int32_t j = 0; j < s.length(); j++) {
switch(s[j]) {
case LEFT_CURLY_BRACE:
case PIPE:
case RIGHT_CURLY_BRACE:
case BACKSLASH: {
emit(BACKSLASH);
break;
}
default:
break;
}
emit(s[j]);
}
}
}
}
void Serializer::emit(const Expression& expr) {
emit(LEFT_CURLY_BRACE);
if (!expr.isReserved() && !expr.isFunctionCall()) {
if (!expr.isFunctionCall()) {
// Literal or variable, no annotation
emit(expr.getOperand());
} else {
@ -176,17 +150,12 @@ void Serializer::emit(const Reserved& reserved) {
UErrorCode localStatus = U_ZERO_ERROR;
const Operator* rator = expr.getOperator(localStatus);
U_ASSERT(U_SUCCESS(localStatus));
if (rator->isReserved()) {
const Reserved& reserved = rator->asReserved();
emit(reserved);
} else {
emit(COLON);
emit(rator->getFunctionName());
// No whitespace after function name, in case it has
// no options. (when there are options, emit(OptionMap) will
// emit the leading whitespace)
emit(rator->getOptionsInternal());
}
emit(COLON);
emit(rator->getFunctionName());
// No whitespace after function name, in case it has
// no options. (when there are options, emit(OptionMap) will
// emit the leading whitespace)
emit(rator->getOptionsInternal());
}
emitAttributes(expr.getAttributesInternal());
emit(RIGHT_CURLY_BRACE);
@ -273,26 +242,6 @@ void Serializer::serializeDeclarations() {
}
}
void Serializer::serializeUnsupported() {
const UnsupportedStatement* statements = dataModel.getUnsupportedStatementsInternal();
U_ASSERT(dataModel.unsupportedStatementsLen == 0 || statements != nullptr);
for (int32_t i = 0; i < dataModel.unsupportedStatementsLen; i++) {
const UnsupportedStatement& s = statements[i];
emit(s.getKeyword());
UErrorCode localErrorCode = U_ZERO_ERROR;
const Reserved* r = s.getBody(localErrorCode);
if (U_SUCCESS(localErrorCode)) {
whitespace();
emit(*r);
}
const Expression* e = s.getExpressionsInternal();
for (int32_t j = 0; j < s.expressionsLen; j++) {
emit(e[j]);
}
}
}
void Serializer::serializeSelectors() {
U_ASSERT(!dataModel.hasPattern());
const Expression* selectors = dataModel.getSelectorsInternal();
@ -319,7 +268,6 @@ void Serializer::serializeVariants() {
// Main (public) serializer method
void Serializer::serialize() {
serializeDeclarations();
serializeUnsupported();
// Pattern message
if (dataModel.hasPattern()) {
emit(dataModel.getPattern());

View file

@ -44,14 +44,12 @@ namespace message2 {
void emit(const Key&);
void emit(const SelectorKeys&);
void emit(const Operand&);
void emit(const Reserved&);
void emit(const Expression&);
void emit(const PatternPart&);
void emit(const Pattern&);
void emit(const Variant*);
void emitAttributes(const OptionMap&);
void emit(const OptionMap&);
void serializeUnsupported();
void serializeDeclarations();
void serializeSelectors();
void serializeVariants();

View file

@ -62,163 +62,6 @@ namespace message2 {
class Literal;
class Operator;
/**
* The `Reserved` class represents a `reserved` annotation, as in the `reserved` nonterminal
* in the MessageFormat 2 grammar or the `Reserved` interface
* defined in
* https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model.md#expressions
*
* `Reserved` is immutable, copyable and movable.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
class U_I18N_API Reserved : public UMemory {
public:
/**
* A `Reserved` is a sequence of literals.
*
* @return The number of literals.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
int32_t numParts() const;
/**
* Indexes into the sequence.
* Precondition: i < numParts()
*
* @param i Index of the part being accessed.
* @return A reference to he i'th literal in the sequence
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
const Literal& getPart(int32_t i) const;
/**
* The mutable `Reserved::Builder` class allows the reserved sequence to be
* constructed one part at a time.
*
* Builder is not copyable or movable.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
class U_I18N_API Builder : public UMemory {
private:
UVector* parts; // Not a LocalPointer for the same reason as in `SelectorKeys::Builder`
public:
/**
* Adds a single literal to the reserved sequence.
*
* @param part The literal to be added. Passed by move.
* @param status Input/output error code
* @return A reference to the builder.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder& add(Literal&& part, UErrorCode& status) noexcept;
/**
* Constructs a new immutable `Reserved` using the list of parts
* set with previous `add()` calls.
*
* The builder object (`this`) can still be used after calling `build()`.
*
* param status Input/output error code
* @return The new Reserved object
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Reserved build(UErrorCode& status) const noexcept;
/**
* Default constructor.
* Returns a builder with an empty Reserved sequence.
*
* param status Input/output error code
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder(UErrorCode& status);
/**
* Destructor.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
virtual ~Builder();
Builder(const Builder&) = delete;
Builder& operator=(const Builder&) = delete;
Builder(Builder&&) = delete;
Builder& operator=(Builder&&) = delete;
}; // class Reserved::Builder
/**
* Non-member swap function.
* @param r1 will get r2's contents
* @param r2 will get r1's contents
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
friend inline void swap(Reserved& r1, Reserved& r2) noexcept {
using std::swap;
swap(r1.bogus, r2.bogus);
swap(r1.parts, r2.parts);
swap(r1.len, r2.len);
}
/**
* Copy constructor.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Reserved(const Reserved& other);
/**
* Assignment operator
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Reserved& operator=(Reserved) noexcept;
/**
* Default constructor.
* Puts the Reserved into a valid but undefined state.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Reserved() { parts = LocalArray<Literal>(); }
/**
* Destructor.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
virtual ~Reserved();
private:
friend class Builder;
friend class Operator;
// True if a copy failed; this has to be distinguished
// from a valid `Reserved` with empty parts
bool bogus = false;
// Possibly-empty list of parts
// `literal` reserved as a quoted literal; `reserved-char` / `reserved-escape`
// strings represented as unquoted literals
/* const */ LocalArray<Literal> parts;
int32_t len = 0;
Reserved(const UVector& parts, UErrorCode& status) noexcept;
// Helper
static void initLiterals(Reserved&, const Reserved&);
};
/**
* The `Literal` class corresponds to the `literal` nonterminal in the MessageFormat 2 grammar,
* https://github.com/unicode-org/message-format-wg/blob/main/spec/message.abnf and the
@ -349,8 +192,6 @@ namespace message2 {
virtual ~Literal();
private:
friend class Reserved::Builder;
/* const */ bool thisIsQuoted = false;
/* const */ UnicodeString contents;
};
@ -986,60 +827,22 @@ namespace message2 {
}; // class OptionMap
#endif
// Internal use only
#ifndef U_IN_DOXYGEN
class U_I18N_API Callable : public UObject {
public:
friend inline void swap(Callable& c1, Callable& c2) noexcept {
using std::swap;
swap(c1.name, c2.name);
swap(c1.options, c2.options);
}
const FunctionName& getName() const { return name; }
const OptionMap& getOptions() const { return options; }
Callable(const FunctionName& f, const OptionMap& opts) : name(f), options(opts) {}
Callable& operator=(Callable) noexcept;
Callable(const Callable&);
Callable() = default;
virtual ~Callable();
private:
/* const */ FunctionName name;
/* const */ OptionMap options;
};
#endif
} // namespace data_model
} // namespace message2
U_NAMESPACE_END
/// @cond DOXYGEN_IGNORE
// Export an explicit template instantiation of the std::variant that is used as a
// data member of various MFDataModel classes.
// (When building DLLs for Windows this is required.)
// (See measunit_impl.h, datefmt.h, collationiterator.h, erarules.h and others
// for similar examples.)
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
#if defined(U_REAL_MSVC) && defined(_MSVC_STL_VERSION)
template class U_I18N_API std::_Variant_storage_<false, icu::message2::data_model::Reserved,icu::message2::data_model::Callable>;
#endif
template class U_I18N_API std::variant<icu::message2::data_model::Reserved,icu::message2::data_model::Callable>;
#endif
/// @endcond
U_NAMESPACE_BEGIN
namespace message2 {
namespace data_model {
/**
* The `Operator` class corresponds to the `FunctionRef | Reserved` type in the
* The `Operator` class corresponds to the `FunctionRef` type in the
* `Expression` interface defined in
* https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model.md#patterns
*
* It represents the annotation that an expression can have: either a function name paired
* with a map from option names to operands (possibly empty),
* or a reserved sequence, which has no meaning and results in an error if the formatter
* is invoked.
* It represents the annotation that an expression can have: a function name paired
* with a map from option names to operands (possibly empty).
*
* `Operator` is immutable, copyable and movable.
*
@ -1048,18 +851,8 @@ namespace message2 {
*/
class U_I18N_API Operator : public UObject {
public:
/**
* Determines if this operator is a reserved annotation.
*
* @return true if and only if this operator represents a reserved sequence.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
UBool isReserved() const { return std::holds_alternative<Reserved>(contents); }
/**
* Accesses the function name.
* Precondition: !isReserved()
*
* @return The function name of this operator.
*
@ -1067,19 +860,8 @@ namespace message2 {
* @deprecated This API is for technology preview only.
*/
const FunctionName& getFunctionName() const;
/**
* Accesses the underlying reserved sequence.
* Precondition: isReserved()
*
* @return The reserved sequence represented by this operator.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
const Reserved& asReserved() const;
/**
* Accesses function options.
* Precondition: !isReserved()
*
* @return A vector of function options for this operator.
*
@ -1087,11 +869,7 @@ namespace message2 {
* @deprecated This API is for technology preview only.
*/
std::vector<Option> getOptions() const {
const Callable* f = std::get_if<Callable>(&contents);
// This case should never happen, as the precondition is !isReserved()
if (f == nullptr) { return {}; }
const OptionMap& opts = f->getOptions();
return opts.getOptions();
return options.getOptions();
}
/**
* The mutable `Operator::Builder` class allows the operator to be constructed
@ -1105,30 +883,12 @@ namespace message2 {
class U_I18N_API Builder : public UMemory {
private:
friend class Operator;
bool isReservedSequence = false;
bool hasFunctionName = false;
bool hasOptions = false;
Reserved asReserved;
FunctionName functionName;
OptionMap::Builder options;
public:
/**
* Sets this operator to be a reserved sequence.
* If a function name and/or options were previously set,
* clears them.
*
* @param reserved The reserved sequence to set as the contents of this Operator.
* (Passed by move.)
* @return A reference to the builder.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder& setReserved(Reserved&& reserved);
/**
* Sets this operator to be a function annotation and sets its name
* to `func`.
* If a reserved sequence was previously set, clears it.
*
* @param func The function name.
* @return A reference to the builder.
@ -1140,7 +900,6 @@ namespace message2 {
/**
* Sets this operator to be a function annotation and adds a
* single option.
* If a reserved sequence was previously set, clears it.
*
* @param key The name of the option.
* @param value The value (right-hand side) of the option.
@ -1152,10 +911,8 @@ namespace message2 {
*/
Builder& addOption(const UnicodeString &key, Operand&& value, UErrorCode& status) noexcept;
/**
* Constructs a new immutable `Operator` using the `reserved` annotation
* or the function name and options that were previously set.
* If neither `setReserved()` nor `setFunctionName()` was previously
* called, then `status` is set to U_INVALID_STATE_ERROR.
* Constructs a new immutable `Operator` using the
* function name and options that were previously set.
*
* The builder object (`this`) can still be used after calling `build()`.
*
@ -1171,7 +928,7 @@ namespace message2 {
Operator build(UErrorCode& status);
/**
* Default constructor.
* Returns a Builder with no function name or reserved sequence set.
* Returns a Builder with no function name or options set.
*
* @param status Input/output error code.
*
@ -1209,7 +966,8 @@ namespace message2 {
friend inline void swap(Operator& o1, Operator& o2) noexcept {
using std::swap;
swap(o1.contents, o2.contents);
swap(o1.name, o2.name);
swap(o1.options, o2.options);
}
/**
* Assignment operator.
@ -1225,7 +983,7 @@ namespace message2 {
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Operator() : contents(Reserved()) {}
Operator() {}
/**
* Destructor.
*
@ -1242,12 +1000,12 @@ namespace message2 {
// Function call constructor
Operator(const FunctionName& f, const UVector& options, UErrorCode&);
// Reserved sequence constructor
Operator(const Reserved& r) : contents(r) {}
const OptionMap& getOptionsInternal() const;
Operator(const FunctionName&, const OptionMap&);
/* const */ std::variant<Reserved, Callable> contents;
/* const */ FunctionName name;
/* const */ OptionMap options;
}; // class Operator
} // namespace data_model
} // namespace message2
@ -1262,7 +1020,6 @@ U_NAMESPACE_END
// for similar examples.)
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
template class U_I18N_API std::optional<icu::message2::data_model::Operator>;
template class U_I18N_API std::optional<icu::message2::data_model::Reserved>;
#endif
/// @endcond
@ -1520,8 +1277,7 @@ namespace message2 {
UBool isStandaloneAnnotation() const;
/**
* Checks if this expression has a function
* annotation (with or without an operand). A reserved
* sequence is not a function annotation.
* annotation (with or without an operand).
*
* @return True if and only if the expression has an annotation
* that is a function.
@ -1531,20 +1287,9 @@ namespace message2 {
*/
UBool isFunctionCall() const;
/**
* Returns true if and only if this expression is
* annotated with a reserved sequence.
*
* @return True if and only if the expression has an
* annotation that is a reserved sequence,
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
UBool isReserved() const;
/**
* Accesses the function or reserved sequence
* Accesses the function
* annotating this expression.
* If !(isFunctionCall() || isReserved()), sets
* If !(isFunctionCall()), sets
* `status` to U_INVALID_STATE_ERROR.
*
* @param status Input/output error code.
@ -1751,203 +1496,6 @@ template class U_I18N_API LocalArray<message2::data_model::Expression>;
namespace message2 {
namespace data_model {
/**
* The `UnsupportedStatement` class corresponds to the `reserved-statement` nonterminal in the MessageFormat 2
* grammar and the `unsupported-statement` type defined in:
* https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/message.json#L169
*
* It represents a keyword (string) together with an optional
* `Reserved` annotation and a non-empty list of expressions.
*
* `UnsupportedStatement` is immutable, copyable and movable.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
class U_I18N_API UnsupportedStatement : public UObject {
public:
/**
* Accesses the keyword of this statement.
*
* @return A reference to a string.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
const UnicodeString& getKeyword() const { return keyword; }
/**
* Accesses the `reserved-body` of this statement.
*
* @param status Input/output error code. Set to U_ILLEGAL_ARGUMENT_ERROR
* if this unsupported statement has no body.
* @return A non-owned pointer to a `Reserved` annotation,
* which is non-null if U_SUCCESS(status).
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
const Reserved* getBody(UErrorCode& status) const;
/**
* Accesses the expressions of this statement.
*
* @return A vector of Expressions.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
std::vector<Expression> getExpressions() const {
if (expressionsLen <= 0 || !expressions.isValid()) {
// This case should never happen, but we can't use an assertion here
return {};
}
return toStdVector<Expression>(expressions.getAlias(), expressionsLen);
}
/**
* The mutable `UnsupportedStatement::Builder` class allows the statement to be constructed
* incrementally.
*
* Builder is not copyable or movable.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
class U_I18N_API Builder : public UMemory {
private:
friend class UnsupportedStatement;
friend class message2::Parser;
UnicodeString keyword;
std::optional<Reserved> body;
UVector* expressions; // Vector of expressions;
// not a LocalPointer for
// the same reason as in `SelectorKeys::builder`
public:
/**
* Sets the keyword of this statement.
*
* @param k The keyword to set.
* @return A reference to the builder.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder& setKeyword(const UnicodeString& k);
/**
* Sets the body of this statement.
*
* @param r The `Reserved` annotation to set as the body. Passed by move.
* @return A reference to the builder.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder& setBody(Reserved&& r);
/**
* Adds an expression to this statement.
*
* @param e The expression to add. Passed by move.
* @param status Input/output error code.
* @return A reference to the builder.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder& addExpression(Expression&& e, UErrorCode& status);
/**
* Constructs a new immutable `UnsupportedStatement` using the keyword,
* body and (if applicable) expressions that were previously set.
* If `setKeyword()` was never called, then `status` is set to
* U_INVALID_STATE_ERROR. If `setBody()` was never called, the body is
* treated as absent (not an error). If `addExpression()` was not called
* at least once, then `status` is set to U_INVALID_STATE_ERROR.
*
* The builder object (`this`) can still be used after calling `build()`.
* @param status Input/output error code.
* @return The new UnsupportedStatement
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
UnsupportedStatement build(UErrorCode& status) const;
/**
* Default constructor.
* Returns a Builder with no keyword or body set.
*
* @param status Input/output error code.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder(UErrorCode& status);
/**
* Destructor.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
virtual ~Builder();
Builder(const Builder&) = delete;
Builder& operator=(const Builder&) = delete;
Builder(Builder&&) = delete;
Builder& operator=(Builder&&) = delete;
}; // class UnsupportedStatement::Builder
/**
* Non-member swap function.
* @param s1 will get s2's contents
* @param s2 will get s1's contents
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
friend inline void swap(UnsupportedStatement& s1, UnsupportedStatement& s2) noexcept {
using std::swap;
swap(s1.keyword, s2.keyword);
swap(s1.body, s2.body);
swap(s1.expressions, s2.expressions);
swap(s1.expressionsLen, s2.expressionsLen);
}
/**
* Copy constructor.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
UnsupportedStatement(const UnsupportedStatement& other);
/**
* Assignment operator.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
UnsupportedStatement& operator=(UnsupportedStatement) noexcept;
/**
* Default constructor.
* Puts the UnsupportedStatement into a valid but undefined state.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
UnsupportedStatement() : expressions(LocalArray<Expression>()) {}
/**
* Destructor.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
virtual ~UnsupportedStatement();
private:
friend class message2::Serializer;
/* const */ UnicodeString keyword;
/* const */ std::optional<Reserved> body;
/* const */ LocalArray<Expression> expressions;
/* const */ int32_t expressionsLen = 0;
const Expression* getExpressionsInternal() const { return expressions.getAlias(); }
UnsupportedStatement(const UnicodeString&, const std::optional<Reserved>&, const UVector&, UErrorCode&);
}; // class UnsupportedStatement
class Pattern;
@ -2110,8 +1658,6 @@ namespace message2 {
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
template class U_I18N_API LocalPointerBase<message2::data_model::PatternPart>;
template class U_I18N_API LocalArray<message2::data_model::PatternPart>;
template class U_I18N_API LocalPointerBase<message2::data_model::UnsupportedStatement>;
template class U_I18N_API LocalArray<message2::data_model::UnsupportedStatement>;
#endif
/// @endcond
@ -2599,7 +2145,7 @@ namespace message2 {
// If non-null, the referent is a member of `expr` so
// its lifetime is the same as the lifetime of the enclosing Binding
// (as long as there's no mutation)
const std::variant<Reserved, Callable>* annotation = nullptr;
const Operator* annotation = nullptr;
const OptionMap& getOptionsInternal() const;
@ -2808,21 +2354,6 @@ namespace message2 {
return toStdVector<Variant>(match->variants.getAlias(), match->numVariants);
return {};
}
/**
* Accesses the unsupported statements for this data model.
*
* @return A vector of unsupported statements.
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
std::vector<UnsupportedStatement> getUnsupportedStatements() const {
std::vector<UnsupportedStatement> result;
if (!bogus) {
return toStdVector<UnsupportedStatement>(unsupportedStatements.getAlias(), unsupportedStatementsLen);
}
return {};
}
/**
* Accesses the pattern (in a message without selectors).
* Returns a reference to an empty pattern if the message has selectors.
@ -2873,8 +2404,6 @@ namespace message2 {
swap(m1.body, m2.body);
swap(m1.bindings, m2.bindings);
swap(m1.bindingsLen, m2.bindingsLen);
swap(m1.unsupportedStatements, m2.unsupportedStatements);
swap(m1.unsupportedStatementsLen, m2.unsupportedStatementsLen);
}
/**
* Assignment operator
@ -2918,7 +2447,6 @@ namespace message2 {
UVector* selectors = nullptr;
UVector* variants = nullptr;
UVector* bindings = nullptr;
UVector* unsupportedStatements = nullptr;
public:
/**
* Adds a binding, There must not already be a binding
@ -2933,17 +2461,6 @@ namespace message2 {
* @deprecated This API is for technology preview only.
*/
Builder& addBinding(Binding&& b, UErrorCode& status);
/**
* Adds an unsupported statement.
*
* @param s The statement. Passed by move.
* @param status Input/output error code.
*
*
* @internal ICU 75 technology preview
* @deprecated This API is for technology preview only.
*/
Builder& addUnsupportedStatement(UnsupportedStatement&& s, UErrorCode& status);
/**
* Adds a selector expression. Copies `expression`.
* If a pattern was previously set, clears the pattern.
@ -3046,16 +2563,9 @@ namespace message2 {
/* const */ LocalArray<Binding> bindings;
int32_t bindingsLen = 0;
// Unsupported statements
// (Treated as a type of `declaration` in the data model spec;
// stored separately for convenience)
/* const */ LocalArray<UnsupportedStatement> unsupportedStatements;
int32_t unsupportedStatementsLen = 0;
const Binding* getLocalVariablesInternal() const;
const Expression* getSelectorsInternal() const;
const Variant* getVariantsInternal() const;
const UnsupportedStatement* getUnsupportedStatementsInternal() const;
int32_t numSelectors() const {
const Matcher* matcher = std::get_if<Matcher>(&body);

View file

@ -34,9 +34,6 @@ static UErrorCode getExpectedRuntimeErrorFromString(const std::string& errorName
if (errorName == "unresolved-variable") {
return U_MF_UNRESOLVED_VARIABLE_ERROR;
}
if (errorName == "unsupported-expression") {
return U_MF_UNSUPPORTED_EXPRESSION_ERROR;
}
if (errorName == "bad-operand") {
return U_MF_OPERAND_MISMATCH_ERROR;
}
@ -58,11 +55,8 @@ static UErrorCode getExpectedRuntimeErrorFromString(const std::string& errorName
if (errorName == "bad-selector") {
return U_MF_SELECTOR_ERROR;
}
if (errorName == "formatting-error") {
return U_MF_FORMATTING_ERROR;
}
// Arbitrary default
return U_MF_UNSUPPORTED_STATEMENT_ERROR;
return U_MF_FORMATTING_ERROR;
}
static UnicodeString u_str(std::string s) {
@ -295,17 +289,14 @@ void TestMessageFormat2::jsonTestsFromFiles(IcuTestErrorCode& errorCode) {
// Do spec tests for syntax errors
runTestsFromJsonFile(*this, "spec/syntax-errors.json", errorCode);
runTestsFromJsonFile(*this, "unsupported-expressions.json", errorCode);
runTestsFromJsonFile(*this, "unsupported-statements.json", errorCode);
runTestsFromJsonFile(*this, "syntax-errors-reserved.json", errorCode);
// Do tests for data model errors
runTestsFromJsonFile(*this, "spec/data-model-errors.json", errorCode);
runTestsFromJsonFile(*this, "more-data-model-errors.json", errorCode);
// Do tests for reserved statements/expressions
runTestsFromJsonFile(*this, "spec/unsupported-expressions.json", errorCode);
runTestsFromJsonFile(*this, "spec/unsupported-statements.json", errorCode);
// Uncomment when reserved syntax is removed
// runTestsFromJsonFile(*this, "syntax-errors-reserved.json", errorCode);
// Do valid spec tests
runTestsFromJsonFile(*this, "spec/syntax.json", errorCode);

View file

@ -42,6 +42,8 @@ public class CoreTest extends CoreTestFmwk {
"syntax-errors-end-of-input.json",
"syntax-errors-reserved.json",
"tricky-declarations.json",
"unsupported-expressions.json",
"unsupported-statements.json",
"valid-tests.json"};
@Test

View file

@ -102,34 +102,6 @@
}
]
},
{
"src": ".match {|horse| ^private}\n 1 {{The value is one.}} * {{The value is not one.}}",
"expErrors": [
{
"type": "missing-selector-annotation"
}
]
},
{
"src": ".match {$foo !select} |1| {{one}} * {{other}}",
"expErrors": [
{
"type": "missing-selector-annotation"
}
]
},
{
"src": ".match {$foo ^select} |1| {{one}} * {{other}}",
"expErrors": [
{
"type": "missing-selector-annotation"
}
]
},
{
"src": ".input {$foo} .match {$foo} one {{one}} * {{other}}",
"expErrors": [

View file

@ -9,13 +9,6 @@
{ "src": ".input {$x :number} {{{$x}}}", "exp": "{$x}", "expErrors": [{ "type": "unresolved-variable" }], "ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"},
{ "src": ".local $foo = {$bar} .match {$foo :number} one {{one}} * {{other}}", "exp": "other", "expErrors": [{ "type": "unresolved-variable" }], "ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"},
{ "src": ".local $bar = {$none :number} .match {$foo :string} one {{one}} * {{{$bar}}}", "exp": "{$none}", "expErrors": [{ "type": "unresolved-variable" }], "ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"},
{ "src": "The value is {horse :func}.", "exp": "The value is {|horse|}.", "expErrors": [{ "type": "unknown-function" }], "ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"},
{ "src": ".matc {-1} {{hello}}",
"expErrors": [{ "type": "unsupported-statement" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{ "src": ".m {-1} {{hello}}",
"expErrors": [{ "type": "unsupported-statement" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782" }
{ "src": "The value is {horse :func}.", "exp": "The value is {|horse|}.", "expErrors": [{ "type": "unknown-function" }], "ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"}
]
}

View file

@ -172,13 +172,6 @@
"src": ".local $x ={a}.input{$y}{{}}",
"exp": ""
},
{
"description": "message -> complex-message -> *(declaration [s]) complex-body -> declaration complex-body -> reserved-statement complex-body -> reserved-keyword expression -> \".\" name expression complex-body",
"src": ".n{a}{{}}",
"exp": "",
"expErrors": [ { "type": "unsupported-statement" } ],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "message -> complex-message -> complex-body -> matcher -> match-statement variant -> match selector key quoted-pattern -> \".match\" expression literal quoted-pattern",
"src": ".match{a :f}a{{}}*{{}}",
@ -298,20 +291,6 @@
"expErrors": [{ "type": "unknown-function" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... annotation -> private-use-annotation -> private-start",
"src": "{^}",
"exp": "{^}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... annotation -> reserved-annotation -> reserved-annotation-start",
"src": "{!}",
"exp": "{!}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "message -> simple-message -> simple-start pattern -> placeholder -> markup -> \"{\" s \"#\" identifier \"}\"",
"src": "{ #a}",
@ -507,132 +486,6 @@
"src": "{0E-1}",
"exp": "0E-1"
},
{
"description": "... reserved-statement -> reserved-keyword s reserved-body 1*([s] expression) -> reserved-keyword s reserved-body expression -> \".\" name s reserved-body-part expression -> \".\" name s reserved-char expression ...",
"src": ".n .{a}{{}}",
"exp": "",
"expErrors": [ { "type": "unsupported-statement" } ],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-statement -> reserved-keyword reserved-body 1*([s] expression) -> reserved-keyword s reserved-body s expression -> \".\" name s reserved-body-part expression -> \".\" name s reserved-char expression ...",
"src": ".n. {a}{{}}",
"exp": "",
"expErrors": [ { "type": "unsupported-statement" } ],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-statement -> reserved-keyword reserved-body 1*([s] expression) -> reserved-keyword reserved-body expression expression -> \".\" name reserved-body-part expression expression -> \".\" name s reserved-char expression expression ...",
"src": ".n.{a}{b}{{}}",
"exp": "",
"expErrors": [ { "type": "unsupported-statement" } ],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation -> reserved-annotation-start reserved-body -> \"!\" reserved-body-part -> \"!\" reserved-char ...",
"src": "{!.}",
"exp": "{!}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation -> reserved-annotation-start s reserved-body -> \"!\" s reserved-body-part -> \"!\" s reserved-char ...",
"src": "{! .}",
"exp": "{!}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{%}",
"exp": "{%}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{*}",
"exp": "{*}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{+}",
"exp": "{+}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{<}",
"exp": "{<}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{>}",
"exp": "{>}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{?}",
"exp": "{?}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation-start ...",
"src": "{~}",
"exp": "{~}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... private-use-annotation -> private-start reserved-body -> \"^\" reserved-body-part -> \"^\" reserved-char ...",
"src": "{^.}",
"exp": "{^}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... private-use-annotation -> private-start s reserved-body -> \"^\" s reserved-body-part -> \"^\" s reserved-char ...",
"src": "{^ .}",
"exp": "{^}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... private-start ...",
"src": "{&}",
"exp": "{&}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation -> reserved-annotation-start reserved-body -> \"!\" reserved-body-part reserved-body-part -> \"!\" reserved-char escaped-char ...",
"src": "{!.\\{}",
"exp": "{!}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation -> reserved-annotation-start reserved-body -> \"!\" reserved-body-part s reserved-body-part -> \"!\" reserved-char s escaped-char ...",
"src": "{!. \\{}",
"exp": "{!}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"description": "... reserved-annotation -> reserved-annotation-start reserved-body -> \"!\" reserved-body-part -> \"!\" quoted-literal ...",
"src": "{!|a|}",
"exp": "{!}",
"expErrors": [{ "type": "unsupported-expression" }],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": "hello { world\t\n}",
"exp": "hello world"
@ -838,130 +691,6 @@
}
]
},
{
"src": "foo {+reserved}",
"exp": "foo {+}",
"expParts": [
{
"type": "literal",
"value": "foo "
},
{
"type": "fallback",
"source": "+"
}
],
"expErrors": [
{
"type": "unsupported-expression"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": "foo {&private}",
"exp": "foo {&}",
"expParts": [
{
"type": "literal",
"value": "foo "
},
{
"type": "fallback",
"source": "&"
}
],
"expErrors": [
{
"type": "unsupported-expression"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": "foo {?reserved @a @b=$c}",
"exp": "foo {?}",
"expParts": [
{
"type": "literal",
"value": "foo "
},
{
"type": "fallback",
"source": "?"
}
],
"expErrors": [
{
"type": "unsupported-expression"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": ".foo {42} {{bar}}",
"exp": "bar",
"expParts": [
{
"type": "literal",
"value": "bar"
}
],
"expErrors": [
{
"type": "unsupported-statement"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": ".foo{42}{{bar}}",
"exp": "bar",
"expParts": [
{
"type": "literal",
"value": "bar"
}
],
"expErrors": [
{
"type": "unsupported-statement"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": ".foo |}lit{| {42}{{bar}}",
"exp": "bar",
"expParts": [
{
"type": "literal",
"value": "bar"
}
],
"expErrors": [
{
"type": "unsupported-statement"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": ".l $y = {|bar|} {{}}",
"exp": "",
"expParts": [
{
"type": "literal",
"value": "bar"
}
],
"expErrors": [
{
"type": "unsupported-statement"
}
],
"ignoreJava": "See https://github.com/unicode-org/message-format-wg/issues/782"
},
{
"src": "{{trailing whitespace}} \n",
"exp": "trailing whitespace"

View file

@ -1,18 +0,0 @@
{
"scenario": "Reserved statements",
"description": "Tests for unsupported statements",
"defaultTestProperties": {
"locale": "en-US",
"expErrors": [
{
"type": "unsupported-statement"
}
]
},
"tests": [
{ "src" : ".i {1} {{}}" },
{ "src" : ".l $y = {|bar|} {{}}" },
{ "src" : ".l $x.y = {|bar|} {{}}" }
]
}

View file

@ -1,11 +1,11 @@
{
"scenario": "Reserved and private annotations",
"description": "Tests for unsupported expressions (reserved/private)",
"description": "Tests for unsupported expressions (reserved/private) (now syntax errors)",
"defaultTestProperties": {
"locale": "en-US",
"expErrors": [
{
"type": "unsupported-expression"
"type": "syntax-error"
}
]
},
@ -47,7 +47,31 @@
{ "src": "hello {$foo +num x \\\\ abcde |3.14| r }" },
{ "src": "hello {$foo >num x \\\\ abcde |aaa||3.14||42| r }" },
{ "src": "hello {$foo >num x \\\\ abcde |aaa||3.14| |42| r }" },
{ "src" : ".input{ $n ~ }{{{$n}}}" }
{ "src" : ".input{ $n ~ }{{{$n}}}" },
{ "src": "foo {+reserved}"},
{ "src": "foo {&private}" },
{ "src": "foo {?reserved @a @b=$c}" },
{ "src": "{!.}" },
{ "src": "{! .}" },
{ "src": "{%}" },
{ "src": "{*}" },
{ "src": "{+}" },
{ "src": "{<}" },
{ "src": "{>}" },
{ "src": "{?}" },
{ "src": "{~}" },
{ "src": "{^.}" },
{ "src": "{^ .}" },
{ "src": "{&}" },
{ "src": "{!.\\{}" },
{ "src": "{!. \\{}" },
{ "src": "{!|a|}" },
{ "src": "{^}" },
{ "src": "{!}" },
{ "src": ".match {$foo !select} |1| {{one}} * {{other}}" },
{ "src": ".match {$foo ^select} |1| {{one}} * {{other}}" },
{ "src": ".match {|horse| ^private}\n 1 {{The value is one.}} * {{The value is not one.}}" },
{ "src": ".match {$foo ^select} |1| {{one}} * {{other}}" }
]
}

View file

@ -0,0 +1,27 @@
{
"scenario": "Reserved statements",
"description": "Tests for unsupported statements (now syntax errors)",
"defaultTestProperties": {
"locale": "en-US",
"expErrors": [
{
"type": "syntax-error"
}
]
},
"tests": [
{ "src" : ".i {1} {{}}" },
{ "src" : ".l $y = {|bar|} {{}}" },
{ "src" : ".l $x.y = {|bar|} {{}}" },
{ "src": ".matc {-1} {{hello}}" },
{ "src": ".m {-1} {{hello}}" },
{ "src": ".n{a}{{}}" },
{ "src": ".foo {42} {{bar}}" },
{ "src": ".foo{42}{{bar}}" },
{ "src": ".foo |}lit{| {42}{{bar}}" },
{ "src": ".n .{a}{{}}" },
{ "src": ".n. {a}{{}}" },
{ "src": ".n.{a}{b}{{}}" }
]
}