Пример #1
0
    def __init__(self, tssconfig={}, encoding=None, outputdir="", lex_debug=None, yacc_debug=None, debug=None):
        """
        Create a new TSSParser.

        ``tssconfig``
            All configurations related to tayra templates, are represented in
            this object.

        ``encoding``
            Character encoding for input Tayra style text.

        ``outputdir``
            To change the directory in which the parsetab.py file (and other
            output files) are written.

        ``lex_debug``
            PLY-Yacc option.

        ``yacc_debug``
            Generate a parser.out file that explains how yacc built the parsing
            table from the grammar.
        """
        self.debug = lex_debug or yacc_debug or debug
        self.encoding = encoding
        optimize = tssconfig.get("parse_optimize", False)
        lextab = tssconfig.get("lextab", LEXTAB) or LEXTAB
        yacctab = tssconfig.get("yacctab", YACCTAB) or YACCTAB
        # Build Lexer
        self.tsslex = TSSLexer(error_func=self._lex_error_func)
        kwargs = {"optimize": optimize} if optimize else {}
        kwargs.update(debug=lex_debug)
        kwargs.update(lextab=lextab) if lextab else None
        self.tsslex.build(**kwargs)
        self.tokens = self.tsslex.tokens
        # Build Yaccer
        kwargs = {"optimize": optimize} if optimize else {}
        kwargs.update(debug=(yacc_debug or debug))
        kwargs.update(outputdir=outputdir) if outputdir else None
        kwargs.update(tabmodule=yacctab)
        self.parser = ply.yacc.yacc(module=self, **kwargs)
        self.parser.tssparser = self  # For AST nodes to access `this`
        # Parser initialization
        self._tssconfig = tssconfig
        self._initialize()
