Changeset 481
- Timestamp:
- 12/06/07 10:20:15 (1 year ago)
- Files:
-
- cly/trunk/cly/builder.py (modified) (7 diffs)
- cly/trunk/cly/exceptions.py (modified) (3 diffs)
- cly/trunk/cly/interactive.py (modified) (2 diffs)
- cly/trunk/cly/rlext.py (modified) (2 diffs)
- cly/trunk/cly/test.py (modified) (2 diffs)
- cly/trunk/doc/developers-guide.rst (modified) (2 diffs)
- cly/trunk/.todo (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
cly/trunk/cly/builder.py
r470 r481 14 14 import os 15 15 import posixpath 16 from xml.dom import minidom 17 from inspect import isclass 18 from cly.exceptions import * 16 19 17 20 … … 22 25 23 26 24 from cly.exceptions import *25 26 27 27 class Node(object): 28 28 """The base class for all grammar nodes. … … 34 34 pairs. There is a useful class called Help which can be used for 35 35 this purpose. 36 37 Help strings are required.38 39 >>> Node()40 Traceback (most recent call last):41 ...42 TypeError: __init__() takes at least 2 arguments (1 given)43 >>> Node("Something")44 <Node:/>45 36 46 37 ``name=None``: string … … 96 87 traversals = 1 97 88 98 def __init__(self, help , *args, **kwargs):89 def __init__(self, help='', *args, **kwargs): 99 90 self._children = {} 100 91 if isinstance(help, basestring): … … 475 466 476 467 def __init__(self, target, *args, **kwargs): 477 Node.__init__(self, '<alias for "%s">' % target, *args, **kwargs) 468 Node.__init__(self, '<alias for "%s">' % target, 469 *args, **kwargs) 478 470 self._target = target 479 471 … … 543 535 with_user_context = None 544 536 545 def __init__(self, help , callback=None, *args, **kwargs):537 def __init__(self, help='', callback=None, *args, **kwargs): 546 538 if isinstance(help, basestring): 547 539 help_string = help … … 658 650 def terminal(self, context): 659 651 """Null-op for empty lines.""" 652 653 def from_xml(cls, xml, extra_nodes=None, **locals): 654 """Build a CLY Grammar from XML. 655 656 ``xml``: string 657 XML source 658 ``extra_nodes``: dictionary 659 Dictionary of Node subclasses. 660 ``locals``: 661 Valid locals() when evaluating XML grammar node attributes. 662 663 Returns a new Grammar object. 664 """ 665 try: 666 dom = minidom.parseString(xml) 667 except Exception, e: 668 raise XMLParseError(str(e)) 669 670 extra_nodes = extra_nodes or {} 671 672 def boolean(v): 673 return v in ('True', 'true') 674 675 def evaluate(v): 676 return eval(v, globals(), locals) 677 678 arg_types = { 679 'traversals': int, 680 'group': int, 681 'order': int, 682 'match_candidates': boolean, 683 } 684 685 arg_types.update(dict.fromkeys( 686 'children follow selected next match advance visible ' \ 687 'terminal depth path candidates find valid callback'.split(), 688 evaluate 689 )) 690 691 node_types = dict([(v.__name__.lower(), v) 692 for v in globals().values() 693 if isclass(v) and issubclass(v, Node)]) 694 695 node_types.update(extra_nodes) 696 697 def parse(parent, xnode): 698 if not xnode: 699 return 700 701 if xnode.nodeType == minidom.Node.ELEMENT_NODE: 702 cls = node_types.get(xnode.localName.lower()) 703 if not cls: 704 raise XMLParseError('Invalid node type "%s"' % name) 705 706 attributes = dict([(str(k), v) for k, v 707 in xnode.attributes.items()]) 708 709 for k, v in attributes.items(): 710 if k.startswith('eval:'): 711 attributes.pop(k) 712 k = k[5:] 713 v = evaluate(v) 714 else: 715 v = arg_types.get(k, str)(v) 716 attributes[k] = v 717 718 name = attributes.pop('name', None) 719 node = cls(**attributes) 720 if name: 721 path = parent.path() + '/' + name 722 parent(**{str(name): node}) 723 else: 724 parent(node) 725 else: 726 node = parent 727 728 parse(node, xnode.firstChild) 729 parse(parent, xnode.nextSibling) 730 731 grammar = Grammar() 732 if dom.firstChild.localName != 'grammar': 733 raise XMLParseError('Invalid root element "%s", expected "grammar"' 734 % dom.firstChild.localName) 735 parse(grammar, dom.firstChild.firstChild) 736 return grammar 737 738 from_xml = classmethod(from_xml) 660 739 661 740 cly/trunk/cly/exceptions.py
r434 r481 14 14 15 15 __all__ = ['Error', 'InvalidHelp', 'InvalidNodePath', 'InvalidAnonymousNode', 16 'ParseError', 'UnexpectedEOL', 'InvalidToken', 'ValidationError'] 16 'ParseError', 'UnexpectedEOL', 'InvalidToken', 'ValidationError', 17 'XMLParseError'] 17 18 __docformat__ = 'restructuredtext en' 18 19 … … 37 38 38 39 40 class XMLParseError(Error): 41 """Report an XML grammar parsing error.""" 42 43 39 44 class ParseError(Error): 40 45 """Report a parse error. Output is formatted using string templates, … … 48 53 49 54 def __init__(self, context, message=None, **kwargs): 50 Error.__init__(self) 55 template = string.Template(message or self.message) 56 message = template.safe_substitute(remaining=context.remaining, 57 **kwargs) 58 Error.__init__(self, message) 51 59 self.context = context 52 self.template_args = kwargs53 if message is not None:54 self.message = message55 60 56 def __str__(self):57 template = string.Template(self.message)58 return template.safe_substitute(remaining=self.context.remaining,59 **self.template_args)60 61 61 62 class UnexpectedEOL(ParseError): cly/trunk/cly/interactive.py
r468 r481 213 213 from StringIO import StringIO 214 214 out = StringIO() 215 traceback.print_exc(file =out)215 traceback.print_exc(file=out) 216 216 print >>sys.stderr, str(exception) 217 217 print >>sys.stderr, out.getvalue() … … 265 265 Interact._dump_traceback(e) 266 266 cly.rlext.force_redisplay() 267 r aise267 return 0 268 268 269 269 cly/trunk/cly/rlext.py
r468 r481 5 5 6 6 # Type declarations 7 # int rl_bind_key (int KEY, rl_command_func_t *FUNCTION);'8 7 rl_command_func_t = CFUNCTYPE(c_int, c_int, c_int) 9 8 10 9 # Cursor variables 11 rl_point = c ast(readline.rl_point, POINTER(c_int))12 rl_end = c ast(readline.rl_end, POINTER(c_int))10 rl_point = c_int.in_dll(readline, 'rl_point') 11 rl_end = c_int.in_dll(readline, 'rl_end') 13 12 14 13 rl_forced_update_display = readline.rl_forced_update_display … … 32 31 """Set or get the cursor location.""" 33 32 if pos is None: 34 return rl_point. contents.value35 elif rl_point. contents.value > rl_end.contents.value:36 rl_point. contents = rl_end.contents37 elif rl_point. contents.value < 0:38 rl_point. contents = c_int(0)33 return rl_point.value 34 elif rl_point.value > rl_end.value: 35 rl_point.value = rl_end.value 36 elif rl_point.value < 0: 37 rl_point.value = 0 39 38 else: 40 rl_point.contents = c_int(pos) 39 rl_point.value = pos 40 return 0 cly/trunk/cly/test.py
r434 r481 9 9 import unittest 10 10 import doctest 11 from cly import Grammar, Parser 12 13 14 class TestXMLGrammar(unittest.TestCase): 15 """Test XML grammar parser.""" 16 def setUp(self): 17 self._output = None 18 19 def _echo(self, **kwargs): 20 self._output = kwargs 21 22 def test_basic(self): 23 xml = """<?xml version="1.0"?> 24 <grammar> 25 <node name='echo'> 26 <variable name='text'> 27 <action callback='echo'/> 28 </variable> 29 </node> 30 </grammar> 31 """ 32 33 grammar = Grammar.from_xml(xml, echo=self._echo) 34 parser = Parser(grammar) 35 parser.execute('echo magic') 36 self.assertEqual(self._output, {'text': 'magic'}) 37 38 def test_integer_types(self): 39 xml = """<?xml version="1.0"?> 40 <grammar> 41 <node name='echo'> 42 <variable name='text' traversals='0'> 43 <alias target='/echo/*'/> 44 <action callback='echo'/> 45 </variable> 46 </node> 47 </grammar> 48 """ 49 50 grammar = Grammar.from_xml(xml, echo=self._echo) 51 parser = Parser(grammar) 52 parser.execute('echo magic monkey') 53 self.assertEqual(self._output, {'text': ['magic', 'monkey']}) 54 55 def test_group(self): 56 xml = """<?xml version="1.0"?> 57 <grammar> 58 <node name='echo'> 59 <group traversals='0'> 60 <variable name='text'> 61 <alias target='../../*'/> 62 <action callback='echo'/> 63 </variable> 64 </group> 65 </node> 66 </grammar> 67 """ 68 69 grammar = Grammar.from_xml(xml, echo=self._echo) 70 parser = Parser(grammar) 71 parser.execute('echo magic monkey') 72 self.assertEqual(self._output, {'text': ['magic', 'monkey']}) 73 74 def test_completion(self): 75 xml = """<?xml version="1.0"?> 76 <grammar> 77 <node name='echo'> 78 <variable name='text' candidates='candidates'> 79 <action callback='echo'/> 80 </variable> 81 </node> 82 </grammar> 83 """ 84 85 def candidates(context, text): 86 return ['monkey', 'muppet'] 87 88 grammar = Grammar.from_xml(xml, echo=self._echo, candidates=candidates) 89 parser = Parser(grammar) 90 context = parser.parse('echo ') 91 self.assertEqual(list(context.candidates()), ['monkey', 'muppet']) 11 92 12 93 … … 21 102 22 103 suite = unittest.TestSuite() 104 suite.addTest(unittest.makeSuite(TestXMLGrammar, 'test')) 23 105 suite.addTest(doctest.DocTestSuite(cly)) 24 106 suite.addTest(doctest.DocTestSuite(cly.interactive)) cly/trunk/doc/developers-guide.rst
r470 r481 10 10 11 11 1. `Defining the grammar`_. 12 2. `Creating a parser`_ based on that grammar. 13 3. Parsing input: 14 15 1. Create a context in which to store parse state. 16 2. Traverse grammar, tokenising and matching input stream. 17 3. Collect variables from input stream. 18 19 4. Execute actions, passing collected variables to callback. 12 2. `Parsing`_ input text. 20 13 21 14 Defining the Grammar … … 301 294 already been collected. 302 295 303 Creating a Parser 304 ------- ----------296 Parsing 297 ------- 305 298 306 299 To actually utilise a grammar you need to bind it to a ``Parser`` 307 300 object. The parser takes care of creating a context for each parse run, 308 and parsing the input. The actual collection of variables is taken care 309 of by the ``Context`` object. 310 311 Sometimes it's useful to pass an arbitrary object through to the grammar 312 callbacks. This can be achieved with the ``user_context`` parameter to 313 the ``parse()`` and ``execute()`` methods of ``Parser``. This object is 314 available to callbacks as the first parameter if the corresponding 315 ``Action`` has ``with_user_context=True`` (this flag can also be set 316 parser-wide by passing the same parameter to the ``Parser`` 317 constructor). It is also available to ``Node`` subclasses as the 318 ``context`` parameter to most methods. 319 320 You can also parse the input without executing the callback, by calling 321 the ``Parser.parse()`` method. This returns a ``Context`` object which 322 can be inspected if desired. Normally though, just call 323 ``Parser.execute()``. 324 325 Here's an example of normal parser use: 326 327 .. code-block:: python 328 329 330 def one(one): 331 print one 332 333 grammar = Grammar( 334 one=Number('One')( 335 Action('Execute one', one), 336 ), 337 ) 301 parsing the input, and usually executing any callbacks. 302 303 Basic usage is: 304 305 .. code-block:: python 338 306 339 307 parser = Parser(grammar) 340 context = parser.parse 341 parser.execute('1234') 342 343 344 And here's an example of how to pass a user-defined object through to 345 your callbacks: 346 347 .. code-block:: python 348 349 def one(context, one): 350 print 'One:', context, one 351 352 grammar = Grammar( 353 one=Number('One')( 354 Action('Execute one', one, with_user_context=True), 355 ), 356 ) 357 358 parser = Parser(grammar) 359 context = parser.parse 360 parser.execute('1234', user_context='Moo') 361 362 This will print:: 363 364 One: Moo 1234 308 parser.execute('some input text') 309 310 This will parse the input text and execute any callbacks. If a parse 311 error occurs a ``cly.exceptions.ParseError`` will be raised. 312 313 Passing User-defined Objects to Callbacks 314 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 315 316 Sometimes it's useful to be able to pass an arbitrary object through to 317 the grammar callbacks. This is achieved by first enabling this behaviour 318 on the desired action nodes by either passing ``with_user_context=True`` 319 to the constructor or enabled across the entire grammar by doing the 320 same on the ``Parser`` constructor. Once enabled, simply pass the object 321 to the ``execute()`` method via the ``user_context`` parameter. That 322 object will then be provided to all action callbacks as the first 323 parameter. 324 325 One way of applying this is by binding all callbacks to methods on a 326 single *class*, then passing an instance of that class as the context: 327 328 .. code-block:: python 329 330 class A(object): 331 def __init__(self, name): 332 self.name = name 333 334 def one(self, one): 335 print "One:", self.name, one 336 337 def two(self, two): 338 print "Two:", self.name, two 339 340 grammar = Grammar( 341 one=Variable('One')( 342 Action('Execute one', A.one), 343 ), 344 two=Variable('Two')( 345 Action('Execute two', A.two), 346 ), 347 ) 348 349 a = A('a') 350 b = A('b') 351 352 parser = Parser(grammar, with_user_context=True) 353 354 parser.execute('one', user_context=a) 355 parser.execute('two', user_context=b) 356 357 This will output:: 358 359 One: a one 360 One: b two 361 365 362 366 363 .. _API documentation: http://swapoff.org/cly/docs cly/trunk/.todo
r434 r481 1 <todo version="0.1.19"> 1 <?xml version="1.0"?> 2 <todo version="0.1.20"> 2 3 <title> 3 4 CLY … … 15 16 Allow with_context to be specified per-parser. 16 17 </note> 18 <note priority="medium" time="1196955863"> 19 Figure out why rlext.py dies when the parser is used... 20 </note> 17 21 </todo>
