Parse a token stream into an array of declaration tuples.
    
        In addition to tokens, requires a dictionary of declared variables,
        a list of selectors that will apply to the declarations parsed in
        this block, and a boolean flag for mercator projection, both needed
        by possible recursive calls back to parse_rule().
    
        Return an array of (property, value, (line, col), importance).
    
        Enter this function after a left-brace is found:
        http://www.w3.org/TR/CSS2/syndata.html#block

def parse_block(tokens, variables, selectors, is_merc):
    """ Parse a token stream into an array of declaration tuples.
    
        In addition to tokens, requires a dictionary of declared variables,
        a list of selectors that will apply to the declarations parsed in
        this block, and a boolean flag for mercator projection, both needed
        by possible recursive calls back to parse_rule().
    
        Return an array of (property, value, (line, col), importance).
    
        Enter this function after a left-brace is found:
        http://www.w3.org/TR/CSS2/syndata.html#block
    """
    #
    # Local helper functions
    #

    def parse_value(tokens, variables):
        """ Look for value tokens after a property name, possibly !important.
        """
        value = []
        while True:
            tname, tvalue, line, col = tokens.next()
            if (tname, tvalue) == ('CHAR', '!'):
                while True:
                    tname, tvalue, line, col = tokens.next()
                    if (tname, tvalue) == ('IDENT', 'important'):
                        while True:
                            tname, tvalue, line, col = tokens.next()
                            if (tname, tvalue) == ('CHAR', ';'):
                                #
                                # end of a high-importance value
                                #
                                return value, True
                            elif (tname, tvalue) == ('CHAR', '}'):
                                #
                                # end of a block means end of a value
                                #
                                raise BlockTerminatedValue(value, True, line, col)
                            elif (tname, tvalue) == ('S', '\n'):
                                raise ParseException('Unexpected end of line', line, col)
                            elif tname not in ('S', 'COMMENT'):
                                raise ParseException('Unexpected values after !important declaration', line, col)
                        break
                    else:
                        raise ParseException('Malformed declaration after "!"', line, col)
                break
            elif (tname, tvalue) == ('CHAR', ';'):
                #
                # end of a low-importance value
                #
                return value, False
            elif (tname, tvalue) == ('CHAR', '}'):
                #
                # end of a block means end of a value
                #
                raise BlockTerminatedValue(value, False, line, col)
            elif tname == 'ATKEYWORD':
                #
                # Possible variable use:
                # http://lesscss.org/#-variables
                #
                tokens = chain(iter(variables[tvalue]), tokens)
            elif (tname, tvalue) == ('S', '\n'):
                raise ParseException('Unexpected end of line', line, col)
            elif tname not in ('S', 'COMMENT'):
                #
                # Legitimate-looking value token.
                #
                value.append((tname, tvalue))
        raise ParseException('Malformed property value', line, col)
    
    #
    # The work.
    #
    
    ruleset = []
    property_values = []
    
    while True:
        tname, tvalue, line, col = tokens.next()
        
        if tname == 'IDENT':
            _tname, _tvalue, _line, _col = tokens.next()
            
            if (_tname, _tvalue) == ('CHAR', ':'):
                #
                # Retrieve and process a value after a property name.
                # http://www.w3.org/TR/CSS2/syndata.html#declaration
                #
                if tvalue not in properties:
                    raise ParseException('Unsupported property name, %s' % tvalue, line, col)

                try:
                    property = Property(tvalue)
                    vtokens, importance = parse_value(tokens, variables)
                except BlockTerminatedValue, e:
                    vtokens, importance = e.tokens, e.important
                    tokens = chain([('CHAR', '}', e.line, e.col)], tokens)

                value = postprocess_value(property, vtokens, importance, line, col)
                property_values.append((property, value, (line, col), importance))
                
            else:
                #
                # We may have just found the start of a nested block.
                # http://lesscss.org/#-nested-rules
                #
                tokens_ = chain([(tname, tvalue, line, col), (_tname, _tvalue, _line, _col)], tokens)
                ruleset += parse_rule(tokens_, variables, [], selectors, is_merc)
        
        elif (tname, tvalue) == ('CHAR', '}'):
            #
            # Closing out a block
            #
            for (selector, property_value) in product(selectors, property_values):

                property, value, (line, col), importance = property_value
                sort_key = value.importance(), selector.specificity(), (line, col)

                ruleset.append(Declaration(selector, property, value, sort_key))
                
            return ruleset
        
        elif tname in ('HASH', ) or (tname, tvalue) in [('CHAR', '.'), ('CHAR', '*'), ('CHAR', '['), ('CHAR', '&')]:
            #
            # One of a bunch of valid ways to start a nested rule.
            #
            # Most will end up rejected by Cascadenik as parsing errors,
            # except for identifiers for text rules and the start of
            # nested blocks with a "&" combinator:
            # http://lesscss.org/#-nested-rules
            #
            tokens_ = chain([(tname, tvalue, line, col)], tokens)
            ruleset += parse_rule(tokens_, variables, [], selectors, is_merc)
        
        elif tname not in ('S', 'COMMENT'):
            raise ParseException('Malformed style rule', line, col)

    raise ParseException('Malformed block', line, col)