diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index d5dfae48b5..b8cfaf10e4 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -205,6 +205,13 @@ class _Parser(object): except: return False + def token_is_call(self): + try: + token = self.prog[self.lex_pos] + return token[1] == 'call' and token[0] == self.LEX_KEYWORD + except: + return False + def token_is_if(self): try: token = self.prog[self.lex_pos] @@ -249,7 +256,7 @@ class _Parser(object): self.lex_pos = 0 self.parent = parent self.funcs = funcs - self.func_names = frozenset(set(self.funcs.keys()) | {'call', 'arguments'}) + self.func_names = frozenset(set(self.funcs.keys()) | {'arguments',}) self.prog = prog[0] self.prog_len = len(self.prog) if prog[1] != '': @@ -295,9 +302,37 @@ class _Parser(object): return NumericInfixNode(operator, left, self.expr()) return left + def call_expression(self): + self.consume() + if not self.token_is_id(): + self.error(_('"call" requires a stored template name')) + name = self.token() + if name not in self.func_names or self.funcs[name].is_python: + self.error(_('{} is not a stored template').format(name)) + text = self.funcs[name].program_text + if not text.startswith('program:'): + self.error((_('A stored template must begin with program:'))) + 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) + def expr(self): if self.token_is_if(): return self.if_expression() + if self.token_is_call(): + return self.call_expression() if self.token_is_id(): # We have an identifier. Determine if it is a function id_ = self.token() @@ -333,19 +368,6 @@ class _Parser(object): return IfNode(arguments[0], (arguments[1],), (arguments[2],)) if (id_ == 'assign' and len(arguments) == 2 and arguments[0].node_type == Node.NODE_RVALUE): return AssignNode(arguments[0].name, arguments[1]) - if id_ == 'call': - if arguments[0].node_type != Node.NODE_RVALUE: - self.error('The function name in a call statement must not be quoted') - name = arguments[0].name - if name not in self.func_names or self.funcs[name].is_python: - self.error(_('{} is not a stored template').format(name)) - text = self.funcs[name].program_text - if not text.startswith('program:'): - self.error((_('A stored template must begin with program:'))) - text = text[len('program:'):] - subprog = _Parser().program(self, self.funcs, - self.parent.lex_scanner.scan(text)) - return CallNode(subprog, arguments[1:]) if id_ == 'arguments': new_args = [] for arg in arguments: @@ -598,7 +620,7 @@ class TemplateFormatter(string.Formatter): lex_scanner = re.Scanner([ (r'(==#|!=#|<=#|<#|>=#|>#)', lambda x,t: (_Parser.LEX_NUMERIC_INFIX, t)), (r'(==|!=|<=|<|>=|>)', lambda x,t: (_Parser.LEX_STRING_INFIX, t)), # noqa - (r'(if|then|else|fi)\b', lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa + (r'(if|then|else|fi|call)\b',lambda x,t: (_Parser.LEX_KEYWORD, t)), # noqa (r'[(),=;]', lambda x,t: (_Parser.LEX_OP, t)), # noqa (r'-?[\d\.]+', lambda x,t: (_Parser.LEX_CONST, t)), # noqa (r'\$', lambda x,t: (_Parser.LEX_ID, t)), # noqa