mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Formatter changes:
1) Fix a regression that broke specifying a separator for a 'for' statement 2) When parsing, use a table lookup instead of a series of ifs 3) Add a function 'newline()' that returns a newline. Intended to be used with strcat(). 4) Better comments
This commit is contained in:
parent
37a13064eb
commit
cc022c6eb9
@ -665,9 +665,10 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
v = SafeFormat().safe_format(txt, mi, _('EXCEPTION: '),
|
v = SafeFormat().safe_format(txt, mi, _('EXCEPTION: '),
|
||||||
mi, global_vars=self.global_vars,
|
mi, global_vars=self.global_vars,
|
||||||
template_functions=self.all_functions,
|
template_functions=self.all_functions,
|
||||||
|
strip_results=False,
|
||||||
break_reporter=self.break_reporter if r == break_on_mi else None)
|
break_reporter=self.break_reporter if r == break_on_mi else None)
|
||||||
w = tv.cellWidget(r, 1)
|
w = tv.cellWidget(r, 1)
|
||||||
w.setText(v)
|
w.setText(v.replace('\n', '\\n'))
|
||||||
w.setCursorPosition(0)
|
w.setCursorPosition(0)
|
||||||
|
|
||||||
def text_cursor_changed(self):
|
def text_cursor_changed(self):
|
||||||
@ -758,7 +759,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
class BreakReporterItem(QTableWidgetItem):
|
class BreakReporterItem(QTableWidgetItem):
|
||||||
|
|
||||||
def __init__(self, txt):
|
def __init__(self, txt):
|
||||||
super().__init__(txt)
|
super().__init__(txt.replace('\n', '\\n') if txt else txt)
|
||||||
self.setFlags(self.flags() & ~(Qt.ItemFlag.ItemIsEditable|Qt.ItemFlag.ItemIsSelectable))
|
self.setFlags(self.flags() & ~(Qt.ItemFlag.ItemIsEditable|Qt.ItemFlag.ItemIsSelectable))
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,14 +332,6 @@ class _Parser(object):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def token_is_separator(self):
|
|
||||||
self.check_eol()
|
|
||||||
try:
|
|
||||||
token = self.prog[self.lex_pos]
|
|
||||||
return token[1] == 'separator' and token[0] == self.LEX_ID
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def token_is_constant(self):
|
def token_is_constant(self):
|
||||||
self.check_eol()
|
self.check_eol()
|
||||||
try:
|
try:
|
||||||
@ -366,7 +358,7 @@ class _Parser(object):
|
|||||||
self.lex_pos = 0
|
self.lex_pos = 0
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.funcs = funcs
|
self.funcs = funcs
|
||||||
self.func_names = frozenset(set(self.funcs.keys()))
|
self.func_names = frozenset(set(self.funcs.keys()) | set(('newline',)))
|
||||||
self.prog = prog[0]
|
self.prog = prog[0]
|
||||||
self.prog_len = len(self.prog)
|
self.prog_len = len(self.prog)
|
||||||
if prog[1] != '':
|
if prog[1] != '':
|
||||||
@ -424,7 +416,7 @@ class _Parser(object):
|
|||||||
"found '{2}'").format('for', 'in', self.token_text()))
|
"found '{2}'").format('for', 'in', self.token_text()))
|
||||||
self.consume()
|
self.consume()
|
||||||
list_expr = self.top_expr()
|
list_expr = self.top_expr()
|
||||||
if self.token_is_separator():
|
if self.token_is('separator'):
|
||||||
self.consume()
|
self.consume()
|
||||||
separator = self.expr()
|
separator = self.expr()
|
||||||
else:
|
else:
|
||||||
@ -513,6 +505,34 @@ class _Parser(object):
|
|||||||
self.funcs[name].cached_parse_tree = subprog
|
self.funcs[name].cached_parse_tree = subprog
|
||||||
return CallNode(self.line_number, name, subprog, arguments)
|
return CallNode(self.line_number, name, subprog, arguments)
|
||||||
|
|
||||||
|
# {keyword: tuple(preprocessor, node builder) }
|
||||||
|
keyword_nodes = {
|
||||||
|
'if': (lambda self:None, if_expression),
|
||||||
|
'for': (lambda self:None, for_expression),
|
||||||
|
'break': (lambda self: self.consume(), lambda self: BreakNode(self.line_number)),
|
||||||
|
'continue': (lambda self: self.consume(), lambda self: ContinueNode(self.line_number)),
|
||||||
|
'return': (lambda self: self.consume(), lambda self: ReturnNode(self.line_number, self.expr())),
|
||||||
|
}
|
||||||
|
|
||||||
|
# {inlined_function_name: tuple(constraint on number of length, node builder) }
|
||||||
|
inlined_function_nodes = {
|
||||||
|
'newline': (lambda args: len(args) == 0,
|
||||||
|
lambda ln, _: ConstantNode(ln, '\n')),
|
||||||
|
'field': (lambda args: len(args) == 1,
|
||||||
|
lambda ln, args: FieldNode(ln, args[0])),
|
||||||
|
'raw_field': (lambda args: len(args) == 1,
|
||||||
|
lambda ln, args: RawFieldNode(ln, *args)),
|
||||||
|
'test': (lambda args: len(args) == 1,
|
||||||
|
lambda ln, args: IfNode(ln, args[0], (args[1],), (args[2],))),
|
||||||
|
'first_non_empty': (lambda args: len(args) == 1,
|
||||||
|
lambda ln, args: FirstNonEmptyNode(ln, args)),
|
||||||
|
'assign': (lambda args: len(args) == 2 and args[0].node_type == Node.NODE_RVALUE,
|
||||||
|
lambda ln, args: AssignNode(ln, args[0].name, args[1])),
|
||||||
|
'contains': (lambda args: len(args) == 4,
|
||||||
|
lambda ln, args: ContainsNode(ln, args)),
|
||||||
|
'print': (lambda _: True,
|
||||||
|
lambda ln, args: PrintNode(ln, args)),
|
||||||
|
}
|
||||||
def expr(self):
|
def expr(self):
|
||||||
if self.token_op_is('('):
|
if self.token_op_is('('):
|
||||||
self.consume()
|
self.consume()
|
||||||
@ -521,29 +541,29 @@ class _Parser(object):
|
|||||||
self.error(_("Expected '{0}', found '{1}'").format(')', self.token_text()))
|
self.error(_("Expected '{0}', found '{1}'").format(')', self.token_text()))
|
||||||
self.consume()
|
self.consume()
|
||||||
return rv
|
return rv
|
||||||
if self.token_is('if'):
|
|
||||||
return self.if_expression()
|
# Check if we have a keyword-type expression
|
||||||
if self.token_is('for'):
|
t = self.token_text()
|
||||||
return self.for_expression()
|
kw_tuple = self.keyword_nodes.get(t, None)
|
||||||
if self.token_is('break'):
|
if kw_tuple:
|
||||||
self.consume()
|
# These are keywords, so there can't be ambiguity between these, ids,
|
||||||
return BreakNode(self.line_number)
|
# and functions.
|
||||||
if self.token_is('continue'):
|
kw_tuple[0](self)
|
||||||
self.consume()
|
return kw_tuple[1](self)
|
||||||
return ContinueNode(self.line_number)
|
|
||||||
if self.token_is('return'):
|
# Not a keyword. Check if we have an id reference or a function call
|
||||||
self.consume()
|
|
||||||
return ReturnNode(self.line_number, self.expr())
|
|
||||||
if self.token_is_id():
|
if self.token_is_id():
|
||||||
|
# We have an identifier. Check if it is a shorthand field reference
|
||||||
line_number = self.line_number
|
line_number = self.line_number
|
||||||
id_ = self.token()
|
id_ = self.token()
|
||||||
# We have an identifier. Check if it is a field reference
|
|
||||||
if len(id_) > 1 and id_[0] == '$':
|
if len(id_) > 1 and id_[0] == '$':
|
||||||
if id_[1] == '$':
|
if id_[1] == '$':
|
||||||
return RawFieldNode(line_number, ConstantNode(self.line_number, id_[2:]))
|
return RawFieldNode(line_number, ConstantNode(self.line_number, id_[2:]))
|
||||||
return FieldNode(line_number, ConstantNode(self.line_number, id_[1:]))
|
return FieldNode(line_number, ConstantNode(self.line_number, id_[1:]))
|
||||||
# Determine if it is a function
|
|
||||||
|
# Do we have a function call?
|
||||||
if not self.token_op_is('('):
|
if not self.token_op_is('('):
|
||||||
|
# Nope. We must have an lvalue (identifier) or an assignment
|
||||||
if self.token_op_is('='):
|
if self.token_op_is('='):
|
||||||
# classic assignment statement
|
# classic assignment statement
|
||||||
self.consume()
|
self.consume()
|
||||||
@ -556,28 +576,26 @@ class _Parser(object):
|
|||||||
id_ = id_.strip()
|
id_ = id_.strip()
|
||||||
if id_ not in self.func_names:
|
if id_ not in self.func_names:
|
||||||
self.error(_('Unknown function {0}').format(id_))
|
self.error(_('Unknown function {0}').format(id_))
|
||||||
# Eat the paren
|
|
||||||
|
# Eat the opening paren, parse the argument list, then eat the closing paren
|
||||||
self.consume()
|
self.consume()
|
||||||
arguments = list()
|
arguments = list()
|
||||||
while not self.token_op_is(')'):
|
while not self.token_op_is(')'):
|
||||||
# evaluate the expression (recursive call)
|
# parse an argument expression (recursive call)
|
||||||
arguments.append(self.expression_list())
|
arguments.append(self.expression_list())
|
||||||
if not self.token_op_is(','):
|
if not self.token_op_is(','):
|
||||||
break
|
break
|
||||||
self.consume()
|
self.consume()
|
||||||
if self.token() != ')':
|
t = self.token()
|
||||||
|
if t != ')':
|
||||||
self.error(_("Expected a '{0}' for function call, "
|
self.error(_("Expected a '{0}' for function call, "
|
||||||
"found '{1}'").format(')', self.token_text()))
|
"found '{1}'").format(')', t))
|
||||||
if id_ == 'field' and len(arguments) == 1:
|
|
||||||
return FieldNode(line_number, arguments[0])
|
# Check for an inlined function
|
||||||
if id_ == 'raw_field' and (len(arguments) in (1, 2)):
|
function_tuple = self.inlined_function_nodes.get(id_, None)
|
||||||
return RawFieldNode(line_number, *arguments)
|
if function_tuple and function_tuple[0](arguments):
|
||||||
if id_ == 'test' and len(arguments) == 3:
|
return function_tuple[1](line_number, arguments)
|
||||||
return IfNode(line_number, arguments[0], (arguments[1],), (arguments[2],))
|
# More complicated special cases
|
||||||
if id_ == 'first_non_empty' and len(arguments) > 0:
|
|
||||||
return FirstNonEmptyNode(line_number, arguments)
|
|
||||||
if (id_ == 'assign' and len(arguments) == 2 and arguments[0].node_type == Node.NODE_RVALUE):
|
|
||||||
return AssignNode(line_number, arguments[0].name, arguments[1])
|
|
||||||
if id_ == 'arguments' or id_ == 'globals' or id_ == 'set_globals':
|
if id_ == 'arguments' or id_ == 'globals' or id_ == 'set_globals':
|
||||||
new_args = []
|
new_args = []
|
||||||
for arg_list in arguments:
|
for arg_list in arguments:
|
||||||
@ -593,12 +611,11 @@ class _Parser(object):
|
|||||||
if id_ == 'set_globals':
|
if id_ == 'set_globals':
|
||||||
return SetGlobalsNode(line_number, new_args)
|
return SetGlobalsNode(line_number, new_args)
|
||||||
return GlobalsNode(line_number, new_args)
|
return GlobalsNode(line_number, new_args)
|
||||||
if id_ == 'contains' and len(arguments) == 4:
|
# Check for calling a stored template
|
||||||
return ContainsNode(line_number, arguments)
|
|
||||||
if id_ == 'print':
|
|
||||||
return PrintNode(line_number, arguments)
|
|
||||||
if id_ in self.func_names and not self.funcs[id_].is_python:
|
if id_ in self.func_names and not self.funcs[id_].is_python:
|
||||||
return self.call_expression(id_, arguments)
|
return self.call_expression(id_, arguments)
|
||||||
|
# We must have a reference to a formatter function. Check if
|
||||||
|
# the right number of arguments were supplied
|
||||||
cls = self.funcs[id_]
|
cls = self.funcs[id_]
|
||||||
if cls.arg_count != -1 and len(arguments) != cls.arg_count:
|
if cls.arg_count != -1 and len(arguments) != cls.arg_count:
|
||||||
self.error(_('Incorrect number of arguments for function {0}').format(id_))
|
self.error(_('Incorrect number of arguments for function {0}').format(id_))
|
||||||
@ -607,6 +624,7 @@ class _Parser(object):
|
|||||||
# String or number
|
# String or number
|
||||||
return ConstantNode(self.line_number, self.token())
|
return ConstantNode(self.line_number, self.token())
|
||||||
else:
|
else:
|
||||||
|
# Who knows what?
|
||||||
self.error(_("Expected an expression, found '{0}'").format(self.token_text()))
|
self.error(_("Expected an expression, found '{0}'").format(self.token_text()))
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user