mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
1) cached 'compiled' stored templates
2) change call syntax so stored templates are usable in single function mode
This commit is contained in:
parent
b208241eba
commit
ac50f0afaf
@ -302,37 +302,21 @@ class _Parser(object):
|
|||||||
return NumericInfixNode(operator, left, self.expr())
|
return NumericInfixNode(operator, left, self.expr())
|
||||||
return left
|
return left
|
||||||
|
|
||||||
def call_expression(self):
|
def call_expression(self, name, arguments):
|
||||||
self.consume()
|
subprog = self.funcs[name].cached_parse_tree
|
||||||
if not self.token_is_id():
|
if subprog is None:
|
||||||
self.error(_('"call" requires a stored template name'))
|
text = self.funcs[name].program_text
|
||||||
name = self.token()
|
if not text.startswith('program:'):
|
||||||
if name not in self.func_names or self.funcs[name].is_python:
|
self.error((_('A stored template must begin with program:')))
|
||||||
self.error(_('{} is not a stored template').format(name))
|
text = text[len('program:'):]
|
||||||
text = self.funcs[name].program_text
|
subprog = _Parser().program(self, self.funcs,
|
||||||
if not text.startswith('program:'):
|
self.parent.lex_scanner.scan(text))
|
||||||
self.error((_('A stored template must begin with program:')))
|
self.funcs[name].cached_parse_tree = subprog
|
||||||
text = text[len('program:'):]
|
|
||||||
if not self.token_op_is_lparen():
|
|
||||||
self.error(_('"call" requires arguments surrounded by "(" ")"'))
|
|
||||||
self.consume()
|
|
||||||
arguments = list()
|
|
||||||
while not self.token_op_is_rparen():
|
|
||||||
arguments.append(self.infix_expr())
|
|
||||||
if not self.token_op_is_comma():
|
|
||||||
break
|
|
||||||
self.consume()
|
|
||||||
if self.token() != ')':
|
|
||||||
self.error(_('Missing closing parenthesis'))
|
|
||||||
subprog = _Parser().program(self, self.funcs,
|
|
||||||
self.parent.lex_scanner.scan(text))
|
|
||||||
return CallNode(subprog, arguments)
|
return CallNode(subprog, arguments)
|
||||||
|
|
||||||
def expr(self):
|
def expr(self):
|
||||||
if self.token_is_if():
|
if self.token_is_if():
|
||||||
return self.if_expression()
|
return self.if_expression()
|
||||||
if self.token_is_call():
|
|
||||||
return self.call_expression()
|
|
||||||
if self.token_is_id():
|
if self.token_is_id():
|
||||||
# We have an identifier. Determine if it is a function
|
# We have an identifier. Determine if it is a function
|
||||||
id_ = self.token()
|
id_ = self.token()
|
||||||
@ -378,6 +362,8 @@ class _Parser(object):
|
|||||||
arg = AssignNode(arg.name, ConstantNode(''))
|
arg = AssignNode(arg.name, ConstantNode(''))
|
||||||
new_args.append(arg)
|
new_args.append(arg)
|
||||||
return ArgumentsNode(new_args)
|
return ArgumentsNode(new_args)
|
||||||
|
if id_ in self.func_names and not self.funcs[id_].is_python:
|
||||||
|
return self.call_expression(id_, arguments)
|
||||||
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_))
|
||||||
@ -394,12 +380,14 @@ class _Interpreter(object):
|
|||||||
m = 'Interpreter: ' + message
|
m = 'Interpreter: ' + message
|
||||||
raise ValueError(m)
|
raise ValueError(m)
|
||||||
|
|
||||||
def program(self, funcs, parent, prog, val):
|
def program(self, funcs, parent, prog, val, is_call=False, args=None):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.parent_kwargs = parent.kwargs
|
self.parent_kwargs = parent.kwargs
|
||||||
self.parent_book = parent.book
|
self.parent_book = parent.book
|
||||||
self.funcs = funcs
|
self.funcs = funcs
|
||||||
self.locals = {'$':val}
|
self.locals = {'$':val}
|
||||||
|
if is_call:
|
||||||
|
return self.do_node_call(CallNode(prog, None), args=args)
|
||||||
return self.expression_list(prog)
|
return self.expression_list(prog)
|
||||||
|
|
||||||
def expression_list(self, prog):
|
def expression_list(self, prog):
|
||||||
@ -472,11 +460,12 @@ class _Interpreter(object):
|
|||||||
return cls.eval_(self.parent, self.parent_kwargs,
|
return cls.eval_(self.parent, self.parent_kwargs,
|
||||||
self.parent_book, self.locals, *args)
|
self.parent_book, self.locals, *args)
|
||||||
|
|
||||||
def do_node_call(self, prog):
|
def do_node_call(self, prog, args=None):
|
||||||
args = list()
|
if args is None:
|
||||||
for arg in prog.expression_list:
|
args = list()
|
||||||
# evaluate the expression (recursive call)
|
for arg in prog.expression_list:
|
||||||
args.append(self.expr(arg))
|
# evaluate the expression (recursive call)
|
||||||
|
args.append(self.expr(arg))
|
||||||
saved_locals = self.locals
|
saved_locals = self.locals
|
||||||
self.locals = {}
|
self.locals = {}
|
||||||
for dex, v in enumerate(args):
|
for dex, v in enumerate(args):
|
||||||
@ -620,7 +609,7 @@ class TemplateFormatter(string.Formatter):
|
|||||||
lex_scanner = re.Scanner([
|
lex_scanner = re.Scanner([
|
||||||
(r'(==#|!=#|<=#|<#|>=#|>#)', lambda x,t: (_Parser.LEX_NUMERIC_INFIX, t)),
|
(r'(==#|!=#|<=#|<#|>=#|>#)', lambda x,t: (_Parser.LEX_NUMERIC_INFIX, t)),
|
||||||
(r'(==|!=|<=|<|>=|>)', lambda x,t: (_Parser.LEX_STRING_INFIX, t)), # noqa
|
(r'(==|!=|<=|<|>=|>)', lambda x,t: (_Parser.LEX_STRING_INFIX, t)), # noqa
|
||||||
(r'(if|then|else|fi|call)\b',lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa
|
(r'(if|then|else|fi)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa
|
||||||
(r'[(),=;]', lambda x,t: (_Parser.LEX_OP, t)), # noqa
|
(r'[(),=;]', lambda x,t: (_Parser.LEX_OP, t)), # noqa
|
||||||
(r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa
|
(r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa
|
||||||
(r'\$', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
(r'\$', lambda x,t: (_Parser.LEX_ID, t)), # noqa
|
||||||
@ -632,9 +621,6 @@ class TemplateFormatter(string.Formatter):
|
|||||||
], flags=re.DOTALL)
|
], flags=re.DOTALL)
|
||||||
|
|
||||||
def _eval_program(self, val, prog, column_name):
|
def _eval_program(self, val, prog, column_name):
|
||||||
# keep a cache of the lex'ed program under the theory that re-lexing
|
|
||||||
# is much more expensive than the cache lookup. This is certainly true
|
|
||||||
# for more than a few tokens, but it isn't clear for simple programs.
|
|
||||||
if column_name is not None and self.template_cache is not None:
|
if column_name is not None and self.template_cache is not None:
|
||||||
tree = self.template_cache.get(column_name, None)
|
tree = self.template_cache.get(column_name, None)
|
||||||
if not tree:
|
if not tree:
|
||||||
@ -644,6 +630,15 @@ class TemplateFormatter(string.Formatter):
|
|||||||
tree = self.gpm_parser.program(self, self.funcs, self.lex_scanner.scan(prog))
|
tree = self.gpm_parser.program(self, self.funcs, self.lex_scanner.scan(prog))
|
||||||
return self.gpm_interpreter.program(self.funcs, self, tree, val)
|
return self.gpm_interpreter.program(self.funcs, self, tree, val)
|
||||||
|
|
||||||
|
def _eval_sfm_call(self, template_name, args):
|
||||||
|
func = self.funcs[template_name]
|
||||||
|
tree = func.cached_parse_tree
|
||||||
|
if tree is None:
|
||||||
|
tree = self.gpm_parser.program(self, self.funcs,
|
||||||
|
self.lex_scanner.scan(func.program_text[len('program:'):]))
|
||||||
|
func.cached_parse_tree = tree
|
||||||
|
return self.gpm_interpreter.program(self.funcs, self, tree, None,
|
||||||
|
is_call=True, args=args)
|
||||||
# ################# Override parent classes methods #####################
|
# ################# Override parent classes methods #####################
|
||||||
|
|
||||||
def get_value(self, key, args, kwargs):
|
def get_value(self, key, args, kwargs):
|
||||||
@ -697,17 +692,22 @@ class TemplateFormatter(string.Formatter):
|
|||||||
else:
|
else:
|
||||||
args = self.arg_parser.scan(fmt[p+1:])[0]
|
args = self.arg_parser.scan(fmt[p+1:])[0]
|
||||||
args = [self.backslash_comma_to_comma.sub(',', a) for a in args]
|
args = [self.backslash_comma_to_comma.sub(',', a) for a in args]
|
||||||
if (func.arg_count == 1 and (len(args) != 1 or args[0])) or \
|
if not func.is_python:
|
||||||
(func.arg_count > 1 and func.arg_count != len(args)+1):
|
args.insert(0, val)
|
||||||
raise ValueError('Incorrect number of expression_list for function '+ fmt[0:p])
|
val = self._eval_sfm_call(fname, args)
|
||||||
if func.arg_count == 1:
|
|
||||||
val = func.eval_(self, self.kwargs, self.book, self.locals, val)
|
|
||||||
if self.strip_results:
|
|
||||||
val = val.strip()
|
|
||||||
else:
|
else:
|
||||||
val = func.eval_(self, self.kwargs, self.book, self.locals, val, *args)
|
if (func.arg_count == 1 and (len(args) != 1 or args[0])) or \
|
||||||
if self.strip_results:
|
(func.arg_count > 1 and func.arg_count != len(args)+1):
|
||||||
val = val.strip()
|
raise ValueError(
|
||||||
|
_('Incorrect number of arguments for function {0}').format(fmt[0:p]))
|
||||||
|
if func.arg_count == 1:
|
||||||
|
val = func.eval_(self, self.kwargs, self.book, self.locals, val)
|
||||||
|
if self.strip_results:
|
||||||
|
val = val.strip()
|
||||||
|
else:
|
||||||
|
val = func.eval_(self, self.kwargs, self.book, self.locals, val, *args)
|
||||||
|
if self.strip_results:
|
||||||
|
val = val.strip()
|
||||||
else:
|
else:
|
||||||
return _('%s: unknown function')%fname
|
return _('%s: unknown function')%fname
|
||||||
if val:
|
if val:
|
||||||
|
@ -1820,6 +1820,7 @@ class FormatterUserFunction(FormatterFunction):
|
|||||||
self.doc = doc
|
self.doc = doc
|
||||||
self.arg_count = arg_count
|
self.arg_count = arg_count
|
||||||
self.program_text = program_text
|
self.program_text = program_text
|
||||||
|
self.cached_parse_tree = None
|
||||||
|
|
||||||
def to_pref(self):
|
def to_pref(self):
|
||||||
return [self.name, self.doc, self.arg_count, self.program_text]
|
return [self.name, self.doc, self.arg_count, self.program_text]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user