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