671 lines
19 KiB
Python
Raw Normal View History

2012-01-28 14:52:09 +00:00
"""
LESSCSS Parser.
http://www.dabeaz.com/ply/ply.html
http://www.w3.org/TR/CSS21/grammar.html#scanner
http://lesscss.org/#docs
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import os
import ply.yacc
from . import lexer
from . import utility
2012-02-12 11:50:57 +00:00
from .scope import Scope
2012-01-28 14:52:09 +00:00
from .color import LessColor
from lesscpy.plib import *
class LessParser(object):
precedence = (
('left', '+', '-'),
('left', '*', '/'),
)
def __init__(self,
lex_optimize=True,
yacc_optimize=True,
yacctab='yacctab',
yacc_debug=False,
scope=None,
outputdir='/tmp',
2012-01-30 12:56:33 +00:00
importlvl=0,
verbose=False):
2012-01-28 14:52:09 +00:00
""" Parser object
@param bool: Optimized lexer
@param bool: Optimized parser
@param string: Yacc tables file
@param bool: Debug mode
@param dict: Included scope
"""
2012-01-30 12:56:33 +00:00
self.verbose = verbose
2012-01-28 14:52:09 +00:00
self.importlvl = importlvl
self.lex = lexer.LessLexer()
if not yacctab:
yacctab = 'yacctab'
self.ignored = ('t_ws', 'css_comment', 'less_comment',
'css_vendor_hack', 'css_keyframes')
self.tokens = [t for t in self.lex.tokens
if t not in self.ignored]
self.parser = ply.yacc.yacc(
module=self,
start='unit',
debug=yacc_debug,
optimize=yacc_optimize,
tabmodule=yacctab,
outputdir=outputdir
)
2012-02-12 11:50:57 +00:00
self.scope = scope if scope else Scope()
2012-01-28 14:52:09 +00:00
self.stash = {}
self.result = None
self.target = None
def parse(self, filename='', debuglevel=0):
""" Parse file.
@param string: Filename
@param int: Debuglevel
"""
2012-02-12 11:50:57 +00:00
self.scope.push()
2012-01-28 14:52:09 +00:00
self.target = filename
self.result = self.parser.parse(filename, lexer=self.lex, debug=debuglevel)
def scopemap(self):
""" Output scopemap.
"""
if self.result:
utility.print_recursive(self.result)
def p_unit(self, p):
""" unit : statement_list
"""
p[0] = p[1]
def p_statement_list_aux(self, p):
""" statement_list : statement_list statement
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_statement_list(self, p):
""" statement_list : statement
"""
p[0] = [p[1]]
def p_statement(self, p):
""" statement : block_decl
| variable_decl
| mixin_decl
"""
p[0] = p[1]
def p_statement_aux(self, p):
""" statement : css_charset css_string ';'
| css_namespace css_string ';'
"""
p[0] = Statement(p)
p[0].parse(None)
def p_statement_namespace(self, p):
""" statement : css_namespace css_ident css_string ';'
"""
p[0] = Statement(p)
p[0].parse(None)
def p_statement_import(self, p):
""" statement : css_import css_string ';'
"""
if self.importlvl > 8:
raise ImportError('Recrusive import level too deep > 8 (circular import ?)')
ipath = utility.destring(p[2])
fn, fe = os.path.splitext(ipath)
if not fe or fe.lower() == '.less':
try:
cpath = os.path.dirname(os.path.abspath(self.target))
if not fe: ipath += '.less'
filename = "%s%s%s" % (cpath, os.sep, ipath)
if os.path.exists(filename):
recurse = LessParser(importlvl=self.importlvl+1)
recurse.parse(filename=filename, debuglevel=0)
2012-02-12 12:11:15 +00:00
self.scope.update(recurse.scope)
2012-01-28 14:52:09 +00:00
else:
err = "Cannot import '%s', file not found" % filename
self.handle_error(err, p, 'W')
p[0] = None
except ImportError as e:
self.handle_error(e, p)
else:
p[0] = Statement(p)
p[0].parse(None)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_mixin_decl(self, p):
""" mixin_decl : block_open_mixin declaration_list brace_close
"""
try:
mixin = Mixin(p)
mixin.parse(self.scope, self.stash)
2012-02-12 12:11:15 +00:00
self.scope.add_mixin(mixin)
2012-01-28 14:52:09 +00:00
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_block_decl(self, p):
""" block_decl : block_open declaration_list brace_close
"""
try:
block = Block(p)
2012-02-12 12:00:56 +00:00
if not self.scope.in_mixin():
2012-02-12 10:11:04 +00:00
block.parse(self.scope)
2012-02-12 13:00:08 +00:00
self.scope.add_block(block)
2012-01-28 14:52:09 +00:00
p[0] = block
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_block_empty(self, p):
""" block_decl : block_open brace_close
"""
p[0] = None
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_block_open_mixin(self, p):
""" block_open_mixin : css_class t_popen block_mixin_args t_pclose brace_open
"""
2012-02-12 12:00:56 +00:00
self.scope.current = '__mixin__'
2012-01-28 14:52:09 +00:00
p[0] = list(p)[1:5]
def p_block_open_mixin_aux(self, p):
""" block_open_mixin : css_class t_popen less_arguments t_pclose brace_open
"""
2012-02-12 12:00:56 +00:00
self.scope.current = '__mixin__'
2012-01-28 14:52:09 +00:00
p[0] = list(p)[1:5]
def p_block_open_mixin_empty(self, p):
""" block_open_mixin : css_class t_popen t_pclose brace_open
"""
2012-02-12 12:00:56 +00:00
self.scope.current = '__mixin__'
2012-01-28 14:52:09 +00:00
p[0] = [p[1]]
def p_block_mixin_args_aux(self, p):
""" block_mixin_args : block_mixin_args ',' block_mixin_arg
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_block_mixin_args(self, p):
""" block_mixin_args : block_mixin_arg
"""
p[0] = [p[1]]
def p_block_mixin_arg_def(self, p):
""" block_mixin_arg : less_variable ':' block_mixin_factor
2012-02-18 12:18:55 +00:00
| less_variable ':' less_variable
2012-01-28 14:52:09 +00:00
"""
p[0] = list(p)[1:4]
def p_block_mixin_arg(self, p):
""" block_mixin_arg : block_mixin_factor
| less_variable
"""
p[0] = p[1]
def p_block_mixin_factor(self, p):
""" block_mixin_factor : css_number
| css_color
| css_ident
| css_string
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_block_open(self, p):
""" block_open : identifier_list brace_open
"""
name = ["%s " % t
if t in '>+'
else t
for t in utility.flatten(p[1])]
2012-02-12 12:00:56 +00:00
self.scope.current = ''.join(name).strip()
2012-01-28 14:52:09 +00:00
p[0] = p[1]
def p_identifier_list_mixin(self, p):
""" mixin : identifier_list ';'
"""
p[0] = p[1]
def p_identifier_list(self, p):
""" identifier_list : identifier_group
| identifier_page
| css_font_face
"""
if type(p[1]) is list:
p[0] = p[1]
else:
p[0] = [p[1]]
def p_identifier_page_aux(self, p):
""" identifier_page : identifier_page dom_filter
"""
p[1].extend(p[2])
p[0] = p[1]
def p_identifier_page(self, p):
""" identifier_page : css_page
"""
p[0] = [p[1]]
def p_identifier_group_op(self, p):
""" identifier_group : identifier_group ',' identifier
| identifier_group '+' identifier
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_identifier_group_aux(self, p):
""" identifier_group : identifier_group identifier
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_identifier_group(self, p):
""" identifier_group : identifier
"""
p[0] = [p[1]]
def p_identifier_group_media(self, p):
""" identifier_group : css_media
"""
p[0] = [p[1]]
def p_identifier(self, p):
""" identifier : css_dom
| css_id
| css_class
| dom_filter
| filter_group
2012-01-28 14:52:09 +00:00
| css_color
| less_combine
| '*'
| '>'
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_declaration_list_aux(self, p):
""" declaration_list : declaration_list declaration
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_declaration_list(self, p):
""" declaration_list : declaration
"""
p[0] = [p[1]]
def p_declaration(self, p):
""" declaration : property_decl
"""
p[0] = p[1]
def p_declaration_block(self, p):
""" declaration : block_decl
| variable_decl
"""
p[0] = p[1]
def p_variable_decl(self, p):
""" variable_decl : less_variable ':' style_list ';'
"""
try:
v = Variable(p)
v.parse(self.scope)
2012-02-12 12:00:56 +00:00
if self.scope.current == '__mixin__':
2012-01-28 14:52:09 +00:00
self.stash[v.name()] = v
else:
2012-02-12 13:00:08 +00:00
self.scope.add_variable(v)
2012-01-28 14:52:09 +00:00
except SyntaxError as e:
2012-01-30 12:56:33 +00:00
self.handle_error(e, p)
2012-01-28 14:52:09 +00:00
p[0] = None
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_property_mixin_call(self, p):
""" property_decl : identifier_list t_popen argument_list t_pclose ';'
"""
n = p[1][0]
2012-02-12 12:11:15 +00:00
mixin = self.scope.mixins(n)
2012-02-12 11:50:57 +00:00
if mixin:
2012-02-12 12:00:56 +00:00
if not self.scope.in_mixin():
2012-01-28 14:52:09 +00:00
try:
2012-02-12 11:50:57 +00:00
p[0] = mixin.call(p[3], self.scope)
2012-01-28 14:52:09 +00:00
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
else:
2012-02-12 11:50:57 +00:00
p[0] = mixin
2012-01-28 14:52:09 +00:00
else:
self.handle_error('Mixin not found in scope: ´%s´' % n, p)
p[0] = None
def p_property_mixin_call_empty(self, p):
""" property_decl : identifier_list t_popen t_pclose ';'
"""
n = p[1][0]
2012-02-12 12:11:15 +00:00
mixin = self.scope.mixins(n)
2012-02-12 11:50:57 +00:00
if mixin:
2012-01-28 14:52:09 +00:00
try:
2012-02-17 09:39:10 +00:00
p[0] = mixin.call(None, self.scope)
2012-01-28 14:52:09 +00:00
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
else:
p[0] = None
def p_property_mixin(self, p):
""" property_decl : mixin
"""
m = ''.join([u.strip() for u in p[1]])
l = utility.block_search(m, self.scope)
2012-02-12 12:11:15 +00:00
mixin = self.scope.mixins(m)
2012-01-28 14:52:09 +00:00
if l:
p[0] = [b.parsed['proplist'] for b in l]
2012-02-12 11:50:57 +00:00
elif mixin:
2012-01-28 14:52:09 +00:00
try:
2012-02-17 09:39:10 +00:00
p[0] = mixin.call(None, self.scope)
2012-01-28 14:52:09 +00:00
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
else:
p[0] = []
def p_property_decl(self, p):
""" property_decl : property ':' style_list ';'
| property ':' style_list
"""
p[0] = Property(p)
2012-02-12 12:00:56 +00:00
if not self.scope.in_mixin():
2012-01-28 14:52:09 +00:00
try:
p[0].parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_prop_decl_bad(self, p):
""" property_decl : property ':' ';'
"""
p[0] = None
2012-02-18 11:53:06 +00:00
def p_property_ie_hack(self, p):
""" property : '*' property
"""
p[0] = "%s%s" % (p[1], p[2])
2012-01-28 14:52:09 +00:00
def p_property(self, p):
""" property : css_property
| css_vendor_property
| css_ident
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_style_list(self, p):
""" style_list : style_group
"""
p[0] = p[1]
def p_less_style_list(self, p):
""" style_list : less_arguments
"""
p[0] = p[1]
def p_style_group_sep(self, p):
""" style_group : style_group ',' style
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_style_group_aux(self, p):
""" style_group : style_group style
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_style_group(self, p):
""" style_group : style
"""
p[0] = [p[1]]
def p_style(self, p):
""" style : expression
| css_important
| css_string
| istring
| css_vendor_property
| css_property
| css_ident
"""
p[0] = p[1]
def p_style_escape(self, p):
""" style : '~' istring
| '~' css_string
"""
2012-02-12 10:11:04 +00:00
p[0] = Call(p)
2012-01-28 14:52:09 +00:00
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_dom_filter(self, p):
""" dom_filter : css_dom filter_group
| css_id filter_group
| css_class filter_group
| less_combine filter_group
"""
p[0] = [p[1], p[2]]
2012-01-28 14:52:09 +00:00
def p_filter_group_aux(self, p):
""" filter_group : filter filter
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_filter_group(self, p):
""" filter_group : filter
"""
p[0] = [p[1]]
def p_filter(self, p):
""" filter : css_filter
| ':' css_ident
| ':' css_vendor_property
2012-01-28 14:52:09 +00:00
| ':' css_filter
| ':' ':' css_ident
| ':' ':' css_vendor_property
2012-01-28 14:52:09 +00:00
"""
p[0] = list(p)[1:]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_expression_aux(self, p):
'''expression : expression '+' expression
| expression '-' expression
| expression '*' expression
| expression '/' expression
'''
2012-01-30 12:56:33 +00:00
p[0] = Expression(p)
2012-01-28 14:52:09 +00:00
def p_expression_p_neg(self, p):
""" expression : '-' t_popen expression t_pclose
"""
p[0] = [p[1], p[3]]
def p_expression_p(self, p):
""" expression : t_popen expression t_pclose
"""
p[0] = p[2]
def p_expression(self, p):
""" expression : factor
"""
p[0] = p[1]
def p_factor(self, p):
"""factor : color
| number
| variable
| css_dom
| fcall
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_fcall(self, p):
""" fcall : css_ident t_popen argument_list t_pclose
| css_property t_popen argument_list t_pclose
| css_vendor_property t_popen argument_list t_pclose
2012-02-12 10:11:04 +00:00
| less_open_format argument_list t_pclose
2012-01-28 14:52:09 +00:00
"""
p[0] = Call(p)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_argument_list_aux_1(self, p):
""" argument_list : argument_list ',' argument
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_argument_list_aux(self, p):
""" argument_list : argument_list argument
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_argument_list(self, p):
""" argument_list : argument
"""
p[0] = [p[1]]
def p_argument(self, p):
""" argument : expression
| css_string
| istring
| css_ident
| css_id
| css_uri
| '='
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_interpolated_str(self, p):
""" istring : less_string
"""
2012-02-12 10:11:04 +00:00
p[0] = String(p)
2012-01-28 14:52:09 +00:00
def p_variable_neg(self, p):
""" variable : '-' variable
"""
p[0] = '-' + p[2]
def p_variable_strange(self, p):
""" variable : t_popen variable t_pclose
"""
p[0] = p[2]
def p_variable(self, p):
""" variable : less_variable
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_color(self, p):
""" color : css_color
"""
p[0] = LessColor().format(p[1])
def p_number(self, p):
""" number : css_number
| css_number_unit
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_scope_open(self, p):
""" brace_open : '{'
"""
2012-02-12 11:50:57 +00:00
self.scope.push()
2012-01-28 14:52:09 +00:00
p[0] = p[1]
def p_scope_close(self, p):
""" brace_close : '}'
"""
self.scope.pop()
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_error(self, t):
""" Internal error handler
@param Lex token: Error token
"""
2012-01-30 12:56:33 +00:00
if t and self.verbose:
2012-01-30 13:54:31 +00:00
print("E: %s line: %d, Syntax Error, token: `%s`, `%s`"
% (self.target, t.lineno, t.type, t.value))
2012-01-28 14:52:09 +00:00
while True:
t = self.lex.token()
if not t or t.value == '}':
if len(self.scope) > 1:
self.scope.pop()
break
self.parser.restart()
return t
def handle_error(self, e, p, t='E'):
""" Custom error handler
@param Exception: Exception
@param Parser token: Parser token
@param string: Error level
"""
l = [n for n in [p.lineno(i) for i in range(len(p))] if n]
l = l[0] if l else -1
2012-01-30 12:56:33 +00:00
if self.verbose:
print("%s: line: %d: " % (t, l), end='')
print(e)
2012-02-11 18:02:08 +00:00