1# -*- coding: utf-8 -*-
2# Copyright (C) 2017      by Juancarlo Añez
3# Copyright (C) 2012-2016 by Juancarlo Añez and Thomas Bragg
4"""
5Python code generation for models defined with grako.model
6"""
7from __future__ import absolute_import, division, print_function, unicode_literals
8
9from grako.util import (
10    indent,
11    safe_name,
12    trim,
13    timestamp,
14    urepr,
15    ustr,
16    compress_seq
17)
18from grako.exceptions import CodegenError
19from grako.objectmodel import Node
20from grako.objectmodel import BASE_CLASS_TOKEN
21from grako.codegen.cgbase import ModelRenderer, CodeGenerator
22
23
24class PythonCodeGenerator(CodeGenerator):
25    def _find_renderer_class(self, item):
26        if not isinstance(item, Node):
27            return None
28
29        name = item.__class__.__name__
30        renderer = globals().get(name, None)
31        if not renderer or not issubclass(renderer, Base):
32            raise CodegenError('Renderer for %s not found' % name)
33        return renderer
34
35
36def codegen(model):
37    return PythonCodeGenerator().render(model)
38
39
40class Base(ModelRenderer):
41    def defines(self):
42        return self.node.defines()
43
44
45class Void(Base):
46    template = 'self._void()'
47
48
49class Fail(Base):
50    template = 'self._fail()'
51
52
53class Comment(Base):
54    def render_fields(self, fields):
55        lines = '\n'.join(
56            '# %s' % ustr(c) for c in self.node.comment.splitlines()
57        )
58        fields.update(lines=lines)
59
60    template = '\n{lines}\n'
61
62
63class EOLComment(Comment):
64    pass
65
66
67class EOF(Base):
68    template = 'self._check_eof()'
69
70
71class _Decorator(Base):
72    template = '{exp}'
73
74
75class Group(_Decorator):
76    template = '''\
77                with self._group():
78                {exp:1::}\
79                '''
80
81
82class Token(Base):
83    def render_fields(self, fields):
84        fields.update(token=urepr(self.node.token))
85
86    template = "self._token({token})"
87
88
89class Constant(Base):
90    def render_fields(self, fields):
91        fields.update(literal=urepr(self.node.literal))
92
93    template = "self._constant({literal})"
94
95
96class Pattern(Base):
97    def render_fields(self, fields):
98        raw_repr = 'r' + urepr(self.node.pattern).replace("\\\\", '\\')
99        fields.update(pattern=raw_repr)
100
101    template = 'self._pattern({pattern})'
102
103
104class Lookahead(_Decorator):
105    template = '''\
106                with self._if():
107                {exp:1::}\
108                '''
109
110
111class NegativeLookahead(_Decorator):
112    template = '''\
113                with self._ifnot():
114                {exp:1::}\
115                '''
116
117
118class Sequence(Base):
119    def render_fields(self, fields):
120        fields.update(seq='\n'.join(self.rend(s) for s in self.node.sequence))
121
122    template = '{seq}'
123
124
125class Choice(Base):
126    def render_fields(self, fields):
127        template = trim(self.option_template)
128        options = [
129            template.format(
130                option=indent(self.rend(o))) for o in self.node.options
131        ]
132        options = '\n'.join(o for o in options)
133        firstset = ' '.join(f[0] for f in sorted(self.node.firstset) if f)
134        if firstset:
135            error = 'expecting one of: ' + firstset
136        else:
137            error = 'no available options'
138        fields.update(n=self.counter(),
139                      options=indent(options),
140                      error=urepr(error)
141                      )
142
143    def render(self, **fields):
144        if len(self.node.options) == 1:
145            return self.rend(self.options[0], **fields)
146        else:
147            return super(Choice, self).render(**fields)
148
149    option_template = '''\
150                    with self._option():
151                    {option}\
152                    '''
153
154    template = '''\
155                with self._choice():
156                {options}
157                    self._error({error})\
158                '''
159
160
161class Closure(_Decorator):
162    def render_fields(self, fields):
163        fields.update(n=self.counter())
164
165    def render(self, **fields):
166        if {()} in self.node.exp.firstset:
167            raise CodegenError('may repeat empty sequence')
168        return '\n' + super(Closure, self).render(**fields)
169
170    template = '''\
171                def block{n}():
172                {exp:1::}
173                self._closure(block{n})\
174                '''
175
176
177class PositiveClosure(Closure):
178    template = '''\
179                def block{n}():
180                {exp:1::}
181                self._positive_closure(block{n})\
182                '''
183
184
185class Join(_Decorator):
186    def render_fields(self, fields):
187        fields.update(n=self.counter())
188
189    def render(self, **fields):
190        if {()} in self.node.exp.firstset:
191            raise CodegenError('may repeat empty sequence')
192        return '\n' + super(Join, self).render(**fields)
193
194    template = '''\
195                def sep{n}():
196                {sep:1::}
197
198                def block{n}():
199                {exp:1::}
200                self._join(block{n}, sep{n})\
201                '''
202
203
204class PositiveJoin(Join):
205    template = '''\
206                def sep{n}():
207                {sep:1::}
208
209                def block{n}():
210                {exp:1::}
211                self._positive_join(block{n}, sep{n})\
212                '''
213
214
215class Gather(Join):
216    template = '''\
217                def sep{n}():
218                {sep:1::}
219
220                def block{n}():
221                {exp:1::}
222                self._gather(block{n}, sep{n})\
223                '''
224
225
226class PositiveGather(Join):
227    template = '''\
228                def sep{n}():
229                {sep:1::}
230
231                def block{n}():
232                {exp:1::}
233                self._positive_gather(block{n}, sep{n})\
234                '''
235
236
237class LeftJoin(PositiveJoin):
238    template = '''\
239                def sep{n}():
240                {sep:1::}
241
242                def block{n}():
243                {exp:1::}
244                self._left_join(block{n}, sep{n})\
245                '''
246
247
248class RightJoin(PositiveJoin):
249    template = '''\
250                def sep{n}():
251                {sep:1::}
252
253                def block{n}():
254                {exp:1::}
255                self._right_join(block{n}, sep{n})\
256                '''
257
258
259class EmptyClosure(Base):
260    template = 'self._empty_closure()'
261
262
263class Optional(_Decorator):
264    template = '''\
265                with self._optional():
266                {exp:1::}\
267                '''
268
269
270class Cut(Base):
271    template = 'self._cut()'
272
273
274class Named(_Decorator):
275    def __str__(self):
276        return '%s:%s' % (self.name, self.rend(self.exp))
277
278    def render_fields(self, fields):
279        fields.update(n=self.counter(),
280                      name=safe_name(self.node.name)
281                      )
282
283    template = '''
284                {exp}
285                self.name_last_node('{name}')\
286                '''
287
288
289class NamedList(Named):
290    template = '''
291                {exp}
292                self.add_last_node_to_name('{name}')\
293                '''
294
295
296class Override(Named):
297    pass
298
299
300class OverrideList(NamedList):
301    pass
302
303
304class Special(Base):
305    pass
306
307
308class RuleRef(Base):
309    template = "self._{name}_()"
310
311
312class RuleInclude(_Decorator):
313    def render_fields(self, fields):
314        super(RuleInclude, self).render_fields(fields)
315        fields.update(exp=self.rend(self.node.rule.exp))
316
317    template = '''
318                {exp}
319                '''
320
321
322class Rule(_Decorator):
323    @staticmethod
324    def param_repr(p):
325        if isinstance(p, (int, float)):
326            return ustr(p)
327        else:
328            return urepr(p.split(BASE_CLASS_TOKEN)[0])
329
330    def render_fields(self, fields):
331        self.reset_counter()
332
333        params = kwparams = ''
334        if self.node.params:
335            params = ', '.join(
336                self.param_repr(self.rend(p))
337                for p in self.node.params
338            )
339        if self.node.kwparams:
340            kwparams = ', '.join(
341                '%s=%s'
342                %
343                (k, self.param_repr(self.rend(v)))
344                for k, v in self.kwparams.items()
345            )
346
347        if params and kwparams:
348            params = params + ', ' + kwparams
349        elif kwparams:
350            params = kwparams
351
352        fields.update(params=params)
353
354        defines = compress_seq(self.defines())
355        ldefs = set(safe_name(d) for d, l in defines if l)
356        sdefs = set(safe_name(d) for d, l in defines if not l and d not in ldefs)
357
358        if not (sdefs or ldefs):
359            sdefines = ''
360        else:
361            sdefs = '[%s]' % ', '.join(urepr(d) for d in sorted(sdefs))
362            ldefs = '[%s]' % ', '.join(urepr(d) for d in sorted(ldefs))
363            if not ldefs:
364                sdefines = '\n\n    self.ast._define(%s, %s)' % (sdefs, ldefs)
365            else:
366                sdefines = indent(
367                    '\n' +
368                    trim(self.define_template % (sdefs, ldefs))
369                )
370
371        fields.update(defines=sdefines)
372        fields.update(
373            check_name='\n    self._check_name()' if self.is_name else '',
374        )
375
376    template = '''
377        @graken({params})
378        def _{name}_(self):
379        {exp:1::}{check_name}{defines}
380        '''
381
382    define_template = '''\
383            self.ast._define(
384                %s,
385                %s
386            )\
387        '''
388
389
390class BasedRule(Rule):
391    def defines(self):
392        return self.rhs.defines()
393
394    def render_fields(self, fields):
395        super(BasedRule, self).render_fields(fields)
396        fields.update(exp=self.rhs)
397
398
399class Grammar(Base):
400    def render_fields(self, fields):
401        abstract_template = trim(self.abstract_rule_template)
402        abstract_rules = [
403            abstract_template.format(name=safe_name(rule.name))
404            for rule in self.node.rules
405        ]
406        abstract_rules = indent('\n'.join(abstract_rules))
407
408        if self.node.whitespace is not None:
409            whitespace = urepr(self.node.whitespace)
410        elif self.node.directives.get('whitespace') is not None:
411            whitespace = 're.compile({0}, RE_FLAGS | re.DOTALL)'.format(urepr(self.node.directives.get('whitespace')))
412        else:
413            whitespace = 'None'
414
415        if self.node.nameguard is not None:
416            nameguard = urepr(self.node.nameguard)
417        elif self.node.directives.get('nameguard') is not None:
418            nameguard = self.node.directives.get('nameguard')
419        else:
420            nameguard = 'None'
421
422        comments_re = urepr(self.node.directives.get('comments'))
423        eol_comments_re = urepr(self.node.directives.get('eol_comments'))
424        ignorecase = self.node.directives.get('ignorecase', 'None')
425        left_recursion = self.node.directives.get('left_recursion', False)
426        parseinfo = self.node.directives.get('parseinfo', True)
427
428        namechars = urepr(self.node.directives.get('namechars') or '')
429
430        rules = '\n'.join([
431            self.get_renderer(rule).render() for rule in self.node.rules
432        ])
433
434        version = str(tuple(int(n) for n in str(timestamp()).split('.')))
435
436        keywords = '\n'.join("    %s," % urepr(k) for k in sorted(self.keywords))
437        if keywords:
438            keywords = '\n%s\n' % keywords
439
440        fields.update(rules=indent(rules),
441                      abstract_rules=abstract_rules,
442                      version=version,
443                      whitespace=whitespace,
444                      nameguard=nameguard,
445                      ignorecase=ignorecase,
446                      comments_re=comments_re,
447                      eol_comments_re=eol_comments_re,
448                      left_recursion=left_recursion,
449                      parseinfo=parseinfo,
450                      keywords=keywords,
451                      namechars=namechars,
452                      )
453
454    abstract_rule_template = '''
455            def {name}(self, ast):
456                return ast
457            '''
458
459    template = '''\
460                #!/usr/bin/env python
461                # -*- coding: utf-8 -*-
462
463                # CAVEAT UTILITOR
464                #
465                # This file was automatically generated by Grako.
466                #
467                #    https://pypi.python.org/pypi/grako/
468                #
469                # Any changes you make to it will be overwritten the next time
470                # the file is generated.
471
472
473                from __future__ import print_function, division, absolute_import, unicode_literals
474
475                from grako.buffering import Buffer
476                from grako.parsing import graken, Parser
477                from grako.util import re, RE_FLAGS, generic_main  # noqa
478
479
480                KEYWORDS = {{{keywords}}}
481
482
483                class {name}Buffer(Buffer):
484                    def __init__(
485                        self,
486                        text,
487                        whitespace={whitespace},
488                        nameguard={nameguard},
489                        comments_re={comments_re},
490                        eol_comments_re={eol_comments_re},
491                        ignorecase={ignorecase},
492                        namechars={namechars},
493                        **kwargs
494                    ):
495                        super({name}Buffer, self).__init__(
496                            text,
497                            whitespace=whitespace,
498                            nameguard=nameguard,
499                            comments_re=comments_re,
500                            eol_comments_re=eol_comments_re,
501                            ignorecase=ignorecase,
502                            namechars=namechars,
503                            **kwargs
504                        )
505
506
507                class {name}Parser(Parser):
508                    def __init__(
509                        self,
510                        whitespace={whitespace},
511                        nameguard={nameguard},
512                        comments_re={comments_re},
513                        eol_comments_re={eol_comments_re},
514                        ignorecase={ignorecase},
515                        left_recursion={left_recursion},
516                        parseinfo={parseinfo},
517                        keywords=None,
518                        namechars={namechars},
519                        buffer_class={name}Buffer,
520                        **kwargs
521                    ):
522                        if keywords is None:
523                            keywords = KEYWORDS
524                        super({name}Parser, self).__init__(
525                            whitespace=whitespace,
526                            nameguard=nameguard,
527                            comments_re=comments_re,
528                            eol_comments_re=eol_comments_re,
529                            ignorecase=ignorecase,
530                            left_recursion=left_recursion,
531                            parseinfo=parseinfo,
532                            keywords=keywords,
533                            namechars=namechars,
534                            buffer_class=buffer_class,
535                            **kwargs
536                        )
537
538                {rules}
539
540
541                class {name}Semantics(object):
542                {abstract_rules}
543
544
545                def main(filename, startrule, **kwargs):
546                    with open(filename) as f:
547                        text = f.read()
548                    parser = {name}Parser()
549                    return parser.parse(text, startrule, filename=filename, **kwargs)
550
551
552                if __name__ == '__main__':
553                    import json
554                    from grako.util import asjson
555
556                    ast = generic_main(main, {name}Parser, name='{name}')
557                    print('AST:')
558                    print(ast)
559                    print()
560                    print('JSON:')
561                    print(json.dumps(asjson(ast), indent=2))
562                    print()
563                '''
564