Directive? processDirective()

in lib/parser.dart [463:741]


  Directive? processDirective() {
    var start = _peekToken.span;

    var tokId = processVariableOrDirective();
    if (tokId is VarDefinitionDirective) return tokId;
    final tokenId = tokId as int;
    switch (tokenId) {
      case TokenKind.DIRECTIVE_IMPORT:
        _next();

        // @import "uri_string" or @import url("uri_string") are identical; only
        // a url can follow an @import.
        String? importStr;
        if (_peekIdentifier()) {
          var func = processFunction(identifier());
          if (func is UriTerm) {
            importStr = func.text;
          }
        } else {
          importStr = processQuotedString(false);
        }

        // Any medias?
        var medias = processMediaQueryList();

        if (importStr == null) {
          _error('missing import string', _peekToken.span);
        }

        return ImportDirective(importStr!.trim(), medias, _makeSpan(start));

      case TokenKind.DIRECTIVE_MEDIA:
        _next();

        // Any medias?
        var media = processMediaQueryList();

        var rules = <TreeNode>[];
        if (_maybeEat(TokenKind.LBRACE)) {
          while (!_maybeEat(TokenKind.END_OF_FILE)) {
            final rule = processRule();
            if (rule == null) break;
            rules.add(rule);
          }

          if (!_maybeEat(TokenKind.RBRACE)) {
            _error('expected } after ruleset for @media', _peekToken.span);
          }
        } else {
          _error('expected { after media before ruleset', _peekToken.span);
        }
        return MediaDirective(media, rules, _makeSpan(start));

      case TokenKind.DIRECTIVE_HOST:
        _next();

        var rules = <TreeNode>[];
        if (_maybeEat(TokenKind.LBRACE)) {
          while (!_maybeEat(TokenKind.END_OF_FILE)) {
            final rule = processRule();
            if (rule == null) break;
            rules.add(rule);
          }

          if (!_maybeEat(TokenKind.RBRACE)) {
            _error('expected } after ruleset for @host', _peekToken.span);
          }
        } else {
          _error('expected { after host before ruleset', _peekToken.span);
        }
        return HostDirective(rules, _makeSpan(start));

      case TokenKind.DIRECTIVE_PAGE:
        // @page S* IDENT? pseudo_page?
        //      S* '{' S*
        //      [ declaration | margin ]?
        //      [ ';' S* [ declaration | margin ]? ]* '}' S*
        //
        // pseudo_page :
        //      ':' [ "left" | "right" | "first" ]
        //
        // margin :
        //      margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
        //
        // margin_sym : @top-left-corner, @top-left, @bottom-left, etc.
        //
        // See http://www.w3.org/TR/css3-page/#CSS21
        _next();

        // Page name
        Identifier? name;
        if (_peekIdentifier()) {
          name = identifier();
        }

        // Any pseudo page?
        Identifier? pseudoPage;
        if (_maybeEat(TokenKind.COLON)) {
          if (_peekIdentifier()) {
            pseudoPage = identifier();
            // TODO(terry): Normalize pseudoPage to lowercase.
            if (isChecked &&
                !(pseudoPage.name == 'left' ||
                    pseudoPage.name == 'right' ||
                    pseudoPage.name == 'first')) {
              _warning(
                  'Pseudo page must be left, top or first', pseudoPage.span);
              return null;
            }
          }
        }

        var pseudoName = pseudoPage is Identifier ? pseudoPage.name : '';
        var ident = name is Identifier ? name.name : '';
        return PageDirective(
            ident, pseudoName, processMarginsDeclarations(), _makeSpan(start));

      case TokenKind.DIRECTIVE_CHARSET:
        // @charset S* STRING S* ';'
        _next();

        var charEncoding = processQuotedString(false);
        return CharsetDirective(charEncoding, _makeSpan(start));

      // TODO(terry): Workaround Dart2js bug continue not implemented in switch
      //              see https://code.google.com/p/dart/issues/detail?id=8270
      /*
      case TokenKind.DIRECTIVE_MS_KEYFRAMES:
        // TODO(terry): For now only IE 10 (are base level) supports @keyframes,
        // -moz- has only been optional since Oct 2012 release of Firefox, not
        // all versions of webkit support @keyframes and opera doesn't yet
        // support w/o -o- prefix.  Add more warnings for other prefixes when
        // they become optional.
        if (isChecked) {
          _warning('@-ms-keyframes should be @keyframes', _makeSpan(start));
        }
        continue keyframeDirective;

      keyframeDirective:
      */
      case TokenKind.DIRECTIVE_KEYFRAMES:
      case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES:
      case TokenKind.DIRECTIVE_MOZ_KEYFRAMES:
      case TokenKind.DIRECTIVE_O_KEYFRAMES:
      // TODO(terry): Remove workaround when bug 8270 is fixed.
      case TokenKind.DIRECTIVE_MS_KEYFRAMES:
        if (tokenId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) {
          _warning('@-ms-keyframes should be @keyframes', _makeSpan(start));
        }
        // TODO(terry): End of workaround.

        // Key frames grammar:
        //
        //     @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}';
        //
        //     browser: [-webkit-, -moz-, -ms-, -o-]
        //
        //     keyframes-blocks:
        //       [keyframe-selectors '{' declarations '}']* ;
        //
        //     keyframe-selectors:
        //       ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ;
        _next();

        Identifier? name;
        if (_peekIdentifier()) {
          name = identifier();
        }

        _eat(TokenKind.LBRACE);

        var keyframe = KeyFrameDirective(tokenId, name, _makeSpan(start));

        do {
          var selectors = Expressions(_makeSpan(start));

          do {
            var term = processTerm() as Expression;

            // TODO(terry): Only allow from, to and PERCENTAGE ...

            selectors.add(term);
          } while (_maybeEat(TokenKind.COMMA));

          keyframe.add(KeyFrameBlock(
              selectors, processDeclarations(), _makeSpan(start)));
        } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile());

        return keyframe;

      case TokenKind.DIRECTIVE_FONTFACE:
        _next();
        return FontFaceDirective(processDeclarations(), _makeSpan(start));

      case TokenKind.DIRECTIVE_STYLET:
        // Stylet grammar:
        //
        //     @stylet IDENT '{'
        //       ruleset
        //     '}'
        _next();

        dynamic name;
        if (_peekIdentifier()) {
          name = identifier();
        }

        _eat(TokenKind.LBRACE);

        var productions = <TreeNode>[];

        start = _peekToken.span;
        while (!_maybeEat(TokenKind.END_OF_FILE)) {
          final rule = processRule();
          if (rule == null) {
            break;
          }
          productions.add(rule);
        }

        _eat(TokenKind.RBRACE);

        return StyletDirective(name as String, productions, _makeSpan(start));

      case TokenKind.DIRECTIVE_NAMESPACE:
        // Namespace grammar:
        //
        // @namespace S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
        // namespace_prefix : IDENT
        _next();

        Identifier? prefix;
        if (_peekIdentifier()) {
          prefix = identifier();
        }

        // The namespace URI can be either a quoted string url("uri_string")
        // are identical.
        String? namespaceUri;
        if (_peekIdentifier()) {
          var func = processFunction(identifier());
          if (func is UriTerm) {
            namespaceUri = func.text;
          }
        } else {
          if (prefix != null && prefix.name == 'url') {
            var func = processFunction(prefix);
            if (func is UriTerm) {
              // @namespace url("");
              namespaceUri = func.text;
              prefix = null;
            }
          } else {
            namespaceUri = processQuotedString(false);
          }
        }

        return NamespaceDirective(
            prefix?.name ?? '', namespaceUri, _makeSpan(start));

      case TokenKind.DIRECTIVE_MIXIN:
        return processMixin();

      case TokenKind.DIRECTIVE_INCLUDE:
        return processInclude(_makeSpan(start));
      case TokenKind.DIRECTIVE_CONTENT:
        // TODO(terry): TBD
        _warning('@content not implemented.', _makeSpan(start));
        return null;
      case TokenKind.DIRECTIVE_MOZ_DOCUMENT:
        return processDocumentDirective();
      case TokenKind.DIRECTIVE_SUPPORTS:
        return processSupportsDirective();
      case TokenKind.DIRECTIVE_VIEWPORT:
      case TokenKind.DIRECTIVE_MS_VIEWPORT:
        return processViewportDirective();
    }
    return null;
  }