Пример #2
0
class TSSParser(object):
    def __init__(self, tssconfig={}, encoding=None, outputdir="", lex_debug=None, yacc_debug=None, debug=None):
        """
        Create a new TSSParser.

        ``tssconfig``
            All configurations related to tayra templates, are represented in
            this object.

        ``encoding``
            Character encoding for input Tayra style text.

        ``outputdir``
            To change the directory in which the parsetab.py file (and other
            output files) are written.

        ``lex_debug``
            PLY-Yacc option.

        ``yacc_debug``
            Generate a parser.out file that explains how yacc built the parsing
            table from the grammar.
        """
        self.debug = lex_debug or yacc_debug or debug
        self.encoding = encoding
        optimize = tssconfig.get("parse_optimize", False)
        lextab = tssconfig.get("lextab", LEXTAB) or LEXTAB
        yacctab = tssconfig.get("yacctab", YACCTAB) or YACCTAB
        # Build Lexer
        self.tsslex = TSSLexer(error_func=self._lex_error_func)
        kwargs = {"optimize": optimize} if optimize else {}
        kwargs.update(debug=lex_debug)
        kwargs.update(lextab=lextab) if lextab else None
        self.tsslex.build(**kwargs)
        self.tokens = self.tsslex.tokens
        # Build Yaccer
        kwargs = {"optimize": optimize} if optimize else {}
        kwargs.update(debug=(yacc_debug or debug))
        kwargs.update(outputdir=outputdir) if outputdir else None
        kwargs.update(tabmodule=yacctab)
        self.parser = ply.yacc.yacc(module=self, **kwargs)
        self.parser.tssparser = self  # For AST nodes to access `this`
        # Parser initialization
        self._tssconfig = tssconfig
        self._initialize()

    def _initialize(self, tssfile=None, tssconfig={}):
        self.tssfile = tssfile
        self.tssconfig = deepcopy(self._tssconfig)
        self.tssconfig.update(tssconfig)
        self.tsslex.reset_lineno()

    def parse(self, text, tssfile=None, tssconfig={}, debuglevel=0):
        """Parse TSS and creates an AST tree. For every
        parsing invocation, the same lex, yacc, app options and objects will
        be used.

        ``filename``
            Name of the file being parsed (for meaningful error messages)
        ``debuglevel``
            Debug level to yacc
        """
        # Parser Initialize
        tssfile = tssfile if tssfile != None else self.tssfile
        self._initialize(tssfile=tssfile, tssconfig=tssconfig)
        self.tsslex.filename = self.tssfile = tssfile

        # parse and get the translation unit
        self.text = text

        # parse and get the Translation Unit
        debuglevel = self.debug or debuglevel
        tracking = True if self.debug or debuglevel else False
        self.tu = self.parser.parse(text, lexer=self.tsslex, debug=debuglevel, tracking=tracking)
        return self.tu

    # ------------------------- Private functions -----------------------------

    def _lex_error_func(self, lex, msg, line, column):
        self._parse_error(msg, self._coord(line, column))

    def _coord(self, lineno, column=None):
        return Coord(file=self.tsslex.filename, line=lineno, column=column)

    def _parse_error(self, msg, coord):
        raise ParseError("%s: %s" % (coord, msg))

    def _printparse(self, p):
        print p[0], "  : ",
        for i in range(1, len(p)):
            print p[i],
        print

    def _termwspac2nonterm(self, p, text, termcls, nontermcls):
        text1 = text.rstrip(" \t\r\n\f")
        term, wspac = text[: len(text1)], text[len(text1) :]
        wc = WC(p.parser, None, S(p.parser, wspac), None)
        return nontermcls(p.parser, termcls(p.parser, term), wc)

    # ---------- Precedence and associativity of operators --------------------

    precedence = (
        ("left", "COMMA"),
        ("left", "QMARK", "COLON"),
        ("left", "EQUAL"),
        ("left", "GT", "LT", "AND", "OR"),
        ("left", "PLUS", "MINUS"),
        ("left", "STAR", "FWDSLASH"),
        ("right", "UNARY"),
    )

    def _buildterms(self, p, terms):
        rc = []
        for t in terms:
            if t == None:
                rc.append(None)
                continue
            elif isinstance(t, tuple):
                cls, idx = t
                rc.append(cls(p.parser, p[idx]))
            else:
                rc.append(t)
        return rc

    # TODO : Makes sure that charset, import and namespace non-terminals
    # does not follow rulesets, media, page, font_face non-terminals

    def p_tss(self, p):
        """tss          : charset
                        | namespace
                        | font_face
                        | page
                        | media
                        | atrule
                        | rulesets
                        | wc
                        | tss charset
                        | tss namespace
                        | tss font_face
                        | tss page
                        | tss media
                        | tss atrule
                        | tss rulesets
                        | tss wc"""
        if len(p) == 3:
            if isinstance(p[2], Rulesets):
                p[2] = Rulesets(p.parser, p[2], None)
            p[0] = Tss(p.parser, p[1], p[2])
        else:
            if isinstance(p[1], Rulesets):
                p[1] = Rulesets(p.parser, p[1], None)
            p[0] = Tss(p.parser, None, p[1])

    # ---- CDATA

    def p_cdata_1(self, p):
        """cdata        : CDO CDATATEXT CDC"""
        x = [(CDO, 1), (CDATATEXT, 2), (CDC, 3)]
        p[0] = Cdata(p.parser, *self._buildterms(p, x))

    def p_cdata_2(self, p):
        """cdata        : CDO CDC"""
        x = [(CDO, 1), None, (CDC, 2)]
        p[0] = Cdata(p.parser, *self._buildterms(p, x))

    # ---- @charset

    def p_charset(self, p):
        """charset      : CHARSET_SYM string SEMICOLON"""
        x = [(CHARSET_SYM, 1), p[2], (SEMICOLON, 3)]
        p[0] = Charset(p.parser, *self._buildterms(p, x))

    # ---- @namespace

    def p_namespace_1(self, p):
        """namespacspec : NAMESPACE_SYM ident
                        | NAMESPACE_SYM EXTN_VAR"""
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        p[0] = [NAMESPACE_SYM(p.parser, p[1]), p[2], None, None]

    def p_namespace_2(self, p):
        """namespacspec : NAMESPACE_SYM string
                        | NAMESPACE_SYM uri
                        | NAMESPACE_SYM EXTN_EXPR"""
        if not isinstance(p[2], (String, Uri)):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_EXPR, ExtnExpr)
        p[0] = [NAMESPACE_SYM(p.parser, p[1]), None, p[2], None]

    def p_namespace_3(self, p):
        """namespacspec : namespacspec string
                        | namespacspec uri
                        | namespacspec EXTN_EXPR"""
        if p[1][2]:
            raise Exception("Multiple string or uri not allowed in namespace")
        if not isinstance(p[2], (String, Uri)):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_EXPR, ExtnExpr)
        p[1][2] = p[2]
        p[0] = p[1]

    def p_namespace_4(self, p):
        """namespace    : namespacspec SEMICOLON"""
        p[1][3] = SEMICOLON(p.parser, p[2])
        p[0] = Namespace(p.parser, *p[1])

    # ---- @font_face

    def p_font_face(self, p):
        """font_face    : FONT_FACE_SYM block"""
        x = [(FONT_FACE_SYM, 1), p[2]]
        p[0] = FontFace(p.parser, *self._buildterms(p, x))

    # ---- atrule

    # Gotcha : Handle generic at-rules
    def p_atrule_1(self, p):
        """atrule       : ATKEYWORD expr block"""
        x = [(ATKEYWORD, 1), p[2], p[3], None, None]
        p[0] = AtRule(p.parser, *self._buildterms(p, x))

    def p_atrule_2(self, p):
        """atrule       : ATKEYWORD expr SEMICOLON"""
        x = [(ATKEYWORD, 1), p[2], None, (SEMICOLON, 3), None]
        p[0] = AtRule(p.parser, *self._buildterms(p, x))

    def p_atrule_3(self, p):
        """atrule       : ATKEYWORD block"""
        x = [(ATKEYWORD, 1), None, p[2], None, None]
        p[0] = AtRule(p.parser, *self._buildterms(p, x))

    def p_atrule_4(self, p):
        """atrule       : ATKEYWORD SEMICOLON"""
        x = [(ATKEYWORD, 1), None, None, (SEMICOLON, 2), None]
        p[0] = AtRule(p.parser, *self._buildterms(p, x))

    def p_atrule_5(self, p):
        """atrule       : ATKEYWORD expr openbrace rulesets closebrace"""
        term = ATKEYWORD(p.parser, p[1])
        p[4] = Rulesets(p.parser, p[4], None)
        p[0] = AtRule(p.parser, term, p[2], None, None, (p[3], p[4], p[5]))

    def p_atrule_6(self, p):
        """atrule       : ATKEYWORD openbrace rulesets closebrace"""
        term = ATKEYWORD(p.parser, p[1])
        p[3] = Rulesets(p.parser, p[3], None)
        p[0] = AtRule(p.parser, term, None, None, None, (p[2], p[3], p[4]))

    # ---- @media

    def p_media_1(self, p):
        """mediaspec    : MEDIA_SYM IDENT
                        | MEDIA_SYM IDENT S """
        wc = (len(p) == 4) and WC(p.parser, None, S(p.parser, p[3]), None) or None
        args = [IDENT(p.parser, p[2]), wc]
        meds = Mediums(p.parser, None, None, Ident(p.parser, *args))
        p[0] = [MEDIA_SYM(p.parser, p[1]), meds, None, None, None, None]

    def p_media_2(self, p):
        """mediaspec    : MEDIA_SYM EXTN_VAR"""
        p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        meds = Mediums(p.parser, None, None, p[2])
        p[0] = [MEDIA_SYM(p.parser, p[1]), meds, None, None, None, None]

    def p_media_3(self, p):
        """mediaspec    : mediaspec COMMA IDENT
                        | mediaspec COMMA IDENT S"""
        cls, value = p[2]
        comma = cls(p.parser, value)
        wc = (len(p) == 5) and WC(p.parser, None, S(p.parser, p[4]), None) or None
        args = [IDENT(p.parser, p[3]), wc]
        p[1][1] = Mediums(p.parser, p[1][1], comma, Ident(p.parser, *args))
        p[0] = p[1]

    def p_media_4(self, p):
        """mediaspec    : mediaspec COMMA EXTN_VAR"""
        cls, value = p[2]
        comma = cls(p.parser, value)
        p[3] = self._termwspac2nonterm(p, p[3], EXTN_VAR, ExtnVar)
        p[1][1] = Mediums(p.parser, p[1][1], comma, p[3])
        p[0] = p[1]

    def p_media_5(self, p):
        """media        : mediaspec exprs openbrace rulesets closebrace"""
        p[4] = Rulesets(p.parser, p[4], None)
        p[1][2], p[1][3], p[1][4], p[1][5] = p[2], p[3], p[4], p[5]
        p[0] = Media(p.parser, *p[1])

    def p_media_6(self, p):
        """media        : mediaspec exprs openbrace closebrace"""
        p[1][2], p[1][3], p[1][4], p[1][5] = p[2], p[3], None, p[4]
        p[0] = Media(p.parser, *p[1])

    def p_media_7(self, p):
        """media        : mediaspec openbrace rulesets closebrace"""
        p[3] = Rulesets(p.parser, p[3], None)
        p[1][2], p[1][3], p[1][4], p[1][5] = None, p[2], p[3], p[4]
        p[0] = Media(p.parser, *p[1])

    def p_media_8(self, p):
        """media        : mediaspec openbrace closebrace"""
        p[1][2], p[1][3], p[1][4], p[1][5] = None, p[2], None, p[3]
        p[0] = Media(p.parser, *p[1])

    # ---- @page

    def p_page_1(self, p):
        """page         : PAGE_SYM ident block
                        | PAGE_SYM EXTN_VAR block"""
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        x = [(PAGE_SYM, 1), p[2], None, None, p[3]]
        p[0] = Page(p.parser, *self._buildterms(p, x))

    def p_page_2(self, p):
        """page         : PAGE_SYM COLON ident block
                        | PAGE_SYM COLON EXTN_VAR block"""
        cls, val = p[2]
        if not isinstance(p[3], Ident):
            p[3] = self._termwspac2nonterm(p, p[3], EXTN_VAR, ExtnVar)
        x = [(PAGE_SYM, 1), None, cls(p.parser, val), p[3], p[4]]
        p[0] = Page(p.parser, *self._buildterms(p, x))

    def p_page_3(self, p):
        """page         : PAGE_SYM ident COLON ident block
                        | PAGE_SYM EXTN_VAR COLON ident block
                        | PAGE_SYM ident COLON EXTN_VAR block
                        | PAGE_SYM EXTN_VAR COLON EXTN_VAR block"""
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        cls, val = p[3]
        if not isinstance(p[4], Ident):
            p[4] = self._termwspac2nonterm(p, p[4], EXTN_VAR, ExtnVar)
        x = [(PAGE_SYM, 1), p[2], cls(p.parser, val), p[4], p[5]]
        p[0] = Page(p.parser, *self._buildterms(p, x))

    def p_page_4(self, p):
        """page         : PAGE_SYM block"""
        x = [(PAGE_SYM, 1), None, None, None, p[2]]
        p[0] = Page(p.parser, *self._buildterms(p, x))

    def p_pagemargin_1(self, p):
        """pagemargin   : PAGE_MARGIN_SYM block"""
        p[0] = PageMargin(p.parser, PAGE_MARGIN_SYM(p.parser, p[1]), p[2])

    def p_pagemargin_2(self, p):
        """pagemargin   : PAGE_MARGIN_SYM"""
        p[0] = PageMargin(p.parser, PAGE_MARGIN_SYM(p.parser, p[1]), None)

    # ---- @import

    def p_import_1(self, p):
        """importspec   : IMPORT_SYM string
                        | IMPORT_SYM uri"""
        p[0] = [IMPORT_SYM(p.parser, p[1]), p[2], None, None]

    def p_import_2(self, p):
        """importspec   : importspec IDENT
                        | importspec IDENT S"""
        wc = (len(p) == 4) and WC(p.parser, None, S(p.parser, p[3]), None) or None
        args = [IDENT(p.parser, p[2]), wc]
        p[1][2] = Mediums(p.parser, None, None, Ident(p.parser, *args))
        p[0] = p[1]

    def p_import_3(self, p):
        """importspec   : importspec EXTN_VAR"""
        p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        p[1][2] = Mediums(p.parser, p[1][2], None, p[2])
        p[0] = p[1]

    def p_import_4(self, p):
        """importspec   : importspec COMMA IDENT
                        | importspec COMMA IDENT S"""
        cls, value = p[2]
        comma = cls(p.parser, value)
        wc = (len(p) == 5) and WC(p.parser, None, S(p.parser, p[4]), None) or None
        args = [IDENT(p.parser, p[3]), wc]
        p[1][2] = Mediums(p.parser, p[1][2], comma, Ident(p.parser, *args))
        p[0] = p[1]

    def p_import_5(self, p):
        """importspec   : importspec COMMA EXTN_VAR"""
        cls, value = p[2]
        p[3] = self._termwspac2nonterm(p, p[3], EXTN_VAR, ExtnVar)
        p[1][2] = Mediums(p.parser, p[1][2], cls(p.parser, value), p[3])
        p[0] = p[1]

    def p_import_6(self, p):
        """import       : importspec SEMICOLON"""
        p[1][3] = SEMICOLON(p.parser, p[2])
        p[0] = Import(p.parser, *p[1])

    # ---- extend

    def p_extend_1(self, p):
        """extend       : EXTEND_SYM selector SEMICOLON"""
        x = [(EXTEND_SYM, 1), p[2], (SEMICOLON, 3), None]
        p[0] = Extend(p.parser, *self._buildterms(p, x))

    def p_extend_2(self, p):
        """extend       : EXTEND_SYM selector SEMICOLON wc"""
        x = [(EXTEND_SYM, 1), p[2], (SEMICOLON, 3), p[4]]
        p[0] = Extend(p.parser, *self._buildterms(p, x))

    # ---- ruleset

    # TODO : only `&` is allowd in DLIMIT terminal, this constraint should
    # be checked inside `ElementName` class
    def p_rulesets_1(self, p):
        """rulesets     : ruleset
                        | rulesets ruleset"""
        args = [p[1], p[2]] if len(p) == 3 else [None, p[1]]
        p[0] = Rulesets(p.parser, *args)

    def p_rulesets_2(self, p):
        """rulesets     : EXTN_STATEMENT
                        | rulesets EXTN_STATEMENT"""
        if len(p) == 3:
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_STATEMENT, ExtnStatement)
            p[0] = Rulesets(p.parser, p[1], p[2])
        else:
            p[1] = self._termwspac2nonterm(p, p[1], EXTN_STATEMENT, ExtnStatement)
            p[0] = Rulesets(p.parser, None, p[1])

    def p_ruleset_1(self, p):
        """ruleset      : block
                        | selectors block"""
        args = [p[1], p[2]] if len(p) == 3 else [None, p[1]]
        args.append(None)
        p[0] = Ruleset(p.parser, *args)

    def p_ruleset_2(self, p):
        """ruleset      : import
                        | cdata"""
        p[0] = Ruleset(p.parser, None, None, p[1])

    def p_selectors_1(self, p):
        """selectors    : selector"""
        x = [None, None, p[1]]
        p[0] = Selectors(p.parser, *self._buildterms(p, x))

    def p_selectors_2(self, p):
        """selectors    : selectors COMMA selector"""
        cls, val = p[2]
        x = [p[1], cls(p.parser, val), p[3]]
        p[0] = Selectors(p.parser, *self._buildterms(p, x))

    def p_selector_1(self, p):
        """selector     : simpselector
                        | EXTN_EXPR """
        if not isinstance(p[1], SimpleSelector):
            p[1] = self._termwspac2nonterm(p, p[1], EXTN_EXPR, ExtnExpr)
        p[0] = Selector(p.parser, None, None, p[1])

    def p_selector_2(self, p):
        """selector     : selector simpselector"""
        p[0] = Selector(p.parser, p[1], None, p[2])

    def p_selector_3(self, p):
        """selector     : selector SEL_GT simpselector
                        | selector SEL_PLUS simpselector
                        | selector SEL_TILDA simpselector"""
        cls, value = p[2]
        p[0] = Selector(p.parser, p[1], cls(p.parser, value), p[3])

    def p_simpselector_1(self, p):
        """simpselector : sel_ident
                        | SEL_EXTN_VAR"""
        if not isinstance(p[1], Ident):
            p[1] = self._termwspac2nonterm(p, p[1], EXTN_VAR, ExtnVar)
        p[0] = SimpleSelector(p.parser, None, None, p[1], None, None, None)

    def p_simpselector_2(self, p):
        """simpselector : sel_hash"""
        p[0] = SimpleSelector(p.parser, None, None, None, p[1], None, None)

    def p_simpselector_3(self, p):
        """simpselector : SEL_STAR
                        | AMPERSAND"""
        cls, value = p[1]
        term = cls(p.parser, value)
        p[0] = SimpleSelector(p.parser, term, None, None, None, None, None)

    def p_simpselector_4(self, p):
        """simpselector : DOT sel_ident
                        | DOT EXTN_VAR"""
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        cls, value = p[1]
        term = cls(p.parser, value)
        p[0] = SimpleSelector(p.parser, None, term, p[2], None, None, None)

    def p_simpselector_5(self, p):
        """simpselector : attrib"""
        p[0] = SimpleSelector(p.parser, None, None, None, None, p[1], None)

    def p_simpselector_6(self, p):
        """simpselector : pseudo"""
        p[0] = SimpleSelector(p.parser, None, None, None, None, None, p[1])

    def p_attrib_1(self, p):
        """attrib       : opensqr sel_ident attroperator sel_ident closesqr
                        | opensqr sel_ident attroperator sel_string closesqr"""
        p[0] = Attrib(p.parser, p[1], p[2], p[3], p[4], p[5])

    def p_attrib_2(self, p):
        """attrib       : opensqr EXTN_VAR attroperator sel_ident closesqr
                        | opensqr EXTN_VAR attroperator sel_string closesqr"""
        p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        p[0] = Attrib(p.parser, p[1], p[2], p[3], p[4], p[5])

    def p_attrib_3(self, p):
        """attrib       : opensqr sel_ident attroperator EXTN_VAR closesqr"""
        p[4] = self._termwspac2nonterm(p, p[4], EXTN_VAR, ExtnVar)
        p[0] = Attrib(p.parser, p[1], p[2], p[3], p[4], p[5])

    def p_attrib_4(self, p):
        """attrib       : opensqr sel_ident attroperator EXTN_EXPR closesqr"""
        p[4] = self._termwspac2nonterm(p, p[4], EXTN_EXPR, ExtnExpr)
        p[0] = Attrib(p.parser, p[1], p[2], p[3], p[4], p[5])

    def p_attrib_5(self, p):
        """attrib       : opensqr sel_ident closesqr
                        | opensqr EXTN_VAR closesqr"""
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        p[0] = Attrib(p.parser, p[1], p[2], None, None, p[3])

    def p_attroperator(self, p):
        """attroperator : SEL_EQUAL
                        | INCLUDES
                        | DASHMATCH
                        | PREFIXMATCH
                        | SUFFIXMATCH
                        | SUBSTRINGMATCH"""
        cls, value = p[1]
        p[0] = AttrOperator(p.parser, cls(p.parser, value))

    def p_pseudo_1(self, p):
        """pseudo       : SEL_COLON sel_ident
                        | SEL_COLON func_call"""
        cls, value = p[1]
        p[0] = Pseudo(p.parser, None, cls(p.parser, value), p[2])

    def p_pseudo_2(self, p):
        """pseudo       : SEL_COLON EXTN_VAR"""
        cls, value = p[1]
        p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        p[0] = Pseudo(p.parser, None, cls(p.parser, value), p[2])

    def p_pseudo_3(self, p):
        """pseudo       : SEL_COLON SEL_COLON sel_ident
                        | SEL_COLON SEL_COLON func_call"""
        cls1, val1 = p[1]
        cls2, val2 = p[2]
        p[0] = Pseudo(p.parser, cls1(p.parser, val1), cls2(p.parser, val2), p[3])

    def p_pseudo_4(self, p):
        """pseudo       : SEL_COLON SEL_COLON EXTN_VAR"""
        cls1, val1 = p[1]
        cls2, val2 = p[2]
        p[3] = self._termwspac2nonterm(p, p[3], EXTN_VAR, ExtnVar)
        p[0] = Pseudo(p.parser, cls1(p.parser, val1), cls2(p.parser, val2), p[3])

    # ---- Declaration block

    def p_block(self, p):
        """block        : openbrace declarations closebrace
                        | openbrace closebrace"""
        args = [p[1], p[2], p[3]] if len(p) == 4 else [p[1], None, p[2]]
        p[0] = Block(p.parser, *args)

    def p_declarations_1(self, p):
        """declarations : extend
                        | declaration
                        | pagemargin
                        | rulesets"""
        if isinstance(p[1], Rulesets):
            p[1] = Rulesets(p.parser, p[1], None)
        p[0] = Declarations(p.parser, None, None, None, p[1])

    def p_declarations_3(self, p):
        """declarations : pagemargin declaration
                        | rulesets declaration"""
        if isinstance(p[1], Rulesets):
            p[1] = Rulesets(p.parser, p[1], None)
        dcls = Declarations(p.parser, None, None, None, p[1])
        p[0] = Declarations(p.parser, dcls, None, None, p[2])

    def p_declarations_4(self, p):
        """declarations : declarations SEMICOLON"""
        term = SEMICOLON(p.parser, p[2])
        p[0] = Declarations(p.parser, p[1], term, None, None)

    def p_declarations_5(self, p):
        """declarations : declarations SEMICOLON declaration"""
        term = SEMICOLON(p.parser, p[2])
        p[0] = Declarations(p.parser, p[1], term, None, p[3])

    def p_declarations_6(self, p):
        """declarations : declarations SEMICOLON wc"""
        term = SEMICOLON(p.parser, p[2])
        p[0] = Declarations(p.parser, p[1], term, p[3], None)

    def p_declarations_7(self, p):
        """declarations : declarations SEMICOLON wc declaration"""
        term = SEMICOLON(p.parser, p[2])
        p[0] = Declarations(p.parser, p[1], term, p[3], p[4])

    def p_declarations_8(self, p):
        """declarations : declarations rulesets
                        | declarations pagemargin"""
        if isinstance(p[2], Rulesets):
            p[2] = Rulesets(p.parser, p[2], None)
        p[0] = Declarations(p.parser, p[1], None, None, p[2])

    def p_declarations_9(self, p):
        """declarations : declarations pagemargin declaration
                        | declarations rulesets declaration"""
        if isinstance(p[2], Rulesets):
            p[2] = Rulesets(p.parser, p[2], None)
        dcls = Declarations(p.parser, p[1], None, None, p[2])
        p[0] = Declarations(p.parser, dcls, None, None, p[3])

    def p_declaration_1(self, p):
        """declaration  : ident COLON exprs
                        | ident COLON EXTN_EXPR
                        | EXTN_VAR COLON exprs
                        | EXTN_VAR COLON EXTN_EXPR"""
        if not isinstance(p[1], Ident):
            p[1] = self._termwspac2nonterm(p, p[1], EXTN_VAR, ExtnVar)
        cls, value = p[2]
        if not isinstance(p[3], Exprs):
            p[3] = self._termwspac2nonterm(p, p[3], EXTN_EXPR, ExtnExpr)
        x = [None, p[1], cls(p.parser, value), p[3], None]
        p[0] = Declaration(p.parser, *self._buildterms(p, x))

    def p_declaration_2(self, p):
        """declaration  : ident COLON exprs prio
                        | ident COLON EXTN_EXPR prio
                        | EXTN_VAR COLON exprs prio
                        | EXTN_VAR COLON EXTN_EXPR prio"""
        if not isinstance(p[1], Ident):
            p[1] = self._termwspac2nonterm(p, p[1], EXTN_VAR, ExtnVar)
        cls, value = p[2]
        if not isinstance(p[3], Exprs):
            p[3] = self._termwspac2nonterm(p, p[3], EXTN_EXPR, ExtnExpr)
        x = [None, p[1], cls(p.parser, value), p[3], p[4]]
        p[0] = Declaration(p.parser, *self._buildterms(p, x))

    def p_declaration_3(self, p):
        """declaration  : PREFIXSTAR ident COLON exprs
                        | PREFIXSTAR ident COLON EXTN_EXPR
                        | PREFIXSTAR EXTN_VAR COLON exprs
                        | PREFIXSTAR EXTN_VAR COLON EXTN_EXPR"""
        cls1, val1 = p[1]
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        cls2, val2 = p[3]
        if not isinstance(p[4], Exprs):
            p[4] = self._termwspac2nonterm(p, p[4], EXTN_EXPR, ExtnExpr)
        x = [cls1(p.parser, val1), p[2], cls2(p.parser, val2), p[4], None]
        p[0] = Declaration(p.parser, *self._buildterms(p, x))

    def p_declaration_4(self, p):
        """declaration  : PREFIXSTAR ident COLON exprs prio
                        | PREFIXSTAR ident COLON EXTN_EXPR prio
                        | PREFIXSTAR EXTN_VAR COLON exprs prio
                        | PREFIXSTAR EXTN_VAR COLON EXTN_EXPR prio"""
        cls1, val1 = p[1]
        if not isinstance(p[2], Ident):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_VAR, ExtnVar)
        cls2, val2 = p[3]
        if not isinstance(p[4], Exprs):
            p[3] = self._termwspac2nonterm(p, p[3], EXTN_EXPR, ExtnExpr)
        x = [cls1(p.parser, val1), p[2], cls2(p.parser, val2), p[4], p[5]]
        p[0] = Declaration(p.parser, *self._buildterms(p, x))

    def p_prio(self, p):
        """prio         : IMPORTANT_SYM
                        | IMPORTANT_SYM wc"""
        x = [(IMPORTANT_SYM, 1), p[2]] if len(p) == 3 else [(IMPORTANT_SYM, 1), None]
        p[0] = Priority(p.parser, *self._buildterms(p, x))

    # ---- expressions

    def p_exprs_1(self, p):
        """exprs        : expr"""
        p[0] = Exprs(p.parser, None, None, p[1])

    def p_exprs_2(self, p):
        """exprs        : exprs expr"""
        plaincss = p.parser.tssparser.tssconfig.get("plaincss", False)
        if plaincss:
            p[0] = Exprs(p.parser, p[1], None, p[2])
        else:
            p[0] = Exprs(p.parser, p[1], None, p[2], s=S(p.parser, " "))

    def p_exprs_3(self, p):
        """exprs        : exprs COMMA expr"""
        cls, value = p[2]
        term = cls(p.parser, value)
        p[0] = Exprs(p.parser, p[1], term, p[3])

    def p_expr_1(self, p):
        """expr         : term"""
        args = [p[1], None, None, None, None]
        p[0] = Expr(p.parser, *args)

    def p_expr_2(self, p):
        """expr         : openparan expr closeparan
                        | openparan exprcolon closeparan"""
        expr = ExprParan(p.parser, p[1], p[2], p[3])
        p[0] = Expr(p.parser, None, expr, None, None, None)

    def p_expr_3(self, p):
        """expr         : expr QMARK exprcolon"""
        cls, value = p[2]
        term = cls(p.parser, value)
        expr = ExprTernary(p.parser, p[1], term, p[3])
        p[0] = Expr(p.parser, None, expr, None, None, None)

    def p_expr_4(self, p):
        """expr         : expr PLUS expr
                        | expr MINUS expr
                        | expr STAR expr
                        | expr AND expr
                        | expr OR expr"""
        cls, value = p[2]
        term = cls(p.parser, value)
        p[0] = Expr(p.parser, None, None, p[1], term, p[3])

    def p_expr_5(self, p):
        """expr         : expr FWDSLASH expr"""
        cls, value = p[2]
        term = cls(p.parser, value)
        p[0] = Expr(p.parser, None, None, p[1], term, p[3])

    def p_expr_6(self, p):
        """expr         : expr EQUAL expr
                        | expr GT expr
                        | expr LT expr"""
        cls, value = p[2]
        term = cls(p.parser, value)
        p[0] = Expr(p.parser, None, None, p[1], term, p[3])

    def p_expr_7(self, p):
        """expr         : MINUS expr %prec UNARY
                        | PLUS expr %prec UNARY"""
        cls, value = p[1]
        term = cls(p.parser, value)
        p[0] = Expr(p.parser, None, None, None, term, p[2])

    def p_exprcolon(self, p):
        """exprcolon    : expr COLON expr"""
        cls, value = p[2]
        term = cls(p.parser, value)
        p[0] = Expr(p.parser, None, None, p[1], term, p[3])

    def p_term_1(self, p):
        """term         : number
                        | dimension
                        | func_call
                        | ident
                        | string
                        | uri
                        | unicoderange
                        | hash
                        | EXTN_VAR"""
        ts = (Number, Dimension, FuncCall, Ident, String, Uri, UnicodeRange, Hash)
        if not isinstance(p[1], ts):
            p[1] = self._termwspac2nonterm(p, p[1], EXTN_VAR, ExtnVar)
        p[0] = Term(p.parser, p[1])

    def p_func_call_1(self, p):
        """func_call    : FUNCTION exprs closeparan
                        | FUNCTION EXTN_EXPR closeparan"""
        if not isinstance(p[2], Exprs):
            p[2] = self._termwspac2nonterm(p, p[2], EXTN_EXPR, ExtnExpr)
        x = [(FUNCTION, 1), p[2], None, p[3]]
        p[0] = FuncCall(p.parser, *self._buildterms(p, x))

    def p_func_call_2(self, p):
        """func_call    : FUNCTION simpselector closeparan"""
        x = [(FUNCTION, 1), None, p[2], p[3]]
        p[0] = FuncCall(p.parser, *self._buildterms(p, x))

    def p_func_call_3(self, p):
        """func_call    : FUNCTION closeparan"""
        x = [(FUNCTION, 1), None, None, p[2]]
        p[0] = FuncCall(p.parser, *self._buildterms(p, x))

    # ---- Terminals with whitespace

    def p_sel_ident(self, p):
        """sel_ident    : SEL_IDENT wc
                        | SEL_IDENT"""
        x = [(IDENT, 1), p[2]] if len(p) == 3 else [(IDENT, 1), None]
        p[0] = Ident(p.parser, *self._buildterms(p, x))

    def p_sel_string(self, p):
        """sel_string   : SEL_STRING wc
                        | SEL_STRING"""
        x = [(STRING, 1), p[2]] if len(p) == 3 else [(STRING, 1), None]
        p[0] = String(p.parser, *self._buildterms(p, x))

    def p_sel_hash(self, p):
        """sel_hash     : SEL_HASH wc
                        | SEL_HASH"""
        (cls, value) = p[1]
        wc = p[2] if len(p) == 3 else None
        p[0] = Hash(p.parser, cls(p.parser, value), wc)

    def p_ident(self, p):
        """ident        : IDENT wc
                        | IDENT"""
        x = [(IDENT, 1), p[2]] if len(p) == 3 else [(IDENT, 1), None]
        p[0] = Ident(p.parser, *self._buildterms(p, x))

    def p_string(self, p):
        """string       : STRING wc
                        | STRING"""
        x = [(STRING, 1), p[2]] if len(p) == 3 else [(STRING, 1), None]
        p[0] = String(p.parser, *self._buildterms(p, x))

    def p_uri(self, p):
        """uri          : URI wc
                        | URI"""
        x = [(URI, 1), p[2]] if len(p) == 3 else [(URI, 1), None]
        p[0] = Uri(p.parser, *self._buildterms(p, x))

    def p_unicoderange(self, p):
        """unicoderange : UNICODERANGE wc
                        | UNICODERANGE"""
        x = [(UNICODERANGE, 1), p[2]] if len(p) == 3 else [(UNICODERANGE, 1), None]
        p[0] = UnicodeRange(p.parser, *self._buildterms(p, x))

    def p_number(self, p):
        """number       : NUMBER wc
                        | NUMBER"""
        (cls, value) = p[1]
        term = cls(p.parser, value)
        wc = p[2] if len(p) == 3 else None
        p[0] = Number(p.parser, term, wc)

    def p_dimension(self, p):
        """dimension    : DIMENSION wc
                        | DIMENSION"""
        (cls, value) = p[1]
        term = cls(p.parser, value)
        wc = p[2] if len(p) == 3 else None
        p[0] = Dimension(p.parser, term, wc)

    def p_hash(self, p):
        """hash         : HASH wc
                        | HASH"""
        (cls, value) = p[1]
        term = cls(p.parser, value)
        wc = p[2] if len(p) == 3 else None
        p[0] = Hash(p.parser, term, wc)

    def p_openbrace(self, p):
        """openbrace    : OPENBRACE wc
                        | OPENBRACE"""
        t = OPENBRACE(p.parser, p[1])
        wc = p[2] if len(p) == 3 else None
        p[0] = Openbrace(p.parser, t, wc)

    def p_closebrace(self, p):
        """closebrace   : CLOSEBRACE wc
                        | CLOSEBRACE"""
        t = CLOSEBRACE(p.parser, p[1])
        # wc = WC(p.parser, None, S(p.parser, p[2]), None) if len(p)==3 else None
        wc = p[2] if len(p) == 3 else None
        p[0] = Closebrace(p.parser, t, wc)

    def p_opensqr(self, p):
        """opensqr      : OPENSQR wc
                        | OPENSQR"""
        t = OPENSQR(p.parser, p[1])
        wc = p[2] if len(p) == 3 else None
        p[0] = Opensqr(p.parser, t, wc)

    def p_closesqr(self, p):
        """closesqr     : CLOSESQR wc
                        | CLOSESQR"""
        t = CLOSESQR(p.parser, p[1])
        wc = p[2] if len(p) == 3 else None
        p[0] = Closesqr(p.parser, t, wc)

    def p_openparan(self, p):
        """openparan    : OPENPARAN wc
                        | OPENPARAN"""
        t = OPENPARAN(p.parser, p[1])
        wc = p[2] if len(p) == 3 else None
        p[0] = Openparan(p.parser, t, wc)

    def p_closeparan(self, p):
        """closeparan   : CLOSEPARAN wc
                        | CLOSEPARAN"""
        t = CLOSEPARAN(p.parser, p[1])
        wc = p[2] if len(p) == 3 else None
        p[0] = Closeparan(p.parser, t, wc)

    def p_wc_1(self, p):
        """wc           : S
                        | wc S"""
        args = [p[1], S(p.parser, p[2]), None] if len(p) == 3 else [None, S(p.parser, p[1]), None]
        p[0] = WC(p.parser, *args)

    def p_wc_2(self, p):
        """wc           : COMMENT
                        | wc COMMENT"""
        args = [p[1], None, COMMENT(p.parser, p[2])] if len(p) == 3 else [None, None, COMMENT(p.parser, p[1])]
        p[0] = WC(p.parser, *args)

    # ---- For confirmance with forward compatible CSS

    def p_error(self, p):
        if p:
            column = self.tsslex._find_tok_column(p)
            self._parse_error("before: %s " % (p.value,), self._coord(p.lineno, column))
        else:
            self._parse_error("At end of input", "")