parseIntoTree method

Node parseIntoTree()

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];
}