parseIntoTree method
Implementation
Node parseIntoTree() {
final tokens = lexIntoTokens();
final parsingStack = <ST>[ST.message];
final syntaxTree = Node(ST.empty, 0, expectedSymbolCount: 1);
final treeTraversalStack = <Node>[syntaxTree];
// Helper function for parsing and constructing tree.
void parseAndConstructNode(ST nonterminal, int ruleIndex) {
final parent = treeTraversalStack.last;
final grammarRule = grammar[nonterminal]![ruleIndex];
// When we run out of tokens, just use -1 to represent the last index.
final positionInMessage =
tokens.isNotEmpty ? tokens.first.positionInMessage : -1;
final node = Node(nonterminal, positionInMessage,
expectedSymbolCount: grammarRule.length);
parsingStack.addAll(grammarRule.reversed);
// For tree construction, add nodes to the parent until the parent has all
// all the children it is expecting.
parent.children.add(node);
if (parent.isFull) {
treeTraversalStack.removeLast();
}
treeTraversalStack.add(node);
}
while (parsingStack.isNotEmpty) {
final symbol = parsingStack.removeLast();
// Figure out which production rule to use.
switch (symbol) {
case ST.message:
if (tokens.isEmpty) {
parseAndConstructNode(ST.message, 4);
} else if (tokens[0].type == ST.closeBrace) {
parseAndConstructNode(ST.message, 4);
} else if (tokens[0].type == ST.string) {
parseAndConstructNode(ST.message, 0);
} else if (tokens[0].type == ST.openBrace) {
if (3 < tokens.length && tokens[3].type == ST.plural) {
parseAndConstructNode(ST.message, 2);
} else if (3 < tokens.length && tokens[3].type == ST.select) {
parseAndConstructNode(ST.message, 3);
} else {
parseAndConstructNode(ST.message, 1);
}
} else {
// Theoretically, we can never get here.
throw L10nException('ICU Syntax Error.');
}
break;
case ST.placeholderExpr:
parseAndConstructNode(ST.placeholderExpr, 0);
break;
case ST.pluralExpr:
parseAndConstructNode(ST.pluralExpr, 0);
break;
case ST.pluralParts:
if (tokens.isNotEmpty &&
(tokens[0].type == ST.identifier ||
tokens[0].type == ST.other ||
tokens[0].type == ST.equalSign)) {
parseAndConstructNode(ST.pluralParts, 0);
} else {
parseAndConstructNode(ST.pluralParts, 1);
}
break;
case ST.pluralPart:
if (tokens.isNotEmpty && tokens[0].type == ST.identifier) {
parseAndConstructNode(ST.pluralPart, 0);
} else if (tokens.isNotEmpty && tokens[0].type == ST.equalSign) {
parseAndConstructNode(ST.pluralPart, 1);
} else if (tokens.isNotEmpty && tokens[0].type == ST.other) {
parseAndConstructNode(ST.pluralPart, 2);
} else {
throw L10nParserException(
'ICU Syntax Error: Plural parts must be of the form "identifier { message }" or "= number { message }"',
filename,
messageId,
messageString,
tokens[0].positionInMessage,
);
}
break;
case ST.selectExpr:
parseAndConstructNode(ST.selectExpr, 0);
break;
case ST.selectParts:
if (tokens.isNotEmpty &&
(tokens[0].type == ST.identifier || tokens[0].type == ST.other)) {
parseAndConstructNode(ST.selectParts, 0);
} else {
parseAndConstructNode(ST.selectParts, 1);
}
break;
case ST.selectPart:
if (tokens.isNotEmpty && tokens[0].type == ST.identifier) {
parseAndConstructNode(ST.selectPart, 0);
} else if (tokens.isNotEmpty && tokens[0].type == ST.other) {
parseAndConstructNode(ST.selectPart, 1);
} else {
throw L10nParserException(
'ICU Syntax Error: Select parts must be of the form "identifier { message }"',
filename,
messageId,
messageString,
tokens[0].positionInMessage);
}
break;
// At this point, we are only handling terminal symbols.
// ignore: no_default_cases
default:
final parent = treeTraversalStack.last;
// If we match a terminal symbol, then remove it from tokens and
// add it to the tree.
if (symbol == ST.empty) {
parent.children.add(Node.empty(-1));
} else if (tokens.isEmpty) {
throw L10nParserException(
'ICU Syntax Error: Expected "${terminalTypeToString[symbol]}" but found no tokens.',
filename,
messageId,
messageString,
messageString.length + 1,
);
} else if (symbol == tokens[0].type) {
final token = tokens.removeAt(0);
parent.children.add(token);
} else {
throw L10nParserException(
'ICU Syntax Error: Expected "${terminalTypeToString[symbol]}" but found "${tokens[0].value}".',
filename,
messageId,
messageString,
tokens[0].positionInMessage,
);
}
if (parent.isFull) {
treeTraversalStack.removeLast();
}
}
}
return syntaxTree.children[0];
